/*
可遵循 aardio 用户协议与 aardio 开源许可证在 aardio 程序中自由使用本组件以及本组件源码,
禁止在非 aardio 开发的程序中引用本组件的任何部份(包含但不限于本组件源码、使用此源码生成的 DLL )
*/
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;

namespace Aardio.InteropServices
{
    //*注意 COM 类里传来的 .Net 对象也会变成 System.__ComObject,非 COM 类才会还原 .Net 对象*/
    [ClassInterface(ClassInterfaceType.AutoDispatch), ComVisible(true)]
    public class Utility
    {
        public CCodeCompiler CreateCompiler(string provideType)
        {
            object obj = this.loadAssembly("System").CreateInstance(provideType);
            if (obj == null)
            {
                return null;
            }
            return new CCodeCompiler(obj as CodeDomProvider);
        }

        public object InvokeMember(object assemblyName, string typeName, string methodName, int invokeAttr, object args, object target)
        {
            try
            {
                Assembly assembly = assemblyName as Assembly;
                if (assembly == null)
                {
                    assembly = this.loadAssembly(assemblyName as string);
                }
                if (assembly != null)
                {

                    Type tAny = assembly.GetType(typeName);
                    if (((BindingFlags)invokeAttr & BindingFlags.CreateInstance) == BindingFlags.CreateInstance)
                    {

                        if (tAny != null && tAny.IsClass)
                        {
                            return CreateInstanceByClassType(tAny, (args as ArrayList), target);
                        }

                    }

                    if (((BindingFlags)invokeAttr & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
                    {
                        if (tAny == null)
                        {
                            tAny = assembly.GetType(typeName + "." + methodName);
                            if (tAny != null && tAny.IsClass && ((invokeAttr & (16 | 8 | 256)) == (16 | 8 | 256)))
                            {
                                return CreateInstanceByClassType(tAny, (args as ArrayList), target);
                            }
                        }

                        if (tAny == null) throw new MissingMethodException();
                    }

                    return InvokeMemberByType(tAny, methodName, invokeAttr, args, target);
                }

            }
            catch (TargetInvocationException targetEx)
            {
                if (targetEx.InnerException != null)
                {
                    throw targetEx.InnerException;
                }
            }

            return null;

        }

        public object InvokeObjectMember(object target, string methodName, int invokeAttr, object args)
        {
            if (target == null)
            {
                return null;
            }

            try
            {
                return InvokeMemberByType(target.GetType(), methodName, invokeAttr, args, target);
            }
            catch (TargetInvocationException targetEx)
            {
                if (targetEx.InnerException != null)
                {
                    throw targetEx.InnerException;
                }
            }

            return null;
        }


        public object InvokeMemberByType(Type tAny, string methodName, int invokeAttr, object args, object target)
        {
            ArrayList argArray = (args as ArrayList);

            if (((BindingFlags)invokeAttr & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod)
            {
                Type[] argTypeArray = new Type[argArray.Count];
                for (int i = 0; i < argArray.Count; i++)
                {
                    argTypeArray[i] = argArray[i] != null ? argArray[i].GetType() : null;
                }


                MethodInfo m = null;
                try
                {
                    m = tAny.GetMethod(methodName, (BindingFlags)invokeAttr | BindingFlags.IgnoreReturn, null, argTypeArray, null);
                }
                catch (SystemException)
                {

                }

                if (m != null)
                {
                    return m.Invoke(target, (args as ArrayList).ToArray());
                }

                MethodInfo[] ms = tAny.GetMethods((BindingFlags)invokeAttr | BindingFlags.IgnoreReturn);

                bool failed = true;
                object ret = InvokeMemberBaseMethodInfo(methodName, ms, argArray, ref failed, target, invokeAttr);
                if (!failed)
                {
                    if (ret != null && ret.GetType() == ColorType) return ((System.Drawing.Color)ret).ToArgb();
                    return ret;
                }
            }
            
            if (((BindingFlags)invokeAttr & BindingFlags.SetProperty) == BindingFlags.SetProperty)
            {
                PropertyInfo prop = tAny.GetProperty(methodName, (BindingFlags)invokeAttr);
                if (prop != null)
                {
                    invokeAttr = invokeAttr & ~(int)BindingFlags.SetField;

                    if (argArray.Count > 0 && argArray[0] != null)
                    {

                        MethodInfo ms = prop.GetSetMethod();
                        var parameters = ms.GetParameters();
                        if (parameters.Length > 0)
                        {
                            var paramType = parameters[0].ParameterType;
                            Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                            if (nullableUnderlyingType != null)
                            {
                                paramType = nullableUnderlyingType;
                            }
                            //string a = paramType.Name;

                            var argType = argArray[0].GetType();
                            var argTypeCode = Type.GetTypeCode(argType);

                            Type enumType = null;
                            if (paramType.IsEnum)
                            {
                                enumType = paramType;
                                paramType = Enum.GetUnderlyingType(enumType);
                            }

                            if (!paramType.Equals(argType))
                            {
                                if (typeof(Delegate).IsAssignableFrom(paramType) && argType.IsCOMObject )
                                {
                                    var dd = new DispatchDelegate(argArray[0]);
                                    argArray[0] = (object)dd.CreateDelegate(paramType);
                                }
                                else if (argTypeCode == TypeCode.Double || argTypeCode == TypeCode.Int32)
                                {

                                    if (IsNumericType(paramType))
                                    {
                                        argArray[0] = Convert.ChangeType(argArray[0], paramType);
                                    }
                                    else if (paramType == ColorType)
                                    {
                                        argArray[0] = ConvertNumToColor(argArray[0], argTypeCode);
                                    }
                                }
                                else if (paramType.IsArray && argType.IsArray)
                                {
                                    var paramEleType = paramType.GetElementType();
                                    var argEleType = argType.GetElementType();
                                    var argEleTypeCode = Type.GetTypeCode(argEleType);

                                    if ((argEleTypeCode == TypeCode.Double) && IsNumericType(paramEleType))
                                    {

                                        var srcArr = (argArray[0] as double[]);
                                        var dstArr = Array.CreateInstance(paramEleType, srcArr.Length);
                                        for (int n = 0; n < srcArr.Length; n++)
                                        {
                                            dstArr.SetValue(Convert.ChangeType(srcArr[n], paramEleType), n);
                                        }

                                        argArray[0] = dstArr;
                                    }

                                }

                            }

                            if (enumType != null) argArray[0] = Enum.ToObject(enumType, argArray[0]);
                        }

                    }
                }
            }



            if (((BindingFlags)invokeAttr & BindingFlags.SetField) == BindingFlags.SetField)
            {
                FieldInfo field = tAny.GetField(methodName, (BindingFlags)invokeAttr);
                if (field != null)
                {
                    invokeAttr = invokeAttr & ~(int)BindingFlags.SetProperty;

                    if (argArray.Count > 0 && argArray[0] != null)
                    {
                        var paramType = field.FieldType;
                        Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                        if (nullableUnderlyingType != null)
                        {
                            paramType = nullableUnderlyingType;
                        }
                        //string a = paramType.Name;

                        var argType = argArray[0].GetType();
                        var argTypeCode = Type.GetTypeCode(argType);

                        Type enumType = null;
                        if (paramType.IsEnum)
                        {
                            enumType = paramType;
                            paramType = Enum.GetUnderlyingType(enumType);
                        }

                        if (!paramType.Equals(argType))
                        {

                            if (typeof(Delegate).IsAssignableFrom(paramType) && argType.IsCOMObject)
                            {
                                var dd = new DispatchDelegate(argArray[0]);
                                argArray[0] = (object)dd.CreateDelegate(paramType);
                            }
                            else if ((argTypeCode == TypeCode.Double) || (argTypeCode == TypeCode.Int32))
                            {

                                if (IsNumericType(paramType))
                                {
                                    argArray[0] = Convert.ChangeType(argArray[0], paramType);
                                }
                                else if (paramType == ColorType)
                                {
                                    argArray[0] = ConvertNumToColor(argArray[0], argTypeCode);
                                }
                            }
                            else if (paramType.IsArray && argType.IsArray)
                            {
                                var paramEleType = paramType.GetElementType();
                                var argEleType = argType.GetElementType();
                                var argEleTypeCode = Type.GetTypeCode(argEleType);

                                if ((argEleTypeCode == TypeCode.Double) && IsNumericType(paramEleType))
                                {

                                    var srcArr = (argArray[0] as double[]);
                                    var dstArr = Array.CreateInstance(paramEleType, srcArr.Length);
                                    for (int n = 0; n < srcArr.Length; n++)
                                    {
                                        dstArr.SetValue(Convert.ChangeType(srcArr[n], paramEleType), n);
                                    }

                                    argArray[0] = dstArr;
                                }

                            }

                        }

                        if (enumType != null) argArray[0] = Enum.ToObject(enumType, argArray[0]);
                    }
                }
            }

          
            object ret2 = tAny.InvokeMember(methodName, (BindingFlags)invokeAttr | BindingFlags.IgnoreReturn, null, target, (args as ArrayList).ToArray());
            if (ret2 != null && ret2.GetType() == ColorType) return ((System.Drawing.Color)ret2).ToArgb();
            return ret2;
        }

        private object CreateInstanceByClassType(Type tClass, ArrayList argArray, object target)
        {
            Type[] argTypeArray = new Type[argArray.Count];
            for (int i = 0; i < argArray.Count; i++)
            {
                argTypeArray[i] = argArray[i] != null ? argArray[i].GetType() : null;
            }

            ConstructorInfo m = null;
            try
            {
                m = tClass.GetConstructor(argTypeArray);
            }
            catch (SystemException)
            {

            }

            if (m != null)
            {
                return m.Invoke(argArray.ToArray());
            }


            ConstructorInfo[] ms = tClass.GetConstructors();

            bool failed = true;
            object ret = InvokeMemberBaseMethodInfo(".ctor", ms, argArray, ref failed, target, (int)BindingFlags.CreateInstance);
            if (!failed)
            {
                return ret;
            }

            return tClass.InvokeMember("", (BindingFlags)BindingFlags.CreateInstance | BindingFlags.IgnoreReturn, null, target, argArray.ToArray());
        }

        private static bool IsNumericType(Type t)
        {
            switch (Type.GetTypeCode(t))
            {
                case TypeCode.Byte:
                case TypeCode.SByte:
                case TypeCode.UInt16:
                case TypeCode.UInt32:
                case TypeCode.UInt64:
                case TypeCode.Int16:
                case TypeCode.Int32:
                case TypeCode.Int64:
                case TypeCode.Decimal:
                case TypeCode.Double:
                case TypeCode.Single:
                    return true;
                default:
                    return false;
            }
        }

        static Type ColorType = typeof(System.Drawing.Color);
        static System.Drawing.Color ConvertNumToColor(object arg, TypeCode argTypeCode)
        {
            byte[] bytes;
            if (argTypeCode == TypeCode.Double) bytes = BitConverter.GetBytes((uint)Convert.ChangeType(arg, typeof(uint)));
            else bytes = BitConverter.GetBytes((int)arg);

            return System.Drawing.Color.FromArgb(bytes[3], bytes[2], bytes[1], bytes[0]);
        }

        private object InvokeMemberBaseMethodInfo(string methodName, MethodBase[] ms, ArrayList argArray, ref bool failed, object target, int invokeAttr)
        {

            for (int i = 0; i < ms.Length; i++)
            {
                if ((ms[i].Name != methodName) || ms[i].IsGenericMethod) continue;
                var parameters = ms[i].GetParameters();

                if (parameters.Length == argArray.Count)
                {
                    failed = false;
                    for (int k = 0; k < parameters.Length; k++)
                    {
                        var paramType = parameters[k].ParameterType;
                        Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                        if (argArray[k] == null)
                        {
                            if (!paramType.IsValueType || nullableUnderlyingType != null)
                                continue;

                            failed = true;
                            break;
                        }

                        if (nullableUnderlyingType != null)
                        {
                            paramType = nullableUnderlyingType;
                        }

                        var argType = argArray[k].GetType();
                        if (paramType.Equals(argType)) continue;

                        Type enumType = null;
                        if (paramType.IsEnum)
                        {
                            enumType = paramType;
                            paramType = Enum.GetUnderlyingType(enumType);
                        }

                        //string a = paramType.Name;
                        if (!paramType.Equals(argType))
                        {
                            if (typeof(Delegate).IsAssignableFrom(paramType) && (argType == null) || argType.IsCOMObject)
                            {
                                if (argType != null)
                                {
                                    var dd = new DispatchDelegate(argArray[k]);
                                    argArray[k] = (object)dd.CreateDelegate(paramType);
                                }
                                
                            }
                            else
                            {
                                failed = true;
                                break;
                            }
                        }
                        else if (enumType != null) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                    }

                    if (!failed)
                    {
                        if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(argArray.ToArray());
                        return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, argArray.ToArray(), null);
                    }
                }
            }

            for (int i = 0; i < ms.Length; i++)
            {
                if ((ms[i].Name != methodName) || ms[i].IsGenericMethod) continue;

                var parameters = ms[i].GetParameters();
                if (parameters.Length > argArray.Count)
                {
                    failed = false;
                    for (int k = 0; k < argArray.Count; k++)
                    {
                        var paramType = parameters[k].ParameterType;
                        Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                        if (argArray[k] == null)
                        {
                            if (!paramType.IsValueType || nullableUnderlyingType != null)
                                continue;

                            failed = true;
                            break;
                        }

                        if (nullableUnderlyingType != null)
                        {
                            paramType = nullableUnderlyingType;
                        }

                        var argType = argArray[k].GetType();
                        if (paramType.Equals(argType)) continue;

                        Type enumType = null;
                        if (paramType.IsEnum)
                        {
                            enumType = paramType;
                            paramType = Enum.GetUnderlyingType(enumType);
                        }

                        if (!paramType.Equals(argType))
                        {
                            if (typeof(Delegate).IsAssignableFrom(paramType) && (argType == null) || argType.IsCOMObject)
                            {
                                if (argType != null)
                                {
                                    var dd = new DispatchDelegate(argArray[k]);
                                    argArray[k] = (object)dd.CreateDelegate(paramType);
                                }  
                            }
                            else
                            {
                                failed = true;
                                break;
                            }
                        }
                        else if (enumType != null && !(argType.IsEnum)) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                    }

                    ArrayList args2 = argArray.Clone() as ArrayList;
                    for (int k = argArray.Count; k < parameters.Length; k++)
                    {
                        if (!parameters[k].IsOptional)
                        {
                            failed = true;
                            break;
                        }

                        args2.Add(parameters[k].DefaultValue);
                    }

                    if (!failed)
                    {
                        if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(args2.ToArray());
                        return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, (args2 as ArrayList).ToArray(), null);
                    }
                }
            }

            for (int i = 0; i < ms.Length; i++)
            {
                if ((ms[i].Name != methodName) || ms[i].IsGenericMethod) continue;

                var parameters = ms[i].GetParameters();
                if (parameters.Length == argArray.Count)
                {
                    failed = false;
                    for (int k = 0; k < parameters.Length; k++)
                    {
                        var paramType = parameters[k].ParameterType;
                        Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                        if (argArray[k] == null)
                        {
                            if (!paramType.IsValueType || nullableUnderlyingType != null)
                                continue;

                            failed = true;
                            break;
                        }

                        if (nullableUnderlyingType != null)
                        {
                            paramType = nullableUnderlyingType;
                        }

                        var argType = argArray[k].GetType();
                        if (paramType.Equals(argType)) continue;

                        Type enumType = null;
                        if (paramType.IsEnum)
                        {
                            enumType = paramType;
                            paramType = Enum.GetUnderlyingType(enumType);
                        }

                        //string a = paramType.Name;
                        if (!paramType.Equals(argType))
                        {
                            if (typeof(Delegate).IsAssignableFrom(paramType) && (argType == null) || argType.IsCOMObject)
                            {
                                if (argType != null)
                                {
                                    var dd = new DispatchDelegate(argArray[k]);
                                    argArray[k] = (object)dd.CreateDelegate(paramType);
                                }
                                continue;
                            }

                            var paramTypeCode = Type.GetTypeCode(paramType);
                            var argTypeCode = Type.GetTypeCode(argType);

                            if (argTypeCode == TypeCode.Double || argTypeCode == TypeCode.Int32)
                            {

                                if (IsNumericType(paramType))
                                {
                                    argArray[k] = Convert.ChangeType(argArray[k], paramType);
                                    if (enumType != null && !(argType.IsEnum)) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                                    continue;
                                }
                                else if (paramType == ColorType)
                                {
                                    argArray[k] = ConvertNumToColor(argArray[k], argTypeCode);
                                    if (enumType != null && !(argType.IsEnum)) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                                    continue;
                                }
                            }
                            else if (paramType.IsArray && argType.IsArray)
                            {
                                var paramEleType = paramType.GetElementType();
                                var argEleType = argType.GetElementType();
                                var argEleTypeCode = Type.GetTypeCode(argEleType);

                                if ((argEleTypeCode == TypeCode.Double) && IsNumericType(paramEleType))
                                {

                                    var srcArr = (argArray[k] as double[]);
                                    var dstArr = Array.CreateInstance(paramEleType, srcArr.Length);
                                    for (int n = 0; n < srcArr.Length; n++)
                                    {
                                        dstArr.SetValue(Convert.ChangeType(srcArr[n], paramEleType), n);
                                    }

                                    argArray[k] = dstArr;
                                    continue;
                                }

                            }

                            failed = true;
                            break;
                        }

                        if (enumType != null && !(argType.IsEnum)) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                    }

                    if (!failed)
                    {
                        if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(argArray.ToArray());
                        return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, argArray.ToArray(), null);
                    }
                }
            }

            for (int i = 0; i < ms.Length; i++)
            {
                if ((ms[i].Name != methodName) || ms[i].IsGenericMethod) continue;

                var parameters = ms[i].GetParameters();
                if (parameters.Length > argArray.Count)
                {
                    failed = false;
                    for (int k = 0; k < argArray.Count; k++)
                    {
                        var paramType = parameters[k].ParameterType;
                        Type nullableUnderlyingType = Nullable.GetUnderlyingType(paramType);
                        if (argArray[k] == null)
                        {
                            if (!paramType.IsValueType || nullableUnderlyingType != null)
                                continue;

                            failed = true;
                            break;
                        }

                        if (nullableUnderlyingType != null)
                        {
                            paramType = nullableUnderlyingType;
                        }

                        var argType = argArray[k].GetType();
                        if (paramType.Equals(argType)) continue;

                        Type enumType = null;
                        if (paramType.IsEnum)
                        {
                            enumType = paramType;
                            paramType = Enum.GetUnderlyingType(enumType);
                        }

                        if (!paramType.Equals(argType))
                        {
                            if (typeof(Delegate).IsAssignableFrom(paramType) && (argType == null) || argType.IsCOMObject)
                            {
                                if (argType != null)
                                {
                                    var dd = new DispatchDelegate(argArray[k]);
                                    argArray[k] = (object)dd.CreateDelegate(paramType);
                                }
                            }
                            else
                            {
                                failed = true;
                                break;
                            }
                        }
                        else if (enumType != null && !(argType.IsEnum)) argArray[k] = Enum.ToObject(enumType, argArray[k]);
                    }

                    ArrayList args2 = argArray.Clone() as ArrayList;
                    for (int k = argArray.Count; k < parameters.Length; k++)
                    {
                        if (!parameters[k].IsOptional)
                        {
                            failed = true;
                            break;
                        }

                        args2.Add(parameters[k].DefaultValue);
                    }

                    if (!failed)
                    {
                        if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(args2.ToArray());
                        return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, (args2 as ArrayList).ToArray(), null);
                    }
                }
            }


            for (int i = 0; i < ms.Length; i++)
            {
                if (ms[i].Name == methodName && ms[i].GetParameters().Length == argArray.Count)
                {
                    if (!ms[i].IsGenericMethod)
                    {
                        failed = false;
                        if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(argArray.ToArray());
                        return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, argArray.ToArray(), null);
                    }
                }
            }

            for (int i = 0; i < ms.Length; i++)
            {
                if (ms[i].Name == methodName && ms[i].GetParameters().Length == argArray.Count)
                {
                    failed = false;
                    if (methodName == ".ctor") return (ms[i] as ConstructorInfo).Invoke(argArray.ToArray());
                    return ms[i].Invoke(target, (BindingFlags)invokeAttr, null, argArray.ToArray(), null);

                }
            }

            failed = true;
            return null;
        }

        public Object InvokeEnumValue(object enumType, string methodName)
        {
            return System.Enum.Parse(enumType as Type, methodName);
        }

  

        public Object InvokeEnumType(object assemblyName, string nameSpace, string enumTypeName)
        {
            Assembly assembly = assemblyName as Assembly;
            if (assembly == null)
            {
                assembly = this.loadAssembly(assemblyName as string);
            }
            if (assembly != null)
            {
                string fullTypeName = null;
                if (nameSpace != null)
                {
                    try
                    {
                        Type nsType = assembly.GetType(nameSpace);
                        if (nsType.IsClass)
                        {
                            fullTypeName = nameSpace + "+" + enumTypeName;
                        }
                        else
                        {
                            fullTypeName = nameSpace + "." + enumTypeName;
                        }
                    }
                    catch (SystemException)
                    {
                        fullTypeName = nameSpace + "." + enumTypeName;
                    }
                }

                Type t = assembly.GetType(fullTypeName);
                if (t.IsEnum)
                {
                    return t as object;
                }
            }
            return null;
        }


        public Object ParseEnum(object assemblyName, string nameSpace, string enumTypeName, string methodName)
        {
            Assembly assembly = assemblyName as Assembly;
            if (assembly == null)
            {
                assembly = this.loadAssembly(assemblyName as string);
            }
            if (assembly != null)
            {
                string fullTypeName = null;
                if (nameSpace != null)
                {
                    try
                    {
                        Type nsType = assembly.GetType(nameSpace);
                        if (nsType.IsClass)
                        {
                            fullTypeName = nameSpace + "+" + enumTypeName;
                        }
                        else
                        {
                            fullTypeName = nameSpace + "." + enumTypeName;
                        }
                    }
                    catch (SystemException)
                    {
                        fullTypeName = nameSpace + "." + enumTypeName;
                    }
                }

                Type t = assembly.GetType(fullTypeName);
                if (t.IsEnum)
                {
                    return System.Enum.Parse(t, methodName);
                }
            }
            return null;
        }

        public Assembly loadAssembly(string assemblyName)
        {
            try
            {
                Assembly assembly = Assembly.LoadWithPartialName(assemblyName);
                Assembly result = assembly;
                return result;
            }
            catch (SystemException)
            {
            }
            try
            {
                Assembly assembly2 = Assembly.Load(AssemblyName.GetAssemblyName(assemblyName));
                Assembly result = assembly2;
                return result;
            }
            catch (SystemException)
            {
            }
            try
            {
                Assembly assembly3 = Assembly.LoadFrom(assemblyName);
                Assembly result = assembly3;
                return result;
            }
            catch (SystemException)
            {
            }
            try
            {
                Assembly assembly4 = Assembly.LoadFile(assemblyName);
                Assembly result = assembly4;
                return result;
            }
            catch (SystemException)
            {
            }
            return null;
        }
    }
}
