/*
 * DelegateOps.cs --
 *
 * Copyright (c) 2007-2012 by Joe Mistachkin.  All rights reserved.
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: $
 */

using System;
using System.Reflection;
using System.Reflection.Emit;

#if NATIVE && LIBRARY
using System.Runtime.InteropServices;
#endif

using Eagle._Attributes;
using Eagle._Components.Public;
using Eagle._Containers.Public;

#if NATIVE && LIBRARY
using Eagle._Interfaces.Private;
#endif

namespace Eagle._Components.Private
{
    [ObjectId("cb1b3474-f840-4b2d-bcbc-5502f7a82232")]
    internal static class DelegateOps
    {
        #region Private Constants
#if NATIVE && LIBRARY
        internal const BindingFlags PublicInstanceBindingFlags = 
            BindingFlags.Instance | BindingFlags.Public;
#endif

        ///////////////////////////////////////////////////////////////////////

        private const TypeAttributes DefaultClassTypeAttributes =
            TypeAttributes.AnsiClass | TypeAttributes.AutoLayout | 
            TypeAttributes.NotPublic | TypeAttributes.Sealed;

        private const MethodAttributes DefaultMethodAttributes = 
            MethodAttributes.Public | MethodAttributes.HideBySig |
            MethodAttributes.Virtual | MethodAttributes.NewSlot;

        private const CallingConventions DefaultCallingConventions = 
            CallingConventions.Standard;
        
        private const MethodAttributes ConstructorMethodAttributes = 
            MethodAttributes.Public | MethodAttributes.HideBySig |
            MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;

        private const MethodImplAttributes DefaultMethodImplAttributes = 
            MethodImplAttributes.Managed | MethodImplAttributes.Runtime;

        ///////////////////////////////////////////////////////////////////////

        private const AssemblyBuilderAccess DefaultManagedAssemblyBuilderAccess =
#if NET_40
            AssemblyBuilderAccess.RunAndCollect;
#else
            AssemblyBuilderAccess.Run;
#endif

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && LIBRARY
        private const AssemblyBuilderAccess DefaultNativeAssemblyBuilderAccess =
            AssemblyBuilderAccess.Run;
#endif

        ///////////////////////////////////////////////////////////////////////

        internal const string InvokeMethodName = "Invoke";
        private const string BeginInvokeMethodName = "BeginInvoke";
        private const string EndInvokeMethodName = "EndInvoke";

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && LIBRARY
        private const string BestFitMappingFieldName = "BestFitMapping";
        private const string CharSetFieldName = "CharSet";
        private const string SetLastErrorFieldName = "SetLastError";
        private const string ThrowOnUnmappableCharFieldName = "ThrowOnUnmappableChar";
#endif
        #endregion

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && LIBRARY
        private static string MakeIDelegateName(Interpreter interpreter)
        {
            return FormatOps.Id(typeof(IDelegate).Name, null,
                GlobalState.NextId(interpreter));
        }

        ///////////////////////////////////////////////////////////////////////

        public static string MakeIModuleName(Interpreter interpreter)
        {
            return FormatOps.Id(typeof(IModule).Name, null,
                GlobalState.NextId(interpreter));
        }
#endif

        ///////////////////////////////////////////////////////////////////////

        private static AssemblyName MakeAssemblyName(Interpreter interpreter)
        {
            return new AssemblyName(FormatOps.Id(
                typeof(AssemblyName).Name, null,
                GlobalState.NextTypeId(interpreter)));
        }

        ///////////////////////////////////////////////////////////////////////

        private static string MakeModuleName(Interpreter interpreter)
        {
            return FormatOps.Id(typeof(Module).Name, null,
                GlobalState.NextTypeId(interpreter));
        }

        ///////////////////////////////////////////////////////////////////////

        private static string MakeTypeName(Interpreter interpreter)
        {
            return FormatOps.Id(typeof(Type).Name, null,
                GlobalState.NextTypeId(interpreter));
        }

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && LIBRARY
        public static ReturnCode LoadNativeModule(
            Interpreter interpreter,
            ModuleFlags flags,
            string fileName,
            string moduleName,
            ref IModule module,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                int loaded = 0;

                return NativeModule.Load(interpreter,
                    (moduleName != null) ? moduleName : MakeIModuleName(interpreter),
                    flags, fileName, ref loaded, ref module, ref error);
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }
#endif

        ///////////////////////////////////////////////////////////////////////

        public static ReturnCode CreateManagedDelegateType(
            Interpreter interpreter,
            AppDomain appDomain,
            AssemblyName assemblyName,
            string moduleName,
            string typeName,
            Type returnType,
            TypeList parameterTypes,
            ref Type type,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                type = CreateManagedDelegateType(
                    (appDomain != null) ? appDomain : interpreter.GetAppDomain(),
                    (assemblyName != null) ? assemblyName : MakeAssemblyName(interpreter),
                    (moduleName != null) ? moduleName : MakeModuleName(interpreter),
                    (typeName != null) ? typeName : MakeTypeName(interpreter),
                    returnType, parameterTypes, ref error);

                return ReturnCode.Ok;
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////

        private static Type CreateManagedDelegateType(
            AppDomain appDomain,
            AssemblyName assemblyName,
            string moduleName,
            string typeName,
            Type returnType,
            TypeList parameterTypes,
            ref Result error
            )
        {
            if (appDomain == null)
            {
                error = "invalid application domain";
                return null;
            }

            if (assemblyName == null)
            {
                error = "invalid assembly name";
                return null;
            }

            if (String.IsNullOrEmpty(moduleName))
            {
                error = "invalid module name";
                return null;
            }

            if (String.IsNullOrEmpty(typeName))
            {
                error = "invalid type name";
                return null;
            }

            Type type = null;

            try
            {
                AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(
                    assemblyName, DefaultManagedAssemblyBuilderAccess);

                ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
                    moduleName);

                TypeBuilder typeBuilder = moduleBuilder.DefineType(
                    typeName, DefaultClassTypeAttributes, typeof(MulticastDelegate));

                ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
                    ConstructorMethodAttributes, DefaultCallingConventions,
                    new Type[] { typeof(object), typeof(IntPtr) });

                constructorBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                    InvokeMethodName, DefaultMethodAttributes, DefaultCallingConventions,
                    returnType, (parameterTypes != null) ? parameterTypes.ToArray() : null);

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                TypeList beginParamterTypes = (parameterTypes != null) ?
                    new TypeList(parameterTypes) : new TypeList();

                beginParamterTypes.Add(typeof(AsyncCallback));
                beginParamterTypes.Add(typeof(object));

                methodBuilder = typeBuilder.DefineMethod(BeginInvokeMethodName,
                    DefaultMethodAttributes, DefaultCallingConventions,
                    typeof(IAsyncResult), beginParamterTypes.ToArray());

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                TypeList endParameterTypes = new TypeList();

                if (parameterTypes != null)
                    foreach (Type parameterType in parameterTypes)
                        if (parameterType.IsByRef)
                            endParameterTypes.Add(parameterType);

                endParameterTypes.Add(typeof(IAsyncResult));

                methodBuilder = typeBuilder.DefineMethod(EndInvokeMethodName,
                    DefaultMethodAttributes, DefaultCallingConventions,
                    returnType, endParameterTypes.ToArray());

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                type = typeBuilder.CreateType();
            }
            catch (Exception e)
            {
                error = e;
            }

            return type;
        }

        ///////////////////////////////////////////////////////////////////////

#if NATIVE && LIBRARY
        public static ReturnCode CreateNativeDelegateType(
            Interpreter interpreter,
            AppDomain appDomain,
            AssemblyName assemblyName,
            string moduleName,
            string typeName,
            CallingConvention callingConvention,
            bool bestFitMapping,
            CharSet charSet,
            bool setLastError,
            bool throwOnUnmappableChar,
            Type returnType,
            TypeList parameterTypes,
            string delegateName,
            IModule module,
            string functionName,
            IntPtr address,
            ref IDelegate @delegate,
            ref Result error
            )
        {
            if (interpreter != null)
            {
                Type type = CreateNativeDelegateType(
                    (appDomain != null) ? appDomain : interpreter.GetAppDomain(),
                    (assemblyName != null) ? assemblyName : MakeAssemblyName(interpreter),
                    (moduleName != null) ? moduleName : MakeModuleName(interpreter),
                    (typeName != null) ? typeName : MakeTypeName(interpreter),
                    callingConvention, bestFitMapping, charSet, setLastError,
                    throwOnUnmappableChar, returnType, parameterTypes, ref error);

                if (type != null)
                {
                    @delegate = new NativeDelegate(
                        (delegateName != null) ? delegateName : MakeIDelegateName(interpreter),
                        null, null, ClientData.Empty, interpreter, callingConvention,
                        returnType, parameterTypes, type, module, functionName, address, 0);

                    return ReturnCode.Ok;
                }
            }
            else
            {
                error = "invalid interpreter";
            }

            return ReturnCode.Error;
        }

        ///////////////////////////////////////////////////////////////////////

        private static Type CreateNativeDelegateType(
            AppDomain appDomain,
            AssemblyName assemblyName,
            string moduleName,
            string typeName,
            CallingConvention callingConvention,
            bool bestFitMapping,
            CharSet charSet,
            bool setLastError,
            bool throwOnUnmappableChar,
            Type returnType,
            TypeList parameterTypes,
            ref Result error
            )
        {
            if (appDomain == null)
            {
                error = "invalid application domain";
                return null;
            }

            if (assemblyName == null)
            {
                error = "invalid assembly name";
                return null;
            }

            if (String.IsNullOrEmpty(moduleName))
            {
                error = "invalid module name";
                return null;
            }

            if (String.IsNullOrEmpty(typeName))
            {
                error = "invalid type name";
                return null;
            }

            Type type = null;

            try
            {
                AssemblyBuilder assemblyBuilder = appDomain.DefineDynamicAssembly(
                    assemblyName, DefaultNativeAssemblyBuilderAccess);

                ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(
                    moduleName);

                TypeBuilder typeBuilder = moduleBuilder.DefineType(
                    typeName, DefaultClassTypeAttributes, typeof(MulticastDelegate));

                ConstructorBuilder constructorBuilder = typeBuilder.DefineConstructor(
                    ConstructorMethodAttributes, DefaultCallingConventions,
                    new Type[] { typeof(object), typeof(IntPtr) });

                constructorBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                    InvokeMethodName, DefaultMethodAttributes, DefaultCallingConventions,
                    returnType, (parameterTypes != null) ? parameterTypes.ToArray() : null);

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                TypeList beginParamterTypes = (parameterTypes != null) ?
                    new TypeList(parameterTypes) : new TypeList();

                beginParamterTypes.Add(typeof(AsyncCallback));
                beginParamterTypes.Add(typeof(object));

                methodBuilder = typeBuilder.DefineMethod(BeginInvokeMethodName,
                    DefaultMethodAttributes, DefaultCallingConventions,
                    typeof(IAsyncResult), beginParamterTypes.ToArray());

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                TypeList endParameterTypes = new TypeList();

                if (parameterTypes != null)
                    foreach (Type parameterType in parameterTypes)
                        if (parameterType.IsByRef)
                            endParameterTypes.Add(parameterType);

                endParameterTypes.Add(typeof(IAsyncResult));

                methodBuilder = typeBuilder.DefineMethod(EndInvokeMethodName,
                    DefaultMethodAttributes, DefaultCallingConventions,
                    returnType, endParameterTypes.ToArray());

                methodBuilder.SetImplementationFlags(DefaultMethodImplAttributes);

                Type attributeType = typeof(UnmanagedFunctionPointerAttribute);

                ConstructorInfo constructorInfo = attributeType.GetConstructor(
                    PublicInstanceBindingFlags, null,
                    new Type[] { typeof(CallingConvention) }, null);

                FieldInfo[] fieldInfo = {
                    attributeType.GetField(BestFitMappingFieldName,
                        PublicInstanceBindingFlags),
                    attributeType.GetField(CharSetFieldName,
                        PublicInstanceBindingFlags),
                    attributeType.GetField(SetLastErrorFieldName,
                        PublicInstanceBindingFlags),
                    attributeType.GetField(ThrowOnUnmappableCharFieldName,
                        PublicInstanceBindingFlags)
                };

                object[] fieldValues = {
                    bestFitMapping,       // default: true
                    charSet,              // default: (CharSet)0
                    setLastError,         // default: false
                    throwOnUnmappableChar // default: false
                };

                CustomAttributeBuilder customAttributeBuilder =
                    new CustomAttributeBuilder(constructorInfo,
                    new object[] { callingConvention }, fieldInfo,
                    fieldValues);

                typeBuilder.SetCustomAttribute(customAttributeBuilder);

                type = typeBuilder.CreateType();
            }
            catch (Exception e)
            {
                error = e;
            }

            return type;
        }
#endif
    }
}
