#if !SILVERLIGHT && !PocketPC
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Resources;
using System.Text;
using System.Threading;
using System.Globalization;

namespace Newtonsoft.Json.Utilities
{
  internal class DynamicWrapperBase
  {
    internal protected object UnderlyingObject;
  }

  internal static class DynamicWrapper
  {
    private static readonly object _lock = new object();
    private static readonly WrapperDictionary _wrapperDictionary = new WrapperDictionary();

    private static ModuleBuilder _moduleBuilder;

    private static ModuleBuilder ModuleBuilder
    {
      get
      {
        Init();
        return _moduleBuilder;
      }
    }

    private static void Init()
    {
      if (_moduleBuilder == null)
      {
        lock (_lock)
        {
          if (_moduleBuilder == null)
          {
            AssemblyName assemblyName = new AssemblyName("Newtonsoft.Json.Dynamic");
            assemblyName.KeyPair = new StrongNameKeyPair(GetStrongKey());

            AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            _moduleBuilder = assembly.DefineDynamicModule("Newtonsoft.Json.DynamicModule", false);
          }
        }
      }
    }

    private static byte[] GetStrongKey()
    {
      using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("Newtonsoft.Json.Dynamic.snk"))
      {
        if (stream == null)
          throw new MissingManifestResourceException("Should have a Newtonsoft.Json.Dynamic.snk as an embedded resource.");

        int length = (int)stream.Length;
        byte[] buffer = new byte[length];
        stream.Read(buffer, 0, length);

        return buffer;
      }
    }

    public static Type GetWrapper(Type interfaceType, Type realObjectType)
    {
      Type wrapperType = _wrapperDictionary.GetType(interfaceType, realObjectType);

      if (wrapperType == null)
      {
        wrapperType = GenerateWrapperType(interfaceType, realObjectType);
        _wrapperDictionary.SetType(interfaceType, realObjectType, wrapperType);
      }

      return wrapperType;
    }

    public static object GetUnderlyingObject(object wrapper)
    {
      DynamicWrapperBase wrapperBase = wrapper as DynamicWrapperBase;
      if (wrapperBase == null)
        throw new ArgumentException("Object is not a wrapper.", "wrapper");

      return wrapperBase.UnderlyingObject;
    }

    private static Type GenerateWrapperType(Type interfaceType, Type underlyingType)
    {
      TypeBuilder wrapperBuilder = ModuleBuilder.DefineType(
          "{0}_{1}_Wrapper".FormatWith(CultureInfo.InvariantCulture, interfaceType.Name, underlyingType.Name),
          TypeAttributes.NotPublic | TypeAttributes.Sealed,
          typeof(DynamicWrapperBase),
          new[] { interfaceType });

      WrapperMethodBuilder wrapperMethod = new WrapperMethodBuilder(underlyingType, wrapperBuilder);

      foreach (MethodInfo method in interfaceType.AllMethods())
      {
        wrapperMethod.Generate(method);
      }

      return wrapperBuilder.CreateType();
    }

    public static T CreateWrapper<T>(object realObject) where T : class
    {
      var dynamicType = GetWrapper(typeof(T), realObject.GetType());
      var dynamicWrapper = (DynamicWrapperBase)Activator.CreateInstance(dynamicType);

      dynamicWrapper.UnderlyingObject = realObject;

      return dynamicWrapper as T;
    }
  }

  internal class WrapperMethodBuilder
  {
    private readonly Type _realObjectType;
    private readonly TypeBuilder _wrapperBuilder;

    public WrapperMethodBuilder(Type realObjectType, TypeBuilder proxyBuilder)
    {
      _realObjectType = realObjectType;
      _wrapperBuilder = proxyBuilder;
    }

    public void Generate(MethodInfo newMethod)
    {
      if (newMethod.IsGenericMethod)
        newMethod = newMethod.GetGenericMethodDefinition();

      FieldInfo srcField = typeof(DynamicWrapperBase).GetField("UnderlyingObject", BindingFlags.Instance | BindingFlags.NonPublic);

      var parameters = newMethod.GetParameters();
      var parameterTypes = parameters.Select(parameter => parameter.ParameterType).ToArray();

      MethodBuilder methodBuilder = _wrapperBuilder.DefineMethod(
          newMethod.Name,
          MethodAttributes.Public | MethodAttributes.Virtual,
          newMethod.ReturnType,
          parameterTypes);

      if (newMethod.IsGenericMethod)
      {
        methodBuilder.DefineGenericParameters(
            newMethod.GetGenericArguments().Select(arg => arg.Name).ToArray());
      }

      ILGenerator ilGenerator = methodBuilder.GetILGenerator();

      LoadUnderlyingObject(ilGenerator, srcField);
      PushParameters(parameters, ilGenerator);
      ExecuteMethod(newMethod, parameterTypes, ilGenerator);
      Return(ilGenerator);
    }

    private static void Return(ILGenerator ilGenerator)
    {
      ilGenerator.Emit(OpCodes.Ret);
    }

    private void ExecuteMethod(MethodBase newMethod, Type[] parameterTypes, ILGenerator ilGenerator)
    {
      MethodInfo srcMethod = GetMethod(newMethod, parameterTypes);

      if (srcMethod == null)
        throw new MissingMethodException("Unable to find method " + newMethod.Name + " on " + _realObjectType.FullName);

      ilGenerator.Emit(OpCodes.Call, srcMethod);
    }

    private MethodInfo GetMethod(MethodBase realMethod, Type[] parameterTypes)
    {
      if (realMethod.IsGenericMethod)
        return _realObjectType.GetGenericMethod(realMethod.Name, parameterTypes);

      return _realObjectType.GetMethod(realMethod.Name, parameterTypes);
    }

    private static void PushParameters(ICollection<ParameterInfo> parameters, ILGenerator ilGenerator)
    {
      for (int i = 1; i < parameters.Count + 1; i++)
        ilGenerator.Emit(OpCodes.Ldarg, i);
    }

    private static void LoadUnderlyingObject(ILGenerator ilGenerator, FieldInfo srcField)
    {
      ilGenerator.Emit(OpCodes.Ldarg_0);
      ilGenerator.Emit(OpCodes.Ldfld, srcField);
    }
  }

  internal class WrapperDictionary
  {
    private readonly Dictionary<string, Type> _wrapperTypes = new Dictionary<string, Type>();

    private static string GenerateKey(Type interfaceType, Type realObjectType)
    {
      return interfaceType.Name + "_" + realObjectType.Name;
    }

    public Type GetType(Type interfaceType, Type realObjectType)
    {
      string key = GenerateKey(interfaceType, realObjectType);

      if (_wrapperTypes.ContainsKey(key))
        return _wrapperTypes[key];

      return null;
    }

    public void SetType(Type interfaceType, Type realObjectType, Type wrapperType)
    {
      string key = GenerateKey(interfaceType, realObjectType);

      if (_wrapperTypes.ContainsKey(key))
        _wrapperTypes[key] = wrapperType;
      else
        _wrapperTypes.Add(key, wrapperType);
    }
  }

  internal static class TypeExtensions
  {
    public static MethodInfo GetGenericMethod(this Type type, string name, params Type[] parameterTypes)
    {
      var methods = type.GetMethods().Where(method => method.Name == name);

      foreach (var method in methods)
      {
        if (method.HasParameters(parameterTypes))
          return method;
      }

      return null;
    }

    public static bool HasParameters(this MethodInfo method, params Type[] parameterTypes)
    {
      var methodParameters = method.GetParameters().Select(parameter => parameter.ParameterType).ToArray();

      if (methodParameters.Length != parameterTypes.Length)
        return false;

      for (int i = 0; i < methodParameters.Length; i++)
        if (methodParameters[i].ToString() != parameterTypes[i].ToString())
          return false;

      return true;
    }

    public static IEnumerable<Type> AllInterfaces(this Type target)
    {
      foreach (var IF in target.GetInterfaces())
      {
        yield return IF;
        foreach (var childIF in IF.AllInterfaces())
        {
          yield return childIF;
        }
      }
    }

    public static IEnumerable<MethodInfo> AllMethods(this Type target)
    {
      var allTypes = target.AllInterfaces().ToList();
      allTypes.Add(target);

      return from type in allTypes
             from method in type.GetMethods()
             select method;
    }
  }
}
#endif