//Licensed to the Apache Software Foundation (ASF) under one or more
//contributor license agreements.  See the NOTICE file distributed with
//this work for additional information regarding copyright ownership.
//The ASF licenses this file to You under the Apache License, Version 2.0
//(the "License"); you may not use this file except in compliance with
//the License.  You may obtain a copy of the License at
//     http://www.apache.org/licenses/LICENSE-2.0
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.

using System;
using System.Reflection;
using System.Collections;
using System.Reflection.Emit;
using System.Threading;

namespace DynamicProxy
{
    /// <summary>
    /// Interface that a user defined proxy handler needs to implement.  This interface 
    /// defines one method that gets invoked by the generated proxy.  
    /// </summary>
    public interface IProxyInvocationHandler
    {
        /// <param name="proxy">The instance of the proxy</param>
        /// <param name="method">The method info that can be used to invoke the actual method on the object implementation</param>
        /// <param name="parameters">Parameters to pass to the method</param>
        /// <returns>Object</returns>
        object Invoke(object proxy, MethodInfo method, object[] parameters);
    }

    /// <summary>
    /// Factory class used to cache Types instances
    /// </summary>
    public class MetaDataFactory
    {
        private static Hashtable typeMap = new Hashtable();

        /// <summary>
        /// Class constructor.  Private because this is a static class.
        /// </summary>
        private MetaDataFactory()
        {
        }

        ///<summary>
        /// Method to add a new Type to the cache, using the type's fully qualified
        /// name as the key
        ///</summary>
        ///<param name="interfaceType">Type to cache</param>
        public static void Add(Type interfaceType)
        {
            if (interfaceType != null)
            {
                lock (typeMap.SyncRoot)
                {
                    if (!typeMap.ContainsKey(interfaceType.FullName))
                    {
                        typeMap.Add(interfaceType.FullName, interfaceType);
                    }
                }
            }
        }

        ///<summary>
        /// Method to return the method of a given type at a specified index.
        ///</summary>
        ///<param name="name">Fully qualified name of the method to return</param>
        ///<param name="i">Index to use to return MethodInfo</param>
        ///<returns>MethodInfo</returns>
        public static MethodInfo GetMethod(string name, int i)
        {
            Type type = null;
            lock (typeMap.SyncRoot)
            {
                type = (Type)typeMap[name];
            }

            return type.GetMethods()[i];
        }

        public static PropertyInfo GetProperty(string name, int i)
        {
            Type type = null;
            lock (typeMap.SyncRoot)
            {
                type = (Type)typeMap[name];
            }

            return type.GetProperties()[i];
        }
    }

    /// <summary>
    /// </summary>
    public class ProxyFactory
    {
        private static ProxyFactory instance;
        private static Object lockObj = new Object();

        private Hashtable typeMap = Hashtable.Synchronized(new Hashtable());
        private static readonly Hashtable opCodeTypeMapper = new Hashtable();

        private const string PROXY_SUFFIX = "Proxy";
        private const string ASSEMBLY_NAME = "ProxyAssembly";
        private const string MODULE_NAME = "ProxyModule";
        private const string HANDLER_NAME = "handler";

        // Initialize the value type mapper.  This is needed for methods with intrinsic 
        // return types, used in the Emit process.
        static ProxyFactory()
        {
            opCodeTypeMapper.Add(typeof(System.Boolean), OpCodes.Ldind_I1);
            opCodeTypeMapper.Add(typeof(System.Int16), OpCodes.Ldind_I2);
            opCodeTypeMapper.Add(typeof(System.Int32), OpCodes.Ldind_I4);
            opCodeTypeMapper.Add(typeof(System.Int64), OpCodes.Ldind_I8);
            opCodeTypeMapper.Add(typeof(System.Double), OpCodes.Ldind_R8);
            opCodeTypeMapper.Add(typeof(System.Single), OpCodes.Ldind_R4);
            opCodeTypeMapper.Add(typeof(System.UInt16), OpCodes.Ldind_U2);
            opCodeTypeMapper.Add(typeof(System.UInt32), OpCodes.Ldind_U4);
        }

        private ProxyFactory()
        {
        }

        public static ProxyFactory GetInstance()
        {
            if (instance == null)
            {
                CreateInstance();
            }

            return instance;
        }

        private static void CreateInstance()
        {
            lock (lockObj)
            {
                if (instance == null)
                {
                    instance = new ProxyFactory();
                }
            }
        }

        public Object Create(IProxyInvocationHandler handler, Type objType, bool isObjInterface)
        {
            string typeName = objType.FullName + PROXY_SUFFIX;
            Type type = (Type)typeMap[typeName];

            // check to see if the type was in the cache.  If the type was not cached, then
            // create a new instance of the dynamic type and add it to the cache.
            if (type == null)
            {
                if (isObjInterface)
                {
                    type = CreateType(handler, new Type[] { objType }, typeName);
                }
                else
                {
                    type = CreateType(handler, objType.GetInterfaces(), typeName);
                }

                typeMap.Add(typeName, type);
            }

            // return a new instance of the type.
            return Activator.CreateInstance(type, new object[] { handler });
        }

        public Object Create(IProxyInvocationHandler handler, Type objType)
        {
            return Create(handler, objType, false);
        }

        private Type CreateType(IProxyInvocationHandler handler, Type[] interfaces, string dynamicTypeName)
        {
            Type retVal = null;

            if (handler != null && interfaces != null)
            {
                Type objType = typeof(System.Object);
                Type handlerType = typeof(IProxyInvocationHandler);

                AppDomain domain = Thread.GetDomain();
                AssemblyName assemblyName = new AssemblyName();
                assemblyName.Name = ASSEMBLY_NAME;
                assemblyName.Version = new Version(1, 0, 0, 0);

                // create a new assembly for this proxy, one that isn't presisted on the file system
                AssemblyBuilder assemblyBuilder = domain.DefineDynamicAssembly(
                    assemblyName, AssemblyBuilderAccess.Run);
                    // assemblyName, AssemblyBuilderAccess.RunAndSave,".");  // to save it to the disk

                // create a new module for this proxy
                ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule(MODULE_NAME);

                // Set the class to be public and sealed
                TypeAttributes typeAttributes =
                    TypeAttributes.Class | TypeAttributes.Public | TypeAttributes.Sealed;

                // Gather up the proxy information and create a new type builder.  One that
                // inherits from Object and implements the interface passed in
                TypeBuilder typeBuilder = moduleBuilder.DefineType(
                    dynamicTypeName, typeAttributes, objType, interfaces);

                // Define a member variable to hold the delegate
                FieldBuilder handlerField = typeBuilder.DefineField(
                    HANDLER_NAME, handlerType, FieldAttributes.Private);


                // build a constructor that takes the delegate object as the only argument
                //ConstructorInfo defaultObjConstructor = objType.GetConstructor( new Type[0] );
                ConstructorInfo superConstructor = objType.GetConstructor(new Type[0]);
                ConstructorBuilder delegateConstructor = typeBuilder.DefineConstructor(
                    MethodAttributes.Public, CallingConventions.Standard, new Type[] { handlerType });

                #region( "Constructor IL Code" )
                ILGenerator constructorIL = delegateConstructor.GetILGenerator();

                // Load "this"
                constructorIL.Emit(OpCodes.Ldarg_0);
                // Load first constructor parameter
                constructorIL.Emit(OpCodes.Ldarg_1);
                // Set the first parameter into the handler field
                constructorIL.Emit(OpCodes.Stfld, handlerField);
                // Load "this"
                constructorIL.Emit(OpCodes.Ldarg_0);
                // Call the super constructor
                constructorIL.Emit(OpCodes.Call, superConstructor);
                // Constructor return
                constructorIL.Emit(OpCodes.Ret);
                #endregion

                // for every method that the interfaces define, build a corresponding 
                // method in the dynamic type that calls the handlers invoke method.  
                foreach (Type interfaceType in interfaces)
                {
                    GenerateMethod(interfaceType, handlerField, typeBuilder);
                }

                retVal = typeBuilder.CreateType();

                // assemblyBuilder.Save(dynamicTypeName + ".dll");
            }

            return retVal;
        }

        private static readonly MethodInfo INVOKE_METHOD = typeof(IProxyInvocationHandler).GetMethod("Invoke");
        private static readonly MethodInfo GET_METHODINFO_METHOD = typeof(MetaDataFactory).GetMethod("GetMethod", new Type[] { typeof(string), typeof(int) });

        private void GenerateMethod( Type interfaceType, FieldBuilder handlerField, TypeBuilder typeBuilder ) {
            MetaDataFactory.Add( interfaceType );
            MethodInfo[] interfaceMethods = interfaceType.GetMethods();
            PropertyInfo[] props = interfaceType.GetProperties();

            for ( int i = 0; i < interfaceMethods.Length; i++ ) {
                MethodInfo methodInfo = interfaceMethods[i];

                // Get the method parameters since we need to create an array
                // of parameter types
                ParameterInfo[] methodParams = methodInfo.GetParameters();
                int numOfParams = methodParams.Length;
                Type[] methodParameters = new Type[ numOfParams ];

                // convert the ParameterInfo objects into Type
                for ( int j = 0; j < numOfParams; j++ ) {
                    methodParameters[j] = methodParams[j].ParameterType;
                }

                // create a new builder for the method in the interface
                MethodBuilder methodBuilder = typeBuilder.DefineMethod(
                    methodInfo.Name, 
                    /*MethodAttributes.Public | MethodAttributes.Virtual | */ methodInfo.Attributes&~MethodAttributes.Abstract,
                    CallingConventions.Standard,
                    methodInfo.ReturnType, methodParameters );                                                   

                #region( "Handler Method IL Code" )
                ILGenerator methodIL = methodBuilder.GetILGenerator();
                        
                // load "this"
                methodIL.Emit( OpCodes.Ldarg_0 );
                // load the handler
                methodIL.Emit( OpCodes.Ldfld, handlerField );
                // load "this" since its needed for the call to invoke
                methodIL.Emit( OpCodes.Ldarg_0 );
                // load the name of the interface, used to get the MethodInfo object
                // from MetaDataFactory
                methodIL.Emit( OpCodes.Ldstr, interfaceType.FullName );
                // load the index, used to get the MethodInfo object 
                // from MetaDataFactory 
                methodIL.Emit( OpCodes.Ldc_I4, i ); 
                // invoke GetMethod in MetaDataFactory
                methodIL.Emit( OpCodes.Call, GET_METHODINFO_METHOD);

                // load the number of parameters onto the stack
                methodIL.Emit( OpCodes.Ldc_I4, numOfParams );
                // create a new array, using the size that was just pused on the stack
                methodIL.Emit( OpCodes.Newarr, typeof(object) );
                        
                // if we have any parameters, then iterate through and set the values
                // of each element to the corresponding arguments
                for ( int j = 0; j < numOfParams; j++ ) {
                    methodIL.Emit( OpCodes.Dup );   // this copies the array
                    methodIL.Emit( OpCodes.Ldc_I4, j );
                    methodIL.Emit( OpCodes.Ldarg, j + 1 );
                    if ( methodParameters[j].IsValueType ) {
                        methodIL.Emit( OpCodes.Box, methodParameters[j] );
                    }
                    methodIL.Emit( OpCodes.Stelem_Ref );                                    
                }

                // call the Invoke method
                methodIL.Emit( OpCodes.Callvirt, INVOKE_METHOD );
                        
                if ( methodInfo.ReturnType != typeof(void) ) { 
                    // if the return type if a value type, then unbox the return value
                    // so that we don't get junk.
                    if ( methodInfo.ReturnType.IsValueType  ) {
                        methodIL.Emit( OpCodes.Unbox, methodInfo.ReturnType );
                        if ( methodInfo.ReturnType.IsEnum ) {
                            methodIL.Emit( OpCodes.Ldind_I4 );
                        } else if ( !methodInfo.ReturnType.IsPrimitive ) {
                            methodIL.Emit( OpCodes.Ldobj, methodInfo.ReturnType );
                        } else {
                            methodIL.Emit( (OpCode) opCodeTypeMapper[ methodInfo.ReturnType ] );
                        }
                    }                                                                     
                } else {
                    // pop the return value that Invoke returned from the stack since
                    // the method's return type is void. 
                    methodIL.Emit( OpCodes.Pop );
                }
                                            
                // Return
                methodIL.Emit( OpCodes.Ret );
                #endregion
            }

            //for (int i = 0; i < props.Length; i++)
            //{
            //    PropertyInfo p = props[i];

            //    PropertyBuilder pb = typeBuilder.DefineProperty(p.Name, p.Attributes, p.PropertyType, new Type[] { p.PropertyType });
            //    pb.SetGetMethod((MethodBuilder)methodTable[p.GetGetMethod()]);
            //    pb.SetSetMethod((MethodBuilder)methodTable[p.GetSetMethod()]);
            //}

            // Iterate through the parent interfaces and recursively call this method
            foreach ( Type parentType in interfaceType.GetInterfaces() ) {
                GenerateMethod( parentType, handlerField, typeBuilder );            
            }
        }
    }
}
