#if ENABLE_LUA_INJECTION
using System;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Unity.CecilTools;
using System.Linq;

[Flags]
public enum InjectFilter
{
    IgnoreConstructor = 1,
    IgnoreProperty = 1 << 1,
    IgnoreGeneric = 1 << 2,
    IgnoreNoToLuaAttr = 1 << 3,
}

public static class ToLuaInjectionHelper
{
    public static string GetArrayRank(TypeReference t)
    {
        ArrayType type = t as ArrayType;
        int count = type.Rank;

        if (count == 1)
        {
            return "[]";
        }

        using (CString.Block())
        {
            CString sb = CString.Alloc(64);
            sb.Append('[');

            for (int i = 1; i < count; i++)
            {
                sb.Append(',');
            }

            sb.Append(']');
            return sb.ToString();
        }
    }

    public static string GetTypeName(TypeReference t, bool bFull = false)
    {
        if (t.IsArray)
        {
            string str = GetTypeName(ElementType.For(t));
            str += GetArrayRank(t);
            return str;
        }
        else if (t.IsByReference)
        {
            //t = t.GetElementType();
            return GetTypeName(ElementType.For(t)) + "&";
        }
        else if (t.IsGenericInstance)
        {
            return GetGenericName(t, bFull);
        }
        else if (t.MetadataType == MetadataType.Void)
        {
            return "void";
        }
        else
        {
            string name = GetPrimitiveTypeStr(t, bFull);
            return name.Replace('+', '.');
        }
    }

    public static string[] GetGenericName(Mono.Collections.Generic.Collection<TypeReference> types, int offset, int count, bool bFull)
    {
        string[] results = new string[count];

        for (int i = 0; i < count; i++)
        {
            int pos = i + offset;

            if (types[pos].IsGenericInstance)
            {
                results[i] = GetGenericName(types[pos], bFull);
            }
            else
            {
                results[i] = GetTypeName(types[pos]);
            }
        }

        return results;
    }

    public static MethodReference GetBaseMethodInstance(this MethodDefinition target)
    {
        MethodDefinition baseMethodDef = null;
        var baseType = target.DeclaringType.BaseType;

        while (baseType != null)
        {
            if (baseType.MetadataToken.TokenType == TokenType.TypeRef)
            {
                break;
            }

            var baseTypeDef = baseType.Resolve();
            baseMethodDef = baseTypeDef.Methods.FirstOrDefault(method =>
            {
                return method.Name == target.Name
                    && target.Parameters
                        .Select(param => param.ParameterType.FullName)
                        .SequenceEqual(method.Parameters.Select(param => param.ParameterType.FullName))
                    && method.ReturnType.FullName == target.ReturnType.FullName;
            });

            if (baseMethodDef != null && !baseMethodDef.IsAbstract)
            {
                if (baseType.IsGenericInstance)
                {
                    MethodReference baseMethodRef = baseTypeDef.Module.Import(baseMethodDef);
                    var baseTypeInstance = (GenericInstanceType)baseType;
                    return baseMethodRef.MakeGenericMethod(baseTypeInstance.GenericArguments.ToArray());
                }
                break;
            }
            else baseMethodDef = null;

           baseType = baseTypeDef.BaseType;
        }

        return baseMethodDef;
    }

    public static bool IsGenericTypeDefinition(this TypeReference type)
    {
        if (type.HasGenericParameters)
        {
            return true;
        }
        else if (type.IsByReference || type.IsArray)
        {
            return ElementType.For(type).IsGenericTypeDefinition();
        }
        else if (type.IsNested)
        {
            var parent = type.DeclaringType;
            while (parent != null)
            {
                if (parent.IsGenericTypeDefinition())
                {
                    return true;
                }

                if (parent.IsNested)
                {
                    parent = parent.DeclaringType;
                }
                else
                {
                    break;
                }
            }
        }

        return type.IsGenericParameter;
    }

    public static bool IsGenericMethodDefinition(this MethodDefinition md)
    {
        if (md.HasGenericParameters
#if UNITY_5_2 || UNITY_5_3 || UNITY_5_3_OR_NEWER
            || md.ContainsGenericParameter
#endif
            )
        {
            return true;
        }

        if (md.DeclaringType != null && md.DeclaringType.IsGenericTypeDefinition())
        {
            return true;
        }

        if (md.ReturnType.IsGenericTypeDefinition())
        {
            return true;
        }

        foreach (var param in md.Parameters)
        {
            if (param.ParameterType.IsGenericTypeDefinition())
            {
                return true;
            }
        }

        return false;
    }

    public static bool GotPassedByReferenceParam(this MethodReference md)
    {
        return md.Parameters.Any(param => param.ParameterType.IsByReference);
    }

    public static TypeReference MakeGenericType(this TypeReference self, params TypeReference[] arguments)
    {
        if (self.GenericParameters.Count != arguments.Length)
        {
            throw new ArgumentException();
        }

        var instance = new GenericInstanceType(self);
        foreach (var argument in arguments)
        {
            instance.GenericArguments.Add(argument);
        }

        return instance;
    }

    public static MethodReference MakeGenericMethod(this MethodReference self, params TypeReference[] arguments)
    {
        if (self.DeclaringType.IsGenericTypeDefinition())
        {
            return self.CloneMethod(self.DeclaringType.MakeGenericType(arguments));
        }
        else
        {
            var genericInstanceMethod = new GenericInstanceMethod(self.CloneMethod());

            foreach (var argument in arguments)
            {
                genericInstanceMethod.GenericArguments.Add(argument);
            }

            return genericInstanceMethod;
        }
    }

    public static MethodReference CloneMethod(this MethodReference self, TypeReference declaringType = null)
    {
        var reference = new MethodReference(self.Name, self.ReturnType, declaringType ?? self.DeclaringType)
        {
            HasThis = self.HasThis,
            ExplicitThis = self.ExplicitThis,
            CallingConvention = self.CallingConvention,
        };

        foreach (ParameterDefinition parameterDef in self.Parameters)
        {
            reference.Parameters.Add(new ParameterDefinition(parameterDef.Name, parameterDef.Attributes, parameterDef.ParameterType));
        }

        foreach (GenericParameter genParamDef in self.GenericParameters)
        {
            reference.GenericParameters.Add(new GenericParameter(genParamDef.Name, reference));
        }

        return reference;
    }

    public static bool IsEnumerator(this MethodDefinition md)
    {
        return md.ReturnType.FullName == "System.Collections.IEnumerator";
    }

    public static bool ReturnVoid(this MethodDefinition target)
    {
        return target.ReturnType.FullName == "System.Void";
    }

    public static bool HasFlag(this LuaInterface.InjectType flag, LuaInterface.InjectType destFlag)
    {
        return (flag & destFlag) == destFlag;
    }

    public static bool HasFlag(this LuaInterface.InjectType flag, byte destFlag)
    {
        return ((byte)flag & destFlag) == destFlag;
    }

    public static bool HasFlag(this InjectFilter flag, InjectFilter destFlag)
    {
        return (flag & destFlag) == destFlag;
    }

    public static string GetPrimitiveTypeStr(TypeReference t, bool bFull)
    {
        switch (t.MetadataType)
        {
            case MetadataType.Single:
                return "float";
            case MetadataType.String:
                return "string";
            case MetadataType.Int32:
                return "int";
            case MetadataType.Double:
                return "double";
            case MetadataType.Boolean:
                return "bool";
            case MetadataType.UInt32:
                return "uint";
            case MetadataType.SByte:
                return "sbyte";
            case MetadataType.Byte:
                return "byte";
            case MetadataType.Int16:
                return "short";
            case MetadataType.UInt16:
                return "ushort";
            case MetadataType.Char:
                return "char";
            case MetadataType.Int64:
                return "long";
            case MetadataType.UInt64:
                return "ulong";
            case MetadataType.Object:
                return "object";
            default:
                return bFull ? t.FullName.Replace("/", "+") : t.Name;
        }
    }

    static string CombineTypeStr(string space, string name)
    {
        if (string.IsNullOrEmpty(space))
        {
            return name;
        }
        else
        {
            return space + "." + name;
        }
    }

    static string GetGenericName(TypeReference t, bool bFull)
    {
        GenericInstanceType type = t as GenericInstanceType;
        var gArgs = type.GenericArguments;

        string typeName = bFull ? t.FullName.Replace("/", "+") : t.Name;
        int count = gArgs.Count;
        int pos = typeName.IndexOf("[");

        if (pos > 0)
        {
            typeName = typeName.Substring(0, pos);
        }

        string str = null;
        string name = null;
        int offset = 0;
        pos = typeName.IndexOf("+");

        while (pos > 0)
        {
            str = typeName.Substring(0, pos);
            typeName = typeName.Substring(pos + 1);
            pos = str.IndexOf('`');

            if (pos > 0)
            {
                count = (int)(str[pos + 1] - '0');
                str = str.Substring(0, pos);
                str += "<" + string.Join(",", GetGenericName(gArgs, offset, count, bFull)) + ">";
                offset += count;
            }

            name = CombineTypeStr(name, str);
            pos = typeName.IndexOf("+");
        }

        str = typeName;

        if (offset < gArgs.Count)
        {
            pos = str.IndexOf('`');
            count = (int)(str[pos + 1] - '0');
            str = str.Substring(0, pos);
            str += "<" + string.Join(",", GetGenericName(gArgs, offset, count, bFull)) + ">";
        }

        return CombineTypeStr(name, str);
    }

    public static void Foreach<T>(this IEnumerable<T> source, Action<T> callback)
    {
        foreach (var val in source)
        {
            callback(val);
        }
    }
}
#endif