using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Mono.Cecil;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Reflection;

namespace ILRuntime.CLR.TypeSystem
{
    public class ILType : IType
    {
        Dictionary<string, List<ILMethod>> methods;
        TypeReference typeRef;
        TypeDefinition definition;
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;
        ILMethod staticConstructor;
        List<ILMethod> constructors;
        IType[] fieldTypes;
        FieldDefinition[] fieldDefinitions;
        IType[] staticFieldTypes;
        FieldDefinition[] staticFieldDefinitions;
        Dictionary<string, int> fieldMapping;
        Dictionary<string, int> staticFieldMapping;
        ILTypeStaticInstance staticInstance;
        Dictionary<int, int> fieldTokenMapping = new Dictionary<int, int>();
        int fieldStartIdx = -1;
        int totalFieldCnt = -1;
        KeyValuePair<string, IType>[] genericArguments;
        IType baseType, byRefType, arrayType, enumType, elementType;
        Type arrayCLRType, byRefCLRType;
        IType[] interfaces;
        bool baseTypeInitialized = false;
        bool interfaceInitialized = false;
        List<ILType> genericInstances;
        bool isDelegate;
        ILRuntimeType reflectionType;
        IType firstCLRBaseType, firstCLRInterface;
        int hashCode = -1;
        static int instance_id = 0x10000000;
        public TypeDefinition TypeDefinition { get { return definition; } }

        public TypeReference TypeReference
        {
            get { return typeRef; }
            set
            {
                typeRef = value;
                RetriveDefinitino(value);
            }
        }

        public IType BaseType
        {
            get
            {
                if (!baseTypeInitialized)
                    InitializeBaseType();
                return baseType;
            }
        }

        public IType[] Implements
        {
            get
            {
                if (!interfaceInitialized)
                    InitializeInterfaces();
                return interfaces;
            }
        }

        public ILTypeStaticInstance StaticInstance
        {
            get
            {
                if (fieldMapping == null)
                    InitializeFields();
                if (methods == null)
                    InitializeMethods();
                return staticInstance;
            }
        }

        public IType[] FieldTypes
        {
            get
            {
                if (fieldMapping == null)
                    InitializeFields();
                return fieldTypes;
            }
        }

        public IType[] StaticFieldTypes
        {
            get
            {
                if (fieldMapping == null)
                    InitializeFields();
                return staticFieldTypes;
            }
        }

        public FieldDefinition[] StaticFieldDefinitions
        {
            get
            {
                if (fieldMapping == null)
                    InitializeFields();
                return staticFieldDefinitions;
            }
        }

        public Dictionary<string, int> FieldMapping
        {
            get
            {
                if (fieldMapping == null)
                    InitializeFields(); return fieldMapping;
            }
        }

        public IType FirstCLRBaseType
        {
            get
            {
                if (!baseTypeInitialized)
                    InitializeBaseType();
                return firstCLRBaseType;
            }
        }

        public IType FirstCLRInterface
        {
            get
            {
                if (!interfaceInitialized)
                    InitializeInterfaces();
                return firstCLRInterface;
            }
        }
        public bool HasGenericParameter
        {
            get
            {
                return definition.HasGenericParameters && genericArguments == null;
            }
        }

        public Dictionary<string, int> StaticFieldMapping { get { return staticFieldMapping; } }
        public ILRuntime.Runtime.Enviorment.AppDomain AppDomain
        {
            get
            {
                return appdomain;
            }
        }

        internal int FieldStartIndex
        {
            get
            {
                if (fieldStartIdx < 0)
                {
                    if (BaseType != null)
                    {
                        if (BaseType is ILType)
                        {
                            fieldStartIdx = ((ILType)BaseType).TotalFieldCount;
                        }
                        else
                            fieldStartIdx = 0;
                    }
                    else
                        fieldStartIdx = 0;
                }
                return fieldStartIdx;
            }
        }

        public int TotalFieldCount
        {
            get
            {
                if (totalFieldCnt < 0)
                {
                    if (fieldMapping == null)
                        InitializeFields();
                    if (BaseType != null)
                    {
                        if (BaseType is ILType)
                        {
                            totalFieldCnt = ((ILType)BaseType).TotalFieldCount + fieldTypes.Length;
                        }
                        else
                            totalFieldCnt = fieldTypes.Length;
                    }
                    else
                        totalFieldCnt = fieldTypes.Length;
                }
                return totalFieldCnt;
            }
        }

        /// <summary>
        /// 初始化IL类型
        /// </summary>
        /// <param name="def">MONO返回的类型定义</param>
        /// <param name="domain">ILdomain</param>
        public ILType(TypeReference def, Runtime.Enviorment.AppDomain domain)
        {
            this.typeRef = def;
            RetriveDefinitino(def);
            appdomain = domain;
        }

        /// <summary>
        /// 加载类型
        /// </summary>
        /// <param name="def"></param>
        void RetriveDefinitino(TypeReference def)
        {
            if (!def.IsGenericParameter)
            {
                if (def is TypeSpecification)
                    RetriveDefinitino(def.GetElementType());
                else
                    definition = def as TypeDefinition;
            }
        }

        public bool IsGenericInstance
        {
            get
            {
                return genericArguments != null;
            }
        }
        public KeyValuePair<string, IType>[] GenericArguments
        {
            get
            {
                return genericArguments;
            }
        }

        public IType ElementType { get { return elementType; } }

        public bool IsArray
        {
            get; private set;
        }

        private bool? isValueType;

        public bool IsValueType
        {
            get
            {
                if (isValueType == null)
                    isValueType = definition.IsValueType;

                return isValueType.Value;
            }
        }

        public bool IsDelegate
        {
            get
            {
                if (!baseTypeInitialized)
                    InitializeBaseType();
                return isDelegate;
            }
        }

        public Type TypeForCLR
        {
            get
            {
                if (!baseTypeInitialized)
                    InitializeBaseType();
                if (definition.IsEnum)
                {
                    if (enumType == null)
                        InitializeFields();
                    return enumType.TypeForCLR;
                }
                else if (typeRef is ArrayType)
                {
                    return arrayCLRType;
                }
                else if (typeRef is ByReferenceType)
                {
                    return byRefCLRType;
                }
                else if (FirstCLRBaseType != null && FirstCLRBaseType is CrossBindingAdaptor)
                {
                    return ((CrossBindingAdaptor)FirstCLRBaseType).RuntimeType.TypeForCLR;
                }
                else if (FirstCLRInterface != null && FirstCLRInterface is CrossBindingAdaptor)
                {
                    return ((CrossBindingAdaptor)FirstCLRInterface).RuntimeType.TypeForCLR;
                }
                else
                    return typeof(ILTypeInstance);
            }
        }

        public Type ReflectionType
        {
            get
            {
                if (reflectionType == null)
                    reflectionType = new ILRuntimeType(this);
                return reflectionType;
            }
        }

        public IType ByRefType
        {
            get
            {
                return byRefType;
            }
        }
        public IType ArrayType
        {
            get
            {
                return arrayType;
            }
        }

        public bool IsEnum
        {
            get
            {
                return definition.IsEnum;
            }
        }
        public string FullName
        {
            get
            {
                return typeRef.FullName;
            }
        }
        public string Name
        {
            get
            {
                return typeRef.Name;
            }
        }
        public List<IMethod> GetMethods()
        {
            if (methods == null)
                InitializeMethods();
            List<IMethod> res = new List<IMethod>();
            foreach (var i in methods)
            {
                foreach (var j in i.Value)
                    res.Add(j);
            }

            return res;
        }
        void InitializeInterfaces()
        {
            interfaceInitialized = true;
            if (definition.HasInterfaces)
            {
                interfaces = new IType[definition.Interfaces.Count];
                for (int i = 0; i < interfaces.Length; i++)
                {
                    interfaces[i] = appdomain.GetType(definition.Interfaces[i], this, null);
                    //only one clrInterface is valid
                    if (interfaces[i] is CLRType && firstCLRInterface == null)
                    {
                        CrossBindingAdaptor adaptor;
                        if (appdomain.CrossBindingAdaptors.TryGetValue(interfaces[i].TypeForCLR, out adaptor))
                        {
                            interfaces[i] = adaptor;
                            firstCLRInterface = adaptor;
                        }
                        else
                            throw new TypeLoadException("Cannot find Adaptor for:" + interfaces[i].TypeForCLR.ToString());
                    }
                }
            }
        }
        void InitializeBaseType()
        {
            if (definition.BaseType != null)
            {
                bool specialProcess = false;
                List<int> spIdx = null;
                if (definition.BaseType.IsGenericInstance)
                {
                    GenericInstanceType git = definition.BaseType as GenericInstanceType;
                    var elementType = appdomain.GetType(definition.BaseType.GetElementType(), this, null);
                    if (elementType is CLRType)
                    {
                        for (int i = 0; i < git.GenericArguments.Count; i++)
                        {
                            var ga = git.GenericArguments[i];
                            if (ga == typeRef)
                            {
                                specialProcess = true;
                                if (spIdx == null)
                                    spIdx = new List<int>();
                                spIdx.Add(i);
                            }
                        }
                    }
                }
                if (specialProcess)
                {
                    //如果泛型参数是自身，则必须要特殊处理，否则会StackOverflow
                    var elementType = appdomain.GetType(definition.BaseType.GetElementType(), this, null);
                    foreach (var i in appdomain.CrossBindingAdaptors)
                    {
                        if (i.Key.IsGenericType && !i.Key.IsGenericTypeDefinition)
                        {
                            var gd = i.Key.GetGenericTypeDefinition();
                            if (gd == elementType.TypeForCLR)
                            {
                                var ga = i.Key.GetGenericArguments();
                                bool match = true;
                                foreach (var j in spIdx)
                                {
                                    if (ga[j] != i.Value.AdaptorType)
                                    {
                                        match = false;
                                        break;
                                    }
                                }
                                if (match)
                                {
                                    baseType = i.Value;
                                    break;
                                }
                            }
                        }
                    }
                    if (baseType == null)
                    {
                        throw new TypeLoadException("Cannot find Adaptor for:" + definition.BaseType.FullName);
                    }
                }
                else
                {
                    baseType = appdomain.GetType(definition.BaseType, this, null);
                    if (baseType is CLRType)
                    {
                        if (baseType.TypeForCLR == typeof(Enum) || baseType.TypeForCLR == typeof(object) || baseType.TypeForCLR == typeof(ValueType) || baseType.TypeForCLR == typeof(System.Enum))
                        {//都是这样，无所谓
                            baseType = null;
                        }
                        else if (baseType.TypeForCLR == typeof(MulticastDelegate))
                        {
                            baseType = null;
                            isDelegate = true;
                        }
                        else
                        {
                            CrossBindingAdaptor adaptor;
                            if (appdomain.CrossBindingAdaptors.TryGetValue(baseType.TypeForCLR, out adaptor))
                            {
                                baseType = adaptor;
                            }
                            else
                                throw new TypeLoadException("Cannot find Adaptor for:" + baseType.TypeForCLR.ToString());
                            //继承了其他系统类型
                            //env.logger.Log_Error("ScriptType:" + Name + " Based On a SystemType:" + BaseType.Name);
                            //HasSysBase = true;
                            //throw new Exception("不得继承系统类型，脚本类型系统和脚本类型系统是隔离的");
                        }
                    }
                }
            }
            var curBase = baseType;
            while (curBase is ILType)
            {
                curBase = curBase.BaseType;
            }
            firstCLRBaseType = curBase;
            baseTypeInitialized = true;
        }

        public IMethod GetMethod(string name)
        {
            if (methods == null)
                InitializeMethods();
            List<ILMethod> lst;
            if (methods.TryGetValue(name, out lst))
            {
                return lst[0];
            }
            return null;
        }

        public IMethod GetMethod(string name, int paramCount)
        {
            if (methods == null)
                InitializeMethods();
            List<ILMethod> lst;
            if (methods.TryGetValue(name, out lst))
            {
                foreach (var i in lst)
                {
                    if (i.ParameterCount == paramCount)
                        return i;
                }
            }
            return null;
        }

        void InitializeMethods()
        {
            methods = new Dictionary<string, List<ILMethod>>();
            constructors = new List<ILMethod>();
            foreach (var i in definition.Methods)
            {
                if (i.IsConstructor)
                {
                    if (i.IsStatic)
                        staticConstructor = new ILMethod(i, this, appdomain);
                    else
                        constructors.Add(new ILMethod(i, this, appdomain));
                }
                else
                {
                    List<ILMethod> lst;
                    if (!methods.TryGetValue(i.Name, out lst))
                    {
                        lst = new List<ILMethod>();
                        methods[i.Name] = lst;
                    }
                    var m = new ILMethod(i, this, appdomain);
                    lst.Add(new ILMethod(i, this, appdomain));
                }
            }

            if (staticConstructor != null && (!TypeReference.HasGenericParameters || IsGenericInstance))
            {
                appdomain.Invoke(staticConstructor, null, null);
            }
        }

        public IMethod GetVirtualMethod(IMethod method)
        {
            IType[] genericArguments = null;
            if (method.IsGenericInstance)
            {
                if (method is ILMethod)
                {
                    genericArguments = ((ILMethod)method).GenericArugmentsArray;
                }
                else
                {
                    genericArguments = ((CLRMethod)method).GenericArguments;
                }
            }

            var m = GetMethod(method.Name, method.Parameters, genericArguments, method.ReturnType);
            if (m == null)
            {
                if (BaseType != null)
                {
                    return BaseType.GetVirtualMethod(method);
                }
                else
                    return null;//BaseType == null means base type is Object or Enum
            }
            else if (m.IsGenericInstance == method.IsGenericInstance)
                return m;
            else
                return method;
        }

        public IMethod GetMethod(string name, List<IType> param, IType[] genericArguments, IType returnType = null)
        {
            if (methods == null)
                InitializeMethods();
            List<ILMethod> lst;
            IMethod genericMethod = null;
            if (methods.TryGetValue(name, out lst))
            {
                for (var idx = 0; idx < lst.Count; idx++)
                {
                    var i = lst[idx];
                    int pCnt = param != null ? param.Count : 0;
                    if (i.ParameterCount == pCnt)
                    {
                        bool match = true;
                        if (genericArguments != null && i.GenericParameterCount == genericArguments.Length)
                        {
                            genericMethod = CheckGenericParams(i, param, ref match);
                        }
                        else
                        {
                            match = CheckGenericArguments(i, genericArguments);
                            if (!match)
                                continue;
                            for (int j = 0; j < pCnt; j++)
                            {
                                if (param[j] != i.Parameters[j])
                                {
                                    match = false;
                                    break;
                                }
                            }
                            if (match)
                            {
                                match = returnType == null || i.ReturnType == returnType;
                            }
                            if (match)
                                return i;
                        }
                    }
                }
            }
            if (genericArguments != null && genericMethod != null)
            {
                var m = genericMethod.MakeGenericMethod(genericArguments);
                lst.Add((ILMethod)m);
                return m;
            }
            return null;
        }

        bool CheckGenericArguments(ILMethod i, IType[] genericArguments)
        {
            if (genericArguments == null)
            {
                return i.GenericArguments == null;
            }
            else
            {
                if (i.GenericArguments == null)
                    return false;
                else if (i.GenericArguments.Length != genericArguments.Length)
                    return false;
                if (i.GenericArguments.Length == genericArguments.Length)
                {
                    for (int j = 0; j < genericArguments.Length; j++)
                    {
                        if (i.GenericArguments[j].Value != genericArguments[j])
                            return false;
                    }
                    return true;
                }
                else
                    return false;
            }
        }

        ILMethod CheckGenericParams(ILMethod i, List<IType> param, ref bool match)
        {
            ILMethod genericMethod = null;
            if (param != null)
            {
                for (int j = 0; j < param.Count; j++)
                {
                    var p = i.Parameters[j];
                    if (p.HasGenericParameter)
                    {
                        //TODO should match the generic parameters;
                        continue;
                    }

                    if (param[j] != p)
                    {
                        match = false;
                        break;
                    }
                }
            }
            if (match)
            {
                genericMethod = i;
            }
            return genericMethod;
        }

        public List<ILMethod> GetConstructors()
        {
            if (constructors == null)
                InitializeMethods();
            return constructors;
        }

        public IMethod GetStaticConstroctor()
        {
            if (constructors == null)
                InitializeMethods();
            return staticConstructor;
        }

        public IMethod GetConstructor(int paramCnt)
        {
            if (constructors == null)
                InitializeMethods();
            foreach (var i in constructors)
            {
                if (i.ParameterCount == paramCnt)
                {
                    return i;
                }
            }
            return null;
        }

        public IMethod GetConstructor(List<IType> param)
        {
            if (constructors == null)
                InitializeMethods();
            foreach (var i in constructors)
            {
                if (i.ParameterCount == param.Count)
                {
                    bool match = true;

                    for (int j = 0; j < param.Count; j++)
                    {
                        if (param[j] != i.Parameters[j])
                        {
                            match = false;
                            break;
                        }
                    }

                    if (match)
                        return i;
                }
            }
            return null;
        }

        public int GetFieldIndex(object token)
        {
            if (fieldMapping == null)
                InitializeFields();
            int idx;
            int hashCode = token.GetHashCode();
            if (fieldTokenMapping.TryGetValue(hashCode, out idx))
                return idx;
            FieldReference f = token as FieldReference;
            if (staticFieldMapping != null && staticFieldMapping.TryGetValue(f.Name, out idx))
            {
                fieldTokenMapping[hashCode] = idx;
                return idx;
            }
            if (fieldMapping.TryGetValue(f.Name, out idx))
            {
                fieldTokenMapping[hashCode] = idx;
                return idx;
            }

            return -1;
        }

        public IType GetField(string name, out int fieldIdx)
        {
            if (fieldMapping == null)
                InitializeFields();
            if (fieldMapping.TryGetValue(name, out fieldIdx))
            {
                return fieldTypes[fieldIdx - FieldStartIndex];
            }
            else if (BaseType != null && BaseType is ILType)
            {
                return ((ILType)BaseType).GetField(name, out fieldIdx);
            }
            else
                return null;
        }

        public IType GetField(int fieldIdx, out FieldDefinition fd)
        {
            if (fieldMapping == null)
                InitializeFields();
            if (fieldIdx < FieldStartIndex)
                return ((ILType)BaseType).GetField(fieldIdx, out fd);
            else
            {
                fd = fieldDefinitions[fieldIdx - FieldStartIndex];
                return fieldTypes[fieldIdx - FieldStartIndex];
            }
        }

        void InitializeFields()
        {
            fieldMapping = new Dictionary<string, int>();
            fieldTypes = new IType[definition.Fields.Count];
            fieldDefinitions = new FieldDefinition[definition.Fields.Count];
            var fields = definition.Fields;
            int idx = FieldStartIndex;
            int idxStatic = 0;
            for (int i = 0; i < fields.Count; i++)
            {
                var field = fields[i];
                if (field.IsStatic)
                {
                    //It makes no sence to initialize
                    if (!TypeReference.HasGenericParameters || IsGenericInstance)
                    {
                        if (staticFieldTypes == null)
                        {
                            staticFieldTypes = new IType[definition.Fields.Count];
                            staticFieldDefinitions = new FieldDefinition[definition.Fields.Count];
                            staticFieldMapping = new Dictionary<string, int>();
                        }
                        staticFieldMapping[field.Name] = idxStatic;
                        staticFieldDefinitions[idxStatic] = field;
                        if (field.FieldType.IsGenericParameter)
                        {
                            staticFieldTypes[idxStatic] = FindGenericArgument(field.FieldType.Name);
                        }
                        else
                            staticFieldTypes[idxStatic] = appdomain.GetType(field.FieldType, this, null);
                        idxStatic++;
                    }
                }
                else
                {
                    fieldMapping[field.Name] = idx;
                    fieldDefinitions[idx - FieldStartIndex] = field;
                    if (field.FieldType.IsGenericParameter)
                    {
                        fieldTypes[idx - FieldStartIndex] = FindGenericArgument(field.FieldType.Name);
                    }
                    else
                        fieldTypes[idx - FieldStartIndex] = appdomain.GetType(field.FieldType, this, null);
                    if (IsEnum)
                    {
                        enumType = fieldTypes[idx - FieldStartIndex];
                    }
                    idx++;
                }
            }
            Array.Resize(ref fieldTypes, idx - FieldStartIndex);
            Array.Resize(ref fieldDefinitions, idx - FieldStartIndex);

            if (staticFieldTypes != null)
            {
                Array.Resize(ref staticFieldTypes, idxStatic);
                Array.Resize(ref staticFieldDefinitions, idxStatic);
                staticInstance = new ILTypeStaticInstance(this);
            }
        }

        public IType FindGenericArgument(string key)
        {
            if (genericArguments != null)
            {
                foreach (var i in genericArguments)
                {
                    if (i.Key == key)
                        return i.Value;
                }
            }
            return null;
        }

        public bool CanAssignTo(IType type)
        {
            bool res = false;
            if (this == type)
            {
                return true;
            }

            if (BaseType != null)
            {
                res = BaseType.CanAssignTo(type);

                if (res) return true;
            }

            if (Implements != null)
            {
                for (int i = 0; i < interfaces.Length; i++)
                {
                    var im = interfaces[i];
                    res = im.CanAssignTo(type);
                    if (res)
                        return true;
                }
            }
            return res;
        }

        public ILTypeInstance Instantiate(bool callDefaultConstructor = true)
        {
            var res = new ILTypeInstance(this);
            if (callDefaultConstructor)
            {
                var m = GetConstructor(CLR.Utils.Extensions.EmptyParamList);
                if (m != null)
                {
                    appdomain.Invoke(m, res, null);
                }
            }
            return res;
        }
        public IType MakeGenericInstance(KeyValuePair<string, IType>[] genericArguments)
        {
            if (genericInstances == null)
                genericInstances = new List<ILType>();
            foreach (var i in genericInstances)
            {
                bool match = true;
                for (int j = 0; j < genericArguments.Length; j++)
                {
                    if (i.genericArguments[j].Value != genericArguments[j].Value)
                    {
                        match = false;
                        break;
                    }
                }
                if (match)
                    return i;
            }
            var res = new ILType(definition, appdomain);
            res.genericArguments = genericArguments;

            genericInstances.Add(res);
            return res;
        }

        public IType MakeByRefType()
        {
            if (byRefType == null)
            {
                var def = new ByReferenceType(typeRef);
                byRefType = new ILType(def, appdomain);
                ((ILType)byRefType).elementType = this;
                ((ILType)byRefType).byRefCLRType = this.TypeForCLR.MakeByRefType();
            }
            return byRefType;
        }

        public IType MakeArrayType()
        {
            if (arrayType == null)
            {
                var def = new ArrayType(typeRef);
                arrayType = new ILType(def, appdomain);
                ((ILType)arrayType).IsArray = true;
                ((ILType)arrayType).elementType = this;
                ((ILType)arrayType).arrayCLRType = this.TypeForCLR.MakeArrayType();
            }
            return arrayType;
        }

        public IType ResolveGenericType(IType contextType)
        {
            var ga = contextType.GenericArguments;
            IType[] kv = new IType[definition.GenericParameters.Count];
            for (int i = 0; i < kv.Length; i++)
            {
                var gp = definition.GenericParameters[i];
                string name = gp.Name;
                foreach (var j in ga)
                {
                    if (j.Key == name)
                    {
                        kv[i] = j.Value;
                        break;
                    }
                }
            }

            foreach (var i in genericInstances)
            {
                bool match = true;
                for (int j = 0; j < kv.Length; j++)
                {
                    if (i.genericArguments[j].Value != kv[j])
                    {
                        match = false;
                        break;
                    }
                }
                if (match)
                    return i;
            }

            return null;
        }

        public override int GetHashCode()
        {
            if (hashCode == -1)
                hashCode = System.Threading.Interlocked.Add(ref instance_id, 1);
            return hashCode;
        }
    }
}
