﻿// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;

using Microsoft.Cci.Extensions;

namespace Microsoft.Cci.Comparers
{
    public sealed class ApiComparer<T> : IComparer<T>
    {
        private readonly Func<T, ApiKind> _kindProvider;
        private readonly Func<T, string> _nameProvider;

        public ApiComparer(Func<T, ApiKind> kindProvider, Func<T, string> nameProvider)
        {
            _kindProvider = kindProvider;
            _nameProvider = nameProvider;
        }

        public int Compare(T x, T y)
        {
            var kindX = _kindProvider(x);
            var kindY = _kindProvider(y);
            var kindComparison = CompareKind(kindX, kindY);
            if (kindComparison != 0)
                return kindComparison;

            var nameX = _nameProvider(x);
            var nameY = _nameProvider(y);
            if (kindX == ApiKind.Namespace && kindY == ApiKind.Namespace)
                return CompareQualifiedNamespaceNames(nameX, nameY);

            return CompareNames(nameX, nameY);
        }

        private static int CompareKind(ApiKind x, ApiKind y)
        {
            var xKindOrder = GetKindOrder(x);
            var yKindOrder = GetKindOrder(y);
            return xKindOrder.CompareTo(yKindOrder);
        }

        private static int CompareNames(string x, string y)
        {
            var xNonGenericName = GetNonGenericName(x);
            var yNonGenericName = GetNonGenericName(y);

            var nameComparison = StringComparer.OrdinalIgnoreCase.Compare(xNonGenericName, yNonGenericName);
            if (nameComparison != 0)
                return nameComparison;

            return x.Length.CompareTo(y.Length);
        }

        private static int CompareQualifiedNamespaceNames(string nameX, string nameY)
        {
            string beforeFirstDotX;
            string afterFirstDotX;
            SplitAtFirstDot(nameX, out beforeFirstDotX, out afterFirstDotX);

            string beforeFirstDotY;
            string afterFirstDotY;
            SplitAtFirstDot(nameY, out beforeFirstDotY, out afterFirstDotY);

            var firstComparison = CompareNamespaceNames(beforeFirstDotX, beforeFirstDotY);
            if (firstComparison != 0)
                return firstComparison;

            return StringComparer.OrdinalIgnoreCase.Compare(nameX, nameY);
        }

        private static int CompareNamespaceNames(string nameX, string nameY)
        {
            var orderX = GetNamspaceOrder(nameX);
            var orderY = GetNamspaceOrder(nameY);

            var comparison = orderX.CompareTo(orderY);
            if (comparison != 0)
                return comparison;

            return StringComparer.OrdinalIgnoreCase.Compare(nameX, nameY);
        }

        private static int GetKindOrder(ApiKind kind)
        {
            switch (kind)
            {
                // Namespace -- no order
                case ApiKind.Namespace:
                    return 0;

                // Types -- no order between types
                case ApiKind.Interface:
                case ApiKind.Delegate:
                case ApiKind.Enum:
                case ApiKind.Struct:
                case ApiKind.Class:
                    return 1;

                // Members
                case ApiKind.EnumField:
                case ApiKind.Field:
                    return 2;
                case ApiKind.Constructor:
                    return 3;
                case ApiKind.Property:
                    return 4;
                case ApiKind.Method:
                case ApiKind.PropertyAccessor:
                case ApiKind.EventAccessor:
                case ApiKind.DelegateMember:
                    return 5;
                case ApiKind.Event:
                    return 6;
                default:
                    throw new ArgumentOutOfRangeException("kind");
            }
        }

        private static int GetNamspaceOrder(string name)
        {
            switch (name)
            {
                case "System":
                    return 0;
                case "Microsoft":
                    return 1;
                case "Windows":
                    return 2;
                default:
                    return 3;
            }
        }

        private static string GetNonGenericName(string name)
        {
            var i = name.IndexOf("<", StringComparison.OrdinalIgnoreCase);
            return i > -1 ? name.Substring(0, i) : name;
        }

        private static void SplitAtFirstDot(string qualifiedName, out string beforeFirstDot, out string afterFirstDot)
        {
            var firstDot = qualifiedName.IndexOf('.');
            if (firstDot < 0)
            {
                beforeFirstDot = qualifiedName;
                afterFirstDot = string.Empty;
            }
            else
            {
                beforeFirstDot = qualifiedName.Substring(0, firstDot);
                afterFirstDot = qualifiedName.Substring(firstDot + 1);
            }
        }
    }
}