using dnlib.DotNet;
using HybridCLR.Editor.ABI;
using HybridCLR.Editor.Meta;
using HybridCLR.Editor.ReversePInvokeWrap;
using HybridCLR.Editor.Template;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using TypeInfo = HybridCLR.Editor.ABI.TypeInfo;
using CallingConvention = System.Runtime.InteropServices.CallingConvention;
using TypeAttributes = dnlib.DotNet.TypeAttributes;
using System.Runtime.InteropServices;

namespace HybridCLR.Editor.MethodBridge
{
    public class Generator
    {
        public class Options
        {
            public string TemplateCode { get; set; }

            public string OutputFile { get; set; }

            public IReadOnlyCollection<GenericMethod> GenericMethods { get; set; }

            public List<RawReversePInvokeMethodInfo> ReversePInvokeMethods { get; set; }

            public IReadOnlyCollection<RawCalliMethodSignatureInfo> CalliMethodSignatures { get; set; }

            public bool Development { get; set; }
        }

        private class ABIReversePInvokeMethodInfo
        {
            public MethodDesc Method { get; set; }

            public CallingConvention Callvention { get; set; }

            public int Count { get; set; }

            public string Signature { get; set; }
        }

        private class CalliMethodInfo
        {
            public MethodDesc Method { get; set; }

            public CallingConvention Callvention { get; set; }

            public string Signature { get; set; }
        }

        private readonly List<GenericMethod> _genericMethods;

        private readonly List<RawReversePInvokeMethodInfo> _originalReversePInvokeMethods;

        private readonly List<RawCalliMethodSignatureInfo> _originalCalliMethodSignatures;

        private readonly string _templateCode;

        private readonly string _outputFile;

        private readonly bool _development;

        private readonly TypeCreator _typeCreator;

        private readonly HashSet<MethodDesc> _managed2nativeMethodSet = new HashSet<MethodDesc>();

        private readonly HashSet<MethodDesc> _native2managedMethodSet = new HashSet<MethodDesc>();

        private readonly HashSet<MethodDesc> _adjustThunkMethodSet = new HashSet<MethodDesc>();

        private List<ABIReversePInvokeMethodInfo> _reversePInvokeMethods;

        private List<CalliMethodInfo> _callidMethods;

        public Generator(Options options)
        {
            List<(GenericMethod, string)> genericMethodInfo = options.GenericMethods.Select(m => (m, m.ToString())).ToList();
            genericMethodInfo.Sort((a, b) => string.CompareOrdinal(a.Item2, b.Item2));
            _genericMethods = genericMethodInfo.Select(m => m.Item1).ToList();
            _originalReversePInvokeMethods = options.ReversePInvokeMethods;
            _originalCalliMethodSignatures = options.CalliMethodSignatures.ToList();

            _templateCode = options.TemplateCode;
            _outputFile = options.OutputFile;
            _typeCreator = new TypeCreator();
            _development = options.Development;
        }

        private readonly Dictionary<string, TypeInfo> _sig2Types = new Dictionary<string, TypeInfo>();

        private TypeInfo GetSharedTypeInfo(TypeSig type)
        {
            var typeInfo = _typeCreator.CreateTypeInfo(type);
            if (!typeInfo.IsStruct)
            {
                return typeInfo;
            }
            string sigName = ToFullName(typeInfo.Klass);
            if (!_sig2Types.TryGetValue(sigName, out var sharedTypeInfo))
            {
                sharedTypeInfo = typeInfo;
                _sig2Types.Add(sigName, sharedTypeInfo);
            }
            return sharedTypeInfo;
        }

        private MethodDesc CreateMethodDesc(MethodDef methodDef, bool forceRemoveThis, TypeSig returnType, List<TypeSig> parameters)
        {
            var paramInfos = new List<ParamInfo>();
            if (forceRemoveThis && !methodDef.IsStatic)
            {
                parameters.RemoveAt(0);
            }
            if (returnType.ContainsGenericParameter)
            {
                throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters");
            }
            foreach (var paramInfo in parameters)
            {
                if (paramInfo.ContainsGenericParameter)
                {
                    throw new Exception($"[PreservedMethod] method:{methodDef} has generic parameters");
                }
                paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) });
            }
            var mbs = new MethodDesc()
            {
                MethodDef = methodDef,
                ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void },
                ParamInfos = paramInfos,
            };
            return mbs;
        }

        private MethodDesc CreateMethodDesc(TypeSig returnType, List<TypeSig> parameters)
        {
            var paramInfos = new List<ParamInfo>();
            if (returnType.ContainsGenericParameter)
            {
                throw new Exception($"[PreservedMethod] method has generic parameters");
            }
            foreach (var paramInfo in parameters)
            {
                if (paramInfo.ContainsGenericParameter)
                {
                    throw new Exception($"[PreservedMethod] method has generic parameters");
                }
                paramInfos.Add(new ParamInfo() { Type = GetSharedTypeInfo(paramInfo) });
            }
            var mbs = new MethodDesc()
            {
                MethodDef = null,
                ReturnInfo = new ReturnInfo() { Type = returnType != null ? GetSharedTypeInfo(returnType) : TypeInfo.s_void },
                ParamInfos = paramInfos,
            };
            return mbs;
        }

        private void AddManaged2NativeMethod(MethodDesc method)
        {
            method.Init();
            _managed2nativeMethodSet.Add(method);
        }

        private void AddNative2ManagedMethod(MethodDesc method)
        {
            method.Init();
            _native2managedMethodSet.Add(method);
        }

        private void AddAdjustThunkMethod(MethodDesc method)
        {
            method.Init();
            _adjustThunkMethodSet.Add(method);
        }

        private void ProcessMethod(MethodDef method, List<TypeSig> klassInst, List<TypeSig> methodInst)
        {
            if (method.IsPrivate || (method.IsAssembly && !method.IsPublic && !method.IsFamily))
            {
                if (klassInst == null && methodInst == null)
                {
                    return;
                }
                else
                {
                    //Debug.Log($"[PreservedMethod] method:{method}");
                }
            }
            ICorLibTypes corLibTypes = method.Module.CorLibTypes;
            TypeSig returnType;
            List<TypeSig> parameters;
            if (klassInst == null && methodInst == null)
            {
                if (method.HasGenericParameters)
                {
                    throw new Exception($"[PreservedMethod] method:{method} has generic parameters");
                }
                returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType);
                parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList();
            }
            else
            {
                var gc = new GenericArgumentContext(klassInst, methodInst);
                returnType = MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(method.ReturnType, gc));
                parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, MetaUtil.Inflate(p.Type, gc))).ToList();
            }

            var m2nMethod = CreateMethodDesc(method, false, returnType, parameters);
            AddManaged2NativeMethod(m2nMethod);

            if (method.IsVirtual)
            {
                if (method.DeclaringType.IsInterface)
                {
                    AddAdjustThunkMethod(m2nMethod);
                }
                //var adjustThunkMethod = CreateMethodDesc(method, true, returnType, parameters);
                AddNative2ManagedMethod(m2nMethod);
            }
            if (method.Name == "Invoke" && method.DeclaringType.IsDelegate)
            {
                var openMethod = CreateMethodDesc(method, true, returnType, parameters);
                AddNative2ManagedMethod(openMethod);
            }
        }

        private void PrepareMethodBridges()
        {
            foreach (var method in _genericMethods)
            {
                ProcessMethod(method.Method, method.KlassInst, method.MethodInst);
            }
            foreach (var reversePInvokeMethod in _originalReversePInvokeMethods)
            {
                MethodDef method = reversePInvokeMethod.Method;
                ICorLibTypes corLibTypes = method.Module.CorLibTypes;

                var returnType = MetaUtil.ToShareTypeSig(corLibTypes, method.ReturnType);
                var parameters = method.Parameters.Select(p => MetaUtil.ToShareTypeSig(corLibTypes, p.Type)).ToList();
                var sharedMethod = CreateMethodDesc(method, true, returnType, parameters);
                sharedMethod.Init();
                AddNative2ManagedMethod(sharedMethod);
            }
        }

        static void CheckUnique(IEnumerable<string> names)
        {
            var set = new HashSet<string>();
            foreach (var name in names)
            {
                if (!set.Add(name))
                {
                    throw new Exception($"[CheckUnique] duplicate name:{name}");
                }
            }
        }


        private List<MethodDesc> _managed2NativeMethodList0;
        private List<MethodDesc> _native2ManagedMethodList0;
        private List<MethodDesc> _adjustThunkMethodList0;

        private List<TypeInfo> _structTypes0;

        private void CollectTypesAndMethods()
        {
            _managed2NativeMethodList0 = _managed2nativeMethodSet.ToList();
            _managed2NativeMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig));

            _native2ManagedMethodList0 = _native2managedMethodSet.ToList();
            _native2ManagedMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig));

            _adjustThunkMethodList0 = _adjustThunkMethodSet.ToList();
            _adjustThunkMethodList0.Sort((a, b) => string.CompareOrdinal(a.Sig, b.Sig));


            var structTypeSet = new HashSet<TypeInfo>();
            CollectStructDefs(_managed2NativeMethodList0, structTypeSet);
            CollectStructDefs(_native2ManagedMethodList0, structTypeSet);
            CollectStructDefs(_adjustThunkMethodList0, structTypeSet);
            _structTypes0 = structTypeSet.ToList();
            _structTypes0.Sort((a, b) => a.TypeId - b.TypeId);

            CheckUnique(_structTypes0.Select(t => ToFullName(t.Klass)));
            CheckUnique(_structTypes0.Select(t => t.CreateSigName()));

            Debug.LogFormat("== before optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}",
                _managed2NativeMethodList0.Count, _native2ManagedMethodList0.Count, _adjustThunkMethodList0.Count, _structTypes0.Count);
        }

        private class AnalyzeFieldInfo
        {
            public FieldDef field;

            public TypeInfo type;
        }

        private class AnalyzeTypeInfo
        {
            public TypeInfo isoType;
            public List<AnalyzeFieldInfo> fields;
            public string signature;
            public uint originalPackingSize;
            public uint packingSize;
            public uint classSize;
            public LayoutKind layout;
            public bool blittable;
        }

        private readonly Dictionary<TypeInfo, AnalyzeTypeInfo> _analyzeTypeInfos = new Dictionary<TypeInfo, AnalyzeTypeInfo>();

        private readonly Dictionary<string, TypeInfo> _signature2Type = new Dictionary<string, TypeInfo>();



        private bool IsBlittable(TypeSig typeSig)
        {
            typeSig = typeSig.RemovePinnedAndModifiers();
            if (typeSig.IsByRef)
            {
                return true;
            }
            switch (typeSig.ElementType)
            {
                case ElementType.Void: return false;
                case ElementType.Boolean:
                case ElementType.I1:
                case ElementType.U1:
                case ElementType.I2:
                case ElementType.Char:
                case ElementType.U2:
                case ElementType.I4:
                case ElementType.U4:
                case ElementType.I8:
                case ElementType.U8:
                case ElementType.R4:
                case ElementType.R8:
                case ElementType.I:
                case ElementType.U:
                case ElementType.Ptr:
                case ElementType.ByRef:
                case ElementType.FnPtr:
                case ElementType.TypedByRef: return true;
                case ElementType.String:
                case ElementType.Class:
                case ElementType.Array:
                case ElementType.SZArray:
                case ElementType.Object:
                case ElementType.Module:
                case ElementType.Var:
                case ElementType.MVar: return false;
                case ElementType.ValueType:
                {
                    TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDef();
                    if (typeDef == null)
                    {
                        throw new Exception($"type:{typeSig} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function");
                    }
                    if (typeDef.IsEnum)
                    {
                        return true;
                    }
                    return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable;
                }
                case ElementType.GenericInst:
                {
                    GenericInstSig gis = (GenericInstSig)typeSig;
                    if (!gis.GenericType.IsValueType)
                    {
                        return false;
                    }
                    TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef();
                    if (typeDef.IsEnum)
                    {
                        return true;
                    }
                    return CalculateAnalyzeTypeInfoBasic(GetSharedTypeInfo(typeSig)).blittable;
                }
                default: throw new NotSupportedException($"{typeSig.ElementType}");
            }
        }

        private AnalyzeTypeInfo CalculateAnalyzeTypeInfoBasic(TypeInfo typeInfo)
        {
            Debug.Assert(typeInfo.IsStruct);
            if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati))
            {
                return ati;
            }
            TypeSig type = typeInfo.Klass;
            TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDefThrow();

            List<TypeSig> klassInst = type.ToGenericInstSig()?.GenericArguments?.ToList();
            GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;

            var fields = new List<AnalyzeFieldInfo>();

            bool blittable = true;
            foreach (FieldDef field in typeDef.Fields)
            {
                if (field.IsStatic)
                {
                    continue;
                }
                TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
                blittable &= IsBlittable(fieldType);
                TypeInfo sharedFieldTypeInfo = GetSharedTypeInfo(fieldType);
                TypeInfo isoType =  ToIsomorphicType(sharedFieldTypeInfo);
                fields.Add(new AnalyzeFieldInfo { field = field, type = isoType });
            }
            //analyzeTypeInfo.blittable = blittable;
            //analyzeTypeInfo.packingSize = blittable ? analyzeTypeInfo.originalPackingSize : 0;

            ClassLayout sa = typeDef.ClassLayout;
            uint originalPackingSize = sa?.PackingSize ?? 0;
            var analyzeTypeInfo = new AnalyzeTypeInfo()
            {
                originalPackingSize = originalPackingSize,
                packingSize = blittable && !typeDef.IsAutoLayout ? originalPackingSize : 0,
                classSize = sa?.ClassSize ?? 0,
                layout = typeDef.IsAutoLayout ? LayoutKind.Auto : (typeDef.IsExplicitLayout ? LayoutKind.Explicit : LayoutKind.Sequential),
                fields = fields,
                blittable = blittable,
            };
            _analyzeTypeInfos.Add(typeInfo, analyzeTypeInfo);
            analyzeTypeInfo.signature = GetOrCalculateTypeInfoSignature(typeInfo);

            if (_signature2Type.TryGetValue(analyzeTypeInfo.signature, out var sharedType))
            {
                // Debug.Log($"[ToIsomorphicType] type:{type.Klass} ==> sharedType:{sharedType.Klass} signature:{signature} ");
                analyzeTypeInfo.isoType = sharedType;
            }
            else
            {
                analyzeTypeInfo.isoType = typeInfo;
                _signature2Type.Add(analyzeTypeInfo.signature, typeInfo);
            }

            return analyzeTypeInfo;
        }

        private string GetOrCalculateTypeInfoSignature(TypeInfo typeInfo)
        {
            if (!typeInfo.IsStruct)
            {
                return typeInfo.CreateSigName();
            }

            var ati = _analyzeTypeInfos[typeInfo];

            //if (_analyzeTypeInfos.TryGetValue(typeInfo, out var ati))
            //{
            //    return ati.signature;
            //}
            //ati = CalculateAnalyzeTypeInfoBasic(typeInfo);
            //_analyzeTypeInfos.Add(typeInfo, ati);
            if (ati.signature != null)
            {
                return ati.signature;
            }

            var sigBuf = new StringBuilder();
            if (ati.packingSize != 0 || ati.classSize != 0 || ati.layout != LayoutKind.Sequential || !ati.blittable)
            {
                sigBuf.Append($"[{ati.classSize}|{ati.packingSize}|{ati.layout}|{(ati.blittable ? 0 : 1)}]");
            }
            foreach (var field in ati.fields)
            {
                string fieldOffset = field.field.FieldOffset != null ? field.field.FieldOffset.ToString() + "|" : "";
                sigBuf.Append("{" + fieldOffset + GetOrCalculateTypeInfoSignature(ToIsomorphicType(field.type)) + "}");
            }
            return ati.signature = sigBuf.ToString();
        }

        private TypeInfo ToIsomorphicType(TypeInfo type)
        {
            if (!type.IsStruct)
            {
                return type;
            }
            return CalculateAnalyzeTypeInfoBasic(type).isoType;
        }

        private MethodDesc ToIsomorphicMethod(MethodDesc method)
        {
            var paramInfos = new List<ParamInfo>();
            foreach (var paramInfo in method.ParamInfos)
            {
                paramInfos.Add(new ParamInfo() { Type = ToIsomorphicType(paramInfo.Type) });
            }
            var mbs = new MethodDesc()
            {
                MethodDef = method.MethodDef,
                ReturnInfo = new ReturnInfo() { Type = ToIsomorphicType(method.ReturnInfo.Type) },
                ParamInfos = paramInfos,
            };
            mbs.Init();
            return mbs;
        }

        private List<MethodDesc> _managed2NativeMethodList;
        private List<MethodDesc> _native2ManagedMethodList;
        private List<MethodDesc> _adjustThunkMethodList;

        private List<TypeInfo> structTypes;

        private void BuildAnalyzeTypeInfos()
        {
            foreach (var type in _structTypes0)
            {
                ToIsomorphicType(type);
            }
            structTypes = _signature2Type.Values.ToList();
            structTypes.Sort((a, b) => a.TypeId - b.TypeId);
        }

        private List<MethodDesc> ToUniqueOrderedList(List<MethodDesc> methods)
        {
            var methodMap = new SortedDictionary<string, MethodDesc>();
            foreach (var method in methods)
            {
                var sharedMethod = ToIsomorphicMethod(method);
                var sig = sharedMethod.Sig;
                if (!methodMap.TryGetValue(sig, out var _))
                {
                    methodMap.Add(sig, sharedMethod);
                }
            }
            return methodMap.Values.ToList();
        }



        private static string MakeReversePInvokeSignature(MethodDesc desc, CallingConvention CallingConventionention)
        {
            string convStr = ((char)('A' + (int)CallingConventionention - 1)).ToString();
            return $"{convStr}{desc.Sig}";
        }
        private static string MakeCalliSignature(MethodDesc desc, CallingConvention CallingConventionention)
        {
            string convStr = ((char)('A' + Math.Max((int)CallingConventionention - 1, 0))).ToString();
            return $"{convStr}{desc.Sig}";
        }

        private static CallingConvention GetCallingConvention(MethodDef method)
        {
            var monoPInvokeCallbackAttr = method.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.Name == "MonoPInvokeCallbackAttribute");
            if (monoPInvokeCallbackAttr == null)
            {
                return CallingConvention.Winapi;
            }
            object delegateTypeSig = monoPInvokeCallbackAttr.ConstructorArguments[0].Value;

            TypeDef delegateTypeDef;
            if (delegateTypeSig is ClassSig classSig)
            {
                delegateTypeDef = classSig.TypeDefOrRef.ResolveTypeDefThrow();
            }
            else if (delegateTypeSig is GenericInstSig genericInstSig)
            {
                delegateTypeDef = genericInstSig.GenericType.TypeDefOrRef.ResolveTypeDefThrow();
            }
            else
            {
                delegateTypeDef = null;
            }

            if (delegateTypeDef == null)
            {
                throw new NotSupportedException($"Unsupported delegate type: {delegateTypeSig}");
            }
            var attr = delegateTypeDef.CustomAttributes.FirstOrDefault(ca => ca.AttributeType.FullName == "System.Runtime.InteropServices.UnmanagedFunctionPointerAttribute");
            if (attr == null)
            {
                return CallingConvention.Winapi;
            }
            var conv = attr.ConstructorArguments[0].Value;
            return (CallingConvention)conv;
        }

        private List<ABIReversePInvokeMethodInfo> BuildABIMethods(List<RawReversePInvokeMethodInfo> rawMethods)
        {
            var methodsBySig = new Dictionary<string, ABIReversePInvokeMethodInfo>();
            foreach (var method in rawMethods)
            {
                var sharedMethod = new MethodDesc
                {
                    MethodDef = method.Method,
                    ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.Method.ReturnType) },
                    ParamInfos = method.Method.Parameters.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p.Type) }).ToList(),
                };
                sharedMethod.Init();
                sharedMethod = ToIsomorphicMethod(sharedMethod);

                CallingConvention callingConv = GetCallingConvention(method.Method);
                string signature = MakeReversePInvokeSignature(sharedMethod, callingConv);

                if (!methodsBySig.TryGetValue(signature, out var arm))
                {
                    arm = new ABIReversePInvokeMethodInfo()
                    {
                        Method = sharedMethod,
                        Signature = signature,
                        Count = 0,
                        Callvention = callingConv,
                    };
                    methodsBySig.Add(signature, arm);
                }
                int preserveCount = method.GenerationAttribute != null ? (int)method.GenerationAttribute.ConstructorArguments[0].Value : 1;
                arm.Count += preserveCount;
            }
            var newMethods = methodsBySig.Values.ToList();
            newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature));
            return newMethods;
        }

        private List<CalliMethodInfo> BuildCalliMethods(List<RawCalliMethodSignatureInfo> rawMethods)
        {
            var methodsBySig = new Dictionary<string, CalliMethodInfo>();
            foreach (var method in rawMethods)
            {
                var sharedMethod = new MethodDesc
                {
                    MethodDef = null,
                    ReturnInfo = new ReturnInfo { Type = GetSharedTypeInfo(method.MethodSig.RetType) },
                    ParamInfos = method.MethodSig.Params.Select(p => new ParamInfo { Type = GetSharedTypeInfo(p) }).ToList(),
                };
                sharedMethod.Init();
                sharedMethod = ToIsomorphicMethod(sharedMethod);

                CallingConvention callingConv = (CallingConvention)((int)method.MethodSig.CallingConvention + 1);
                string signature = MakeCalliSignature(sharedMethod, callingConv);

                if (!methodsBySig.TryGetValue(signature, out var arm))
                {
                    arm = new CalliMethodInfo()
                    {
                        Method = sharedMethod,
                        Signature = signature,
                        Callvention = callingConv,
                    };
                    methodsBySig.Add(signature, arm);
                }
            }
            var newMethods = methodsBySig.Values.ToList();
            newMethods.Sort((a, b) => string.CompareOrdinal(a.Signature, b.Signature));
            return newMethods;
        }

        private void BuildOptimizedMethods()
        {
            _managed2NativeMethodList = ToUniqueOrderedList(_managed2NativeMethodList0);
            _native2ManagedMethodList = ToUniqueOrderedList(_native2ManagedMethodList0);
            _adjustThunkMethodList = ToUniqueOrderedList(_adjustThunkMethodList0);
            _reversePInvokeMethods = BuildABIMethods(_originalReversePInvokeMethods);
            _callidMethods = BuildCalliMethods(_originalCalliMethodSignatures);
        }

        private void OptimizationTypesAndMethods()
        {
            BuildAnalyzeTypeInfos();
            BuildOptimizedMethods();
            Debug.LogFormat("== after optimization struct:{3} managed2native:{0} native2managed:{1} adjustThunk:{2}",
                               _managed2NativeMethodList.Count, _native2ManagedMethodList.Count, _adjustThunkMethodList.Count, structTypes.Count);
        }

        private void GenerateCode()
        {
            var frr = new FileRegionReplace(_templateCode);

            List<string> lines = new List<string>(20_0000)
            {
                "\n",
                $"// DEVELOPMENT={(_development ? 1 : 0)}",
                "\n"
            };

            var classInfos = new List<ClassInfo>();
            var classTypeSet = new Dictionary<TypeInfo, ClassInfo>();
            foreach (var type in structTypes)
            {
                GenerateClassInfo(type, classTypeSet, classInfos);
            }

            GenerateStructDefines(classInfos, lines);

            // use structTypes0 to generate signature
            GenerateStructureSignatureStub(_structTypes0, lines);

            foreach (var method in _managed2NativeMethodList)
            {
                GenerateManaged2NativeMethod(method, lines);
            }

            GenerateManaged2NativeStub(_managed2NativeMethodList, lines);

            foreach (var method in _native2ManagedMethodList)
            {
                GenerateNative2ManagedMethod(method, lines);
            }

            GenerateNative2ManagedStub(_native2ManagedMethodList, lines);

            foreach (var method in _adjustThunkMethodList)
            {
                GenerateAdjustThunkMethod(method, lines);
            }

            GenerateAdjustThunkStub(_adjustThunkMethodList, lines);

            GenerateReversePInvokeWrappers(_reversePInvokeMethods, lines);

            foreach (var method in _callidMethods)
            {
                GenerateManaged2NativeFunctionPointerMethod(method, lines);
            }
            GenerateManaged2NativeFunctionPointerMethodStub(_callidMethods, lines);


            frr.Replace("CODE", string.Join("\n", lines));

            Directory.CreateDirectory(Path.GetDirectoryName(_outputFile));

            frr.Commit(_outputFile);
        }

        private static string GetIl2cppCallConventionName(CallingConvention conv)
        {
            switch (conv)
            {
                case 0:
                case CallingConvention.Winapi:
                    return "DEFAULT_CALL";
                case CallingConvention.Cdecl:
                    return "CDECL";
                case CallingConvention.StdCall:
                    return "STDCALL";
                case CallingConvention.ThisCall:
                    return "THISCALL";
                case CallingConvention.FastCall:
                    return "FASTCALL";
                default:
                    throw new NotSupportedException($"Unsupported CallingConvention {conv}");
            }
        }

        private void GenerateReversePInvokeWrappers(List<ABIReversePInvokeMethodInfo> methods, List<string> lines)
        {
            int methodIndex = 0;
            var stubCodes = new List<string>();
            foreach (var methodInfo in methods)
            {
                MethodDesc method = methodInfo.Method;
                string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention);
                string paramDeclaringListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}"));
                string paramNameListWithoutMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"__arg{p.Index}").Concat(new string[] { "method" }));
                string paramTypeListWithMethodInfoStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()}").Concat(new string[] { "const MethodInfo*" }));
                string methodTypeDef = $"typedef {method.ReturnInfo.Type.GetTypeName()} (*Callback)({paramTypeListWithMethodInfoStr})";
                for (int i = 0; i < methodInfo.Count; i++, methodIndex++)
                {
                    lines.Add($@"
{method.ReturnInfo.Type.GetTypeName()} {il2cppCallConventionName} __ReversePInvokeMethod_{methodIndex}({paramDeclaringListWithoutMethodInfoStr})
{{
    il2cpp::vm::ScopedThreadAttacher _vmThreadHelper;
    const MethodInfo* method = InterpreterModule::GetMethodInfoByReversePInvokeWrapperIndex({methodIndex});
    {methodTypeDef};
    {(method.ReturnInfo.IsVoid ? "" : "return ")}((Callback)(method->methodPointerCallByInterp))({paramNameListWithoutMethodInfoStr});
}}
        ");
                    stubCodes.Add($"\t{{\"{methodInfo.Signature}\", (Il2CppMethodPointer)__ReversePInvokeMethod_{methodIndex}}},");
                }
                Debug.Log($"[ReversePInvokeWrap.Generator] method:{method.MethodDef} wrapperCount:{methodInfo.Count}");
            }

            lines.Add(@"
const ReversePInvokeMethodData hybridclr::interpreter::g_reversePInvokeMethodStub[]
{
");
            lines.AddRange(stubCodes);

            lines.Add(@"
    {nullptr, nullptr},
};
");
        }

        public void Generate()
        {
            PrepareMethodBridges();
            CollectTypesAndMethods();
            OptimizationTypesAndMethods();
            GenerateCode();
        }

        private void CollectStructDefs(List<MethodDesc> methods, HashSet<TypeInfo> structTypes)
        {
            foreach (var method in methods)
            {
                foreach(var paramInfo in method.ParamInfos)
                {
                    if (paramInfo.Type.IsStruct)
                    {
                        structTypes.Add(paramInfo.Type);
                        if (paramInfo.Type.Klass.ContainsGenericParameter)
                        {
                            throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{paramInfo.Type.Klass} contains generic parameter");
                        }
                    }
                    
                }
                if (method.ReturnInfo.Type.IsStruct)
                {
                    structTypes.Add(method.ReturnInfo.Type);
                    if (method.ReturnInfo.Type.Klass.ContainsGenericParameter)
                    {
                        throw new Exception($"[CollectStructDefs] method:{method.MethodDef} type:{method.ReturnInfo.Type.Klass} contains generic parameter");
                    }
                }
            }
            
        }

        class FieldInfo
        {
            public FieldDef field;
            public TypeInfo type;
        }

        class ClassInfo
        {
            public TypeInfo type;
            public List<AnalyzeFieldInfo> fields;
            public uint packingSize;
            public uint classSize;
            public LayoutKind layout;
            public bool blittable;
        }

        private void GenerateClassInfo(TypeInfo type, Dictionary<TypeInfo, ClassInfo> typeSet, List<ClassInfo> classInfos)
        {
            if (typeSet.ContainsKey(type))
            {
                return;
            }

            AnalyzeTypeInfo ati = CalculateAnalyzeTypeInfoBasic(type);
            //TypeSig typeSig = type.Klass;
            //var fields = new List<FieldInfo>();

            //TypeDef typeDef = typeSig.ToTypeDefOrRef().ResolveTypeDefThrow();

            //List<TypeSig> klassInst = typeSig.ToGenericInstSig()?.GenericArguments?.ToList();
            //GenericArgumentContext ctx = klassInst != null ? new GenericArgumentContext(klassInst, null) : null;

            //ClassLayout sa = typeDef.ClassLayout;

            //ICorLibTypes corLibTypes = typeDef.Module.CorLibTypes;
            //bool blittable = true;
            //foreach (FieldDef field in typeDef.Fields)
            //{
            //    if (field.IsStatic)
            //    {
            //        continue;
            //    }
            //    TypeSig fieldType = ctx != null ? MetaUtil.Inflate(field.FieldType, ctx) : field.FieldType;
            //    fieldType = MetaUtil.ToShareTypeSig(corLibTypes, fieldType);
            //    var fieldTypeInfo = ToIsomorphicType(GetSharedTypeInfo(fieldType));
            //    if (fieldTypeInfo.IsStruct)
            //    {
            //        GenerateClassInfo(fieldTypeInfo, typeSet, classInfos);
            //    }
            //    blittable &= IsBlittable(fieldType, fieldTypeInfo, typeSet);
            //    fields.Add(new FieldInfo { field = field, type = fieldTypeInfo });
            //}

            foreach (var field in ati.fields)
            {
                if (field.type.IsStruct)
                {
                    GenerateClassInfo(field.type, typeSet, classInfos);
                }
            }
            var classInfo = new ClassInfo()
            {
                type = type,
                fields = ati.fields,
                packingSize = ati.packingSize,
                classSize = ati.classSize,
                layout = ati.layout,
                blittable = ati.blittable,
            };
            typeSet.Add(type, classInfo);
            classInfos.Add(classInfo);
        }

        private void GenerateStructDefines(List<ClassInfo> classInfos, List<string> lines)
        {
            foreach (var ci in classInfos)
            {
                lines.Add($"// {ci.type.Klass}");
                uint packingSize = ci.packingSize;
                uint classSize = ci.classSize;
               
                if (ci.layout == LayoutKind.Explicit)
                {
                    lines.Add($"struct {ci.type.GetTypeName()} {{");
                    lines.Add("\tunion {");
                    if (classSize > 0)
                    {
                        lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};");
                    }
                    int index = 0;
                    foreach (var field in ci.fields)
                    {
                        uint offset = field.field.FieldOffset.Value;
                        string fieldName = $"__{index}";
                        string commentFieldName = $"{field.field.Name}";
                        lines.Add("\t#pragma pack(push, 1)");
                        lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName};}}; // {commentFieldName}");
                        lines.Add($"\t#pragma pack(pop)");
                        if (packingSize > 0)
                        {
                            lines.Add($"\t#pragma pack(push, {packingSize})");
                        }
                        lines.Add($"\tstruct {{ {(offset > 0 ? $"char {fieldName}_offsetPadding_forAlignmentOnly[{offset}]; " : "")}{field.type.GetTypeName()} {fieldName}_forAlignmentOnly;}}; // {commentFieldName}");
                        if (packingSize > 0)
                        {
                            lines.Add($"\t#pragma pack(pop)");
                        }
                        ++index;
                    }
                    lines.Add("\t};");
                    lines.Add("};");
                }
                else
                {
                    if (packingSize != 0)
                    {
                        lines.Add($"#pragma pack(push, {packingSize})");
                    }
                    lines.Add($"{(classSize > 0 ? "union" : "struct")} {ci.type.GetTypeName()} {{");
                    if (classSize > 0)
                    {
                        lines.Add($"\tstruct {{ char __fieldSize_offsetPadding[{classSize}];}};");
                        lines.Add("\tstruct {");
                    }
                    int index = 0;
                    foreach (var field in ci.fields)
                    {
                        string fieldName = $"__{index}";
                        string commentFieldName = $"{field.field.Name}";
                        lines.Add($"\t{field.type.GetTypeName()} {fieldName}; // {commentFieldName}");
                        ++index;
                    }
                    if (classSize > 0)
                    {
                        lines.Add("\t};");
                    }
                    lines.Add("};");
                    if (packingSize != 0)
                    {
                        lines.Add($"#pragma pack(pop)");
                    }
                }
            }
        }

        private const string SigOfObj = "u";

        private static string ToFullName(TypeSig type)
        {
            type = type.RemovePinnedAndModifiers();
            switch (type.ElementType)
            {
                case ElementType.Void: return "v";
                case ElementType.Boolean: return "u1";
                case ElementType.I1: return "i1";
                case ElementType.U1: return "u1";
                case ElementType.I2: return "i2";
                case ElementType.Char:
                case ElementType.U2: return "u2";
                case ElementType.I4: return "i4";
                case ElementType.U4: return "u4";
                case ElementType.I8: return "i8";
                case ElementType.U8: return "u8";
                case ElementType.R4: return "r4";
                case ElementType.R8: return "r8";
                case ElementType.I: return "i";
                case ElementType.U:
                case ElementType.String:
                case ElementType.Ptr:
                case ElementType.ByRef:
                case ElementType.Class:
                case ElementType.Array:
                case ElementType.SZArray:
                case ElementType.FnPtr:
                case ElementType.Object:
                    return SigOfObj;
                case ElementType.Module:
                case ElementType.Var:
                case ElementType.MVar:
                    throw new NotSupportedException($"ToFullName type:{type}");
                case ElementType.TypedByRef: return TypeInfo.strTypedByRef;
                case ElementType.ValueType:
                {
                    TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef();
                    if (typeDef == null)
                    {
                        throw new Exception($"type:{type} definition could not be found. Please try `HybridCLR/Genergate/LinkXml`, then Build once to generate the AOT dll, and then regenerate the bridge function");
                    }
                    if (typeDef.IsEnum)
                    {
                        return ToFullName(typeDef.GetEnumUnderlyingType());
                    }
                    return ToValueTypeFullName((ClassOrValueTypeSig)type);
                }
                case ElementType.GenericInst:
                    {
                        GenericInstSig gis = (GenericInstSig)type;
                        if (!gis.GenericType.IsValueType)
                        {
                            return SigOfObj;
                        }
                        TypeDef typeDef = gis.GenericType.ToTypeDefOrRef().ResolveTypeDef();
                        if (typeDef.IsEnum)
                        {
                            return ToFullName(typeDef.GetEnumUnderlyingType());
                        }
                        return $"{ToValueTypeFullName(gis.GenericType)}<{string.Join(",", gis.GenericArguments.Select(a => ToFullName(a)))}>";
                    }
                default: throw new NotSupportedException($"{type.ElementType}");
            }
        }

        private static bool IsSystemOrUnityAssembly(ModuleDef module)
        {
            if (module.IsCoreLibraryModule == true)
            {
                return true;
            }
            string assName = module.Assembly.Name.String;
            return assName.StartsWith("System.") || assName.StartsWith("UnityEngine.");
        }

        private static string ToValueTypeFullName(ClassOrValueTypeSig type)
        {
            TypeDef typeDef = type.ToTypeDefOrRef().ResolveTypeDef();
            if (typeDef == null)
            {
                throw new Exception($"type:{type} resolve fail");
            }

            if (typeDef.DeclaringType != null)
            {
                return $"{ToValueTypeFullName((ClassOrValueTypeSig)typeDef.DeclaringType.ToTypeSig())}/{typeDef.Name}";
            }

            if (IsSystemOrUnityAssembly(typeDef.Module))
            {
                return type.FullName;
            }
            return $"{Path.GetFileNameWithoutExtension(typeDef.Module.Name)}:{typeDef.FullName}";
        }

        private void GenerateStructureSignatureStub(List<TypeInfo> types, List<string> lines)
        {
            lines.Add("const FullName2Signature hybridclr::interpreter::g_fullName2SignatureStub[] = {");
            foreach (var type in types)
            {
                TypeInfo isoType = ToIsomorphicType(type);
                lines.Add($"\t{{\"{ToFullName(type.Klass)}\", \"{isoType.CreateSigName()}\"}},");
            }
            lines.Add("\t{ nullptr, nullptr},");
            lines.Add("};");
        }

        private void GenerateManaged2NativeStub(List<MethodDesc> methods, List<string> lines)
        {
            lines.Add($@"
const Managed2NativeMethodInfo hybridclr::interpreter::g_managed2nativeStub[] = 
{{
");

            foreach (var method in methods)
            {
                lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", __M2N_{method.CreateInvokeSigName()}}},");
            }

            lines.Add($"\t{{nullptr, nullptr}},");
            lines.Add("};");
        }

        private void GenerateNative2ManagedStub(List<MethodDesc> methods, List<string> lines)
        {
            lines.Add($@"
const Native2ManagedMethodInfo hybridclr::interpreter::g_native2managedStub[] = 
{{
");

            foreach (var method in methods)
            {
                lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_{method.CreateInvokeSigName()}}},");
            }

            lines.Add($"\t{{nullptr, nullptr}},");
            lines.Add("};");
        }

        private void GenerateAdjustThunkStub(List<MethodDesc> methods, List<string> lines)
        {
            lines.Add($@"
const NativeAdjustThunkMethodInfo hybridclr::interpreter::g_adjustThunkStub[] = 
{{
");

            foreach (var method in methods)
            {
                lines.Add($"\t{{\"{method.CreateInvokeSigName()}\", (Il2CppMethodPointer)__N2M_AdjustorThunk_{method.CreateCallSigName()}}},");
            }

            lines.Add($"\t{{nullptr, nullptr}},");
            lines.Add("};");
        }

        private string GetManaged2NativePassParam(TypeInfo type, string varName)
        {
            return $"M2NFromValueOrAddress<{type.GetTypeName()}>({varName})";
        }

        private string GetNative2ManagedPassParam(TypeInfo type, string varName)
        {
            return type.NeedExpandValue() ? $"(uint64_t)({varName})" : $"N2MAsUint64ValueOrAddress<{type.GetTypeName()}>({varName})";
        }

        private void GenerateManaged2NativeMethod(MethodDesc method, List<string> lines)
        {
            string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" }));
            string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")).Concat(new string[] { "method" }));

            lines.Add($@"
static void __M2N_{method.CreateCallSigName()}(const MethodInfo* method, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{{
    typedef {method.ReturnInfo.Type.GetTypeName()} (*NativeMethod)({paramListStr});
    {(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(method->methodPointerCallByInterp))({paramNameListStr});
}}
");
        }

        private string GenerateArgumentSizeAndOffset(List<ParamInfo> paramInfos)
        {
            StringBuilder s = new StringBuilder();
            int index = 0;
            foreach (var param in paramInfos)
            {
                s.AppendLine($"\tconstexpr int __ARG_OFFSET_{index}__ = {(index > 0 ? $"__ARG_OFFSET_{index - 1}__ + __ARG_SIZE_{index-1}__" : "0")};");
                s.AppendLine($"\tconstexpr int __ARG_SIZE_{index}__ = (sizeof(__arg{index}) + 7)/8;");
                index++;
            }
            s.AppendLine($"\tconstexpr int __TOTAL_ARG_SIZE__ = {(paramInfos.Count > 0 ? $"__ARG_OFFSET_{index-1}__ + __ARG_SIZE_{index-1}__" : "1")};");
            return s.ToString();
        }

        private string GenerateCopyArgumentToInterpreterStack(List<ParamInfo> paramInfos)            
        {
            StringBuilder s = new StringBuilder();
            int index = 0;
            foreach (var param in paramInfos)
            {
                if (param.Type.IsPrimitiveType)
                {
                    if (param.Type.NeedExpandValue())
                    {
                        s.AppendLine($"\targs[__ARG_OFFSET_{index}__].u64 = __arg{index};");
                    }
                    else
                    {
                        s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};");
                    }
                }
                else
                {
                    s.AppendLine($"\t*({param.Type.GetTypeName()}*)(args + __ARG_OFFSET_{index}__) = __arg{index};");
                }
                index++;
            }
            return s.ToString();
        }

        private void GenerateNative2ManagedMethod0(MethodDesc method, bool adjustorThunk, List<string> lines)
        {
            string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}").Concat(new string[] { "const MethodInfo* method" }));
            lines.Add($@"
static {method.ReturnInfo.Type.GetTypeName()} __N2M_{(adjustorThunk ? "AdjustorThunk_" : "")}{method.CreateCallSigName()}({paramListStr})
{{
    {(adjustorThunk ? "__arg0 += sizeof(Il2CppObject);" : "")}
{GenerateArgumentSizeAndOffset(method.ParamInfos)}
    StackObject args[__TOTAL_ARG_SIZE__];
{GenerateCopyArgumentToInterpreterStack(method.ParamInfos)}
    {(method.ReturnInfo.IsVoid ? "Interpreter::Execute(method, args, nullptr);" : $"{method.ReturnInfo.Type.GetTypeName()} ret; Interpreter::Execute(method, args, &ret); return ret;")}
}}
");
        }

        private void GenerateNative2ManagedMethod(MethodDesc method, List<string> lines)
        {
            GenerateNative2ManagedMethod0(method, false, lines);
        }

        private void GenerateAdjustThunkMethod(MethodDesc method, List<string> lines)
        {
            GenerateNative2ManagedMethod0(method, true, lines);
        }

        private void GenerateManaged2NativeFunctionPointerMethod(CalliMethodInfo methodInfo, List<string> lines)
        {
            MethodDesc method = methodInfo.Method;
            string paramListStr = string.Join(", ", method.ParamInfos.Select(p => $"{p.Type.GetTypeName()} __arg{p.Index}"));
            string paramNameListStr = string.Join(", ", method.ParamInfos.Select(p => GetManaged2NativePassParam(p.Type, $"localVarBase+argVarIndexs[{p.Index}]")));
            string il2cppCallConventionName = GetIl2cppCallConventionName(methodInfo.Callvention);
            lines.Add($@"
static void __M2NF_{methodInfo.Signature}(const void* methodPointer, uint16_t* argVarIndexs, StackObject* localVarBase, void* ret)
{{
    typedef {method.ReturnInfo.Type.GetTypeName()} ({il2cppCallConventionName} *NativeMethod)({paramListStr});
    {(!method.ReturnInfo.IsVoid ? $"*({method.ReturnInfo.Type.GetTypeName()}*)ret = " : "")}((NativeMethod)(methodPointer))({paramNameListStr});
}}
");
        }

        private void GenerateManaged2NativeFunctionPointerMethodStub(List<CalliMethodInfo> calliMethodSignatures, List<string> lines)
        {
            lines.Add(@"
const Managed2NativeFunctionPointerCallData hybridclr::interpreter::g_managed2NativeFunctionPointerCallStub[]
{
");
            foreach (var method in calliMethodSignatures)
            {
                lines.Add($"\t{{\"{method.Signature}\", __M2NF_{method.Signature}}},");
            }

            lines.Add(@"
    {nullptr, nullptr},
};
");
        }
    }
}
