// -----------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
// -----------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Reflection;
using System.Text;
using Microsoft.Internal;

namespace System.ComponentModel.Composition
{
    internal static class ContractNameServices
    {
        const char NamespaceSeparator = '.';
        const char ArrayOpeningBracket = '[';
        const char ArrayClosingBracket = ']';
        const char ArraySeparator = ',';
        const char PointerSymbol = '*';
        const char ReferenceSymbol = '&';
        const char GenericArityBackQuote = '`';
        const char NestedClassSeparator = '+';
        const char ContractNameGenericOpeningBracket = '(';
        const char ContractNameGenericClosingBracket = ')';
        const char ContractNameGenericArgumentSeparator = ',';
        const char CustomModifiersSeparator = ' ';
        const char GenericFormatOpeningBracket = '{';
        const char GenericFormatClosingBracket = '}';

        [ThreadStatic]
        private static Dictionary<Type, string> typeIdentityCache;

        private static Dictionary<Type, string> TypeIdentityCache
        {
            get
            {
                return typeIdentityCache = typeIdentityCache ?? new Dictionary<Type, string>();
            }
        }

        internal static string GetTypeIdentity(Type type)
        {
            return GetTypeIdentity(type, true);
        }

        internal static string GetTypeIdentity(Type type, bool formatGenericName)
        {
            Assumes.NotNull(type);
            string typeIdentity = null;

            if (!TypeIdentityCache.TryGetValue(type, out typeIdentity))
            {
                if (!type.IsAbstract && type.IsSubclassOf(typeof(Delegate)))
                {
                    MethodInfo method = type.GetMethod("Invoke");
                    typeIdentity = ContractNameServices.GetTypeIdentityFromMethod(method);
                }
                else if (type.IsGenericParameter)
                {
                    StringBuilder typeIdentityStringBuilder = new StringBuilder();
                    WriteTypeArgument(typeIdentityStringBuilder, false, type, formatGenericName);
                    typeIdentityStringBuilder.Remove(typeIdentityStringBuilder.Length - 1, 1);
                    typeIdentity = typeIdentityStringBuilder.ToString();
                }
                else
                {
                    StringBuilder typeIdentityStringBuilder = new StringBuilder();
                    WriteTypeWithNamespace(typeIdentityStringBuilder, type, formatGenericName);
                    typeIdentity = typeIdentityStringBuilder.ToString();
                }

                Assumes.IsTrue(!string.IsNullOrEmpty(typeIdentity));
                TypeIdentityCache.Add(type, typeIdentity);
            }

            return typeIdentity;
        }

        internal static string GetTypeIdentityFromMethod(MethodInfo method)
        {
            return GetTypeIdentityFromMethod(method, true);
        }

        internal static string GetTypeIdentityFromMethod(MethodInfo method, bool formatGenericName)
        {
            StringBuilder methodNameStringBuilder = new StringBuilder();

            WriteTypeWithNamespace(methodNameStringBuilder, method.ReturnType, formatGenericName);

            methodNameStringBuilder.Append("(");

            ParameterInfo[] parameters = method.GetParameters();

            for (int i = 0; i < parameters.Length; i++)
            {
                if (i != 0)
                {
                    methodNameStringBuilder.Append(",");
                }

                WriteTypeWithNamespace(methodNameStringBuilder, parameters[i].ParameterType, formatGenericName);
            }
            methodNameStringBuilder.Append(")");

            return methodNameStringBuilder.ToString();
        }

        private static void WriteTypeWithNamespace(StringBuilder typeName, Type type, bool formatGenericName)
        {
            // Writes type with namesapce
            if (!string.IsNullOrEmpty(type.Namespace))
            {
                typeName.Append(type.Namespace);
                typeName.Append(NamespaceSeparator);
            }
            WriteType(typeName, type, formatGenericName);
        }

        private static void WriteType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            // Writes type name
            if (type.IsGenericType)
            {
                //
                // Reflection format stores all the generic arguments (including the ones for parent types) on the leaf type.
                // These arguments are placed in a queue and are written out based on generic arity (`X) of each type
                //
                Queue<Type> genericTypeArguments = new Queue<Type>(type.GetGenericArguments());
                WriteGenericType(typeName, type, type.IsGenericTypeDefinition, genericTypeArguments, formatGenericName);
                Assumes.IsTrue(genericTypeArguments.Count == 0, "Expecting genericTypeArguments queue to be empty.");
            }
            else
            {
                WriteNonGenericType(typeName, type, formatGenericName);
            }
        }

        private static void WriteNonGenericType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes non-generic type
            //
            if (type.DeclaringType != null)
            {
                WriteType(typeName, type.DeclaringType, formatGenericName);
                typeName.Append(NestedClassSeparator);
            }
            if (type.IsArray)
            {
                WriteArrayType(typeName, type, formatGenericName);
            }
            else if (type.IsPointer)
            {
                WritePointerType(typeName, type, formatGenericName);
            }
            else if (type.IsByRef)
            {
                WriteByRefType(typeName, type, formatGenericName);
            }
            else
            {
                typeName.Append(type.Name);
            }
        }

        private static void WriteArrayType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes array type  e.g <TypeName>[]
            // Note that jagged arrays are stored in reverse order
            // e.g. C#: Int32[][,]  Reflection: Int32[,][]
            // we are following C# order for arrays
            //
            Type rootElementType = FindArrayElementType(type);
            WriteType(typeName, rootElementType, formatGenericName);
            Type elementType = type;
            do
            {
                WriteArrayTypeDimensions(typeName, elementType);
            }
            while ((elementType = elementType.GetElementType()) != null && elementType.IsArray);
        }

        private static void WritePointerType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes pointer type  e.g <TypeName>*
            //
            WriteType(typeName, type.GetElementType(), formatGenericName);
            typeName.Append(PointerSymbol);
        }

        private static void WriteByRefType(StringBuilder typeName, Type type, bool formatGenericName)
        {
            //
            // Writes by ref type e.g <TypeName>&
            //
            WriteType(typeName, type.GetElementType(), formatGenericName);
            typeName.Append(ReferenceSymbol);
        }

        private static void WriteArrayTypeDimensions(StringBuilder typeName, Type type)
        {
            //
            // Writes array type dimensions e.g. [,,]
            //
            typeName.Append(ArrayOpeningBracket);
            int rank = type.GetArrayRank();
            for (int i = 1; i < rank; i++)
            {
                typeName.Append(ArraySeparator);
            }
            typeName.Append(ArrayClosingBracket);
        }

        private static void WriteGenericType(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes generic type including parent generic types
            // genericTypeArguments contains type arguments obtained from the most nested type
            // isDefinition parameter indicates if we are dealing with generic type definition
            //
            if (type.DeclaringType != null)
            {
                if (type.DeclaringType.IsGenericType)
                {
                    WriteGenericType(typeName, type.DeclaringType, isDefinition, genericTypeArguments, formatGenericName);
                }
                else
                {
                    WriteNonGenericType(typeName, type.DeclaringType, formatGenericName);
                }
                typeName.Append(NestedClassSeparator);
            }
            WriteGenericTypeName(typeName, type, isDefinition, genericTypeArguments, formatGenericName);
        }

        private static void WriteGenericTypeName(StringBuilder typeName, Type type, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes generic type name, e.g. generic name and generic arguments
            //
            Assumes.IsTrue(type.IsGenericType, "Expecting type to be a generic type");
            int genericArity = GetGenericArity(type);
            string genericTypeName = FindGenericTypeName(type.GetGenericTypeDefinition().Name);
            typeName.Append(genericTypeName);
            WriteTypeArgumentsString(typeName, genericArity, isDefinition, genericTypeArguments, formatGenericName);
        }

        private static void WriteTypeArgumentsString(StringBuilder typeName, int argumentsCount, bool isDefinition, Queue<Type> genericTypeArguments, bool formatGenericName)
        {
            //
            // Writes type arguments in brackets, e.g. (<contract_name1>, <contract_name2>, ...)
            //
            if (argumentsCount == 0)
            {
                return;
            }
            typeName.Append(ContractNameGenericOpeningBracket);
            for (int i = 0; i < argumentsCount; i++)
            {
                Assumes.IsTrue(genericTypeArguments.Count > 0, "Expecting genericTypeArguments to contain at least one Type");
                Type genericTypeArgument = genericTypeArguments.Dequeue();
                WriteTypeArgument(typeName, isDefinition, genericTypeArgument, formatGenericName);
            }
            typeName.Remove(typeName.Length - 1, 1);
            typeName.Append(ContractNameGenericClosingBracket);
        }

        private static void WriteTypeArgument(StringBuilder typeName, bool isDefinition, Type genericTypeArgument, bool formatGenericName)
        {
            if (!isDefinition && !genericTypeArgument.IsGenericParameter)
            {
                WriteTypeWithNamespace(typeName, genericTypeArgument, formatGenericName);
            }

            if (formatGenericName && genericTypeArgument.IsGenericParameter)
            {
                typeName.Append(GenericFormatOpeningBracket);
                typeName.Append(genericTypeArgument.GenericParameterPosition);
                typeName.Append(GenericFormatClosingBracket);
            }
            typeName.Append(ContractNameGenericArgumentSeparator);
        }

        //internal for testability
        internal static void WriteCustomModifiers(StringBuilder typeName, string customKeyword, Type[] types, bool formatGenericName)
        {
            //
            // Writes custom modifiers in the format: customKeyword(<contract_name>,<contract_name>,...)
            //
            typeName.Append(CustomModifiersSeparator);
            typeName.Append(customKeyword);
            Queue<Type> typeArguments = new Queue<Type>(types);
            WriteTypeArgumentsString(typeName, types.Length, false, typeArguments, formatGenericName);
            Assumes.IsTrue(typeArguments.Count == 0, "Expecting genericTypeArguments queue to be empty.");
        }

        private static Type FindArrayElementType(Type type)
        {
            //
            // Gets array element type by calling GetElementType() until the element is not an array
            //
            Type elementType = type;
            while ((elementType = elementType.GetElementType()) != null && elementType.IsArray) { }
            return elementType;
        }

        private static string FindGenericTypeName(string genericName)
        {
            //
            // Gets generic type name omitting the backquote and arity indicator
            // List`1 -> List
            // Arity indicator is returned as output parameter
            //
            int indexOfBackQuote = genericName.IndexOf(GenericArityBackQuote);
            if (indexOfBackQuote > -1)
            {
                genericName = genericName.Substring(0, indexOfBackQuote);
            }
            return genericName;
        }

        private static int GetGenericArity(Type type)
        {
            if (type.DeclaringType == null)
            {
                return type.GetGenericArguments().Length;
            }

            // The generic arity is equal to the difference in the number of generic arguments
            // from the type and the declaring type.

            int delclaringTypeGenericArguments = type.DeclaringType.GetGenericArguments().Length;
            int typeGenericArguments = type.GetGenericArguments().Length;

            Assumes.IsTrue(typeGenericArguments >= delclaringTypeGenericArguments);

            return typeGenericArguments - delclaringTypeGenericArguments;
        }
    }
}
