// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;

namespace Microsoft.AspNetCore.Routing.Matching
{
    // Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
    //
    // NOTE:
    // When ordering endpoints, we compare the route templates as an absolute last resort.
    // This is used as a factor to ensure that we always have a predictable ordering
    // for tests, errors, etc.
    //
    // When we group endpoints we don't consider the route template, because we're trying
    // to group endpoints not separate them.
    //
    // TLDR: 
    //  IComparer implementation considers the template string as a tiebreaker.
    //  IEqualityComparer implementation does not.
    //  This is cool and good.
    internal class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
    {
        private readonly IComparer<Endpoint>[] _comparers;

        public EndpointComparer(IEndpointComparerPolicy[] policies)
        {
            // Order, Precedence, (others)...
            _comparers = new IComparer<Endpoint>[2 + policies.Length];
            _comparers[0] = OrderComparer.Instance;
            _comparers[1] = PrecedenceComparer.Instance;
            for (var i = 0; i < policies.Length; i++)
            {
                _comparers[i + 2] = policies[i].Comparer;
            }
        }

        public int Compare(Endpoint x, Endpoint y)
        {
            // We don't expose this publicly, and we should never call it on
            // a null endpoint.
            Debug.Assert(x != null);
            Debug.Assert(y != null);

            var compare = CompareCore(x, y);

            // Since we're sorting, use the route template as a last resort.
            return compare == 0 ? ComparePattern(x, y) : compare;
        }

        private int ComparePattern(Endpoint x, Endpoint y)
        {
            // A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
            var routeEndpointX = x as RouteEndpoint;
            var routeEndpointY = y as RouteEndpoint;

            if (routeEndpointX != null)
            {
                if (routeEndpointY != null)
                {
                    return string.Compare(routeEndpointX.RoutePattern.RawText, routeEndpointY.RoutePattern.RawText, StringComparison.OrdinalIgnoreCase);
                }

                return 1;
            }
            else if (routeEndpointY != null)
            {
                return -1;
            }

            return 0;
        }

        public bool Equals(Endpoint x, Endpoint y)
        {
            // We don't expose this publicly, and we should never call it on
            // a null endpoint.
            Debug.Assert(x != null);
            Debug.Assert(y != null);

            return CompareCore(x, y) == 0;
        }
        
        public int GetHashCode(Endpoint obj)
        {
            // This should not be possible to call publicly.
            Debug.Fail("We don't expect this to be called.");
            throw new System.NotImplementedException();
        }

        private int CompareCore(Endpoint x, Endpoint y)
        {
            for (var i = 0; i < _comparers.Length; i++)
            {
                var compare = _comparers[i].Compare(x, y);
                if (compare != 0)
                {
                    return compare;
                }
            }

            return 0;
        }

        private class OrderComparer : IComparer<Endpoint>
        {
            public static readonly IComparer<Endpoint> Instance = new OrderComparer();

            public int Compare(Endpoint x, Endpoint y)
            {
                var routeEndpointX = x as RouteEndpoint;
                var routeEndpointY = y as RouteEndpoint;

                if (routeEndpointX != null)
                {
                    if (routeEndpointY != null)
                    {
                        return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
                    }

                    return 1;
                }
                else if (routeEndpointY != null)
                {
                    return -1;
                }

                return 0;
            }
        }

        private class PrecedenceComparer : IComparer<Endpoint>
        {
            public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();

            public int Compare(Endpoint x, Endpoint y)
            {
                var routeEndpointX = x as RouteEndpoint;
                var routeEndpointY = y as RouteEndpoint;

                if (routeEndpointX != null)
                {
                    if (routeEndpointY != null)
                    {
                        return routeEndpointX.RoutePattern.InboundPrecedence
                            .CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
                    }

                    return 1;
                }
                else if (routeEndpointY != null)
                {
                    return -1;
                }

                return 0;
            }
        }
    }
}
