#region CPL License
/*
Nuclex Framework
Copyright (C) 2002-2010 Nuclex Development Labs
This library is free software; you can redistribute it and/or
modify it under the terms of the IBM Common Public License as
published by the IBM Corporation; either version 1.0 of the
License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
IBM Common Public License for more details.
You should have received a copy of the IBM Common Public
License along with this library
*/
#endregion
#if !(XBOX360 || WINDOWS_PHONE)
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
namespace Nuclex.Support.Cloning {
  /// An action that takes its arguments as references to a structure 
  /// Type of the first argument to the method 
  /// Type of the second argument to the method 
  /// (ref TFirst first, ref TSecond second)
    where TFirst : struct
    where TSecond : struct;
  /// 
  ///   Cloning factory which uses expression trees to improve performance when cloning
  ///   is a high-frequency action.
  ///  
  public class ExpressionTreeCloner : ICloneFactory {
    /// Initializes the static members of the expression tree cloner 
    static ExpressionTreeCloner() {
      shallowCloners = new ConcurrentDictionary>();
      deepCloners = new ConcurrentDictionary>();
    }
    /// 
    ///   Creates a deep clone of the specified object, also creating clones of all
    ///   child objects being referenced
    ///  
    /// Type of the object that will be cloned 
    /// A deep clone of the provided object 
    public static TCloned DeepClone(
      TCloned objectToClone, bool usePropertyBasedClone
    ) {
      object objectToCloneAsObject = objectToClone;
      if(objectToCloneAsObject == null) {
        return default(TCloned);
      }
      if(usePropertyBasedClone) {
        throw new NotImplementedException("Not implemented yet");
      } else {
        Func cloner = getOrCreateDeepFieldBasedCloner(typeof(TCloned));
        return (TCloned)cloner(objectToCloneAsObject);
      }
    }
    /// 
    ///   Creates a shallow clone of the specified object, reusing any referenced objects
    ///  
    /// Type of the object that will be cloned 
    /// A shallow clone of the provided object 
    public static TCloned ShallowClone(
      TCloned objectToClone, bool usePropertyBasedClone
    ) {
      throw new NotImplementedException("Not implemented yet");
    }
    /// 
    ///   Creates a deep clone of the specified object, also creating clones of all
    ///   child objects being referenced
    ///  
    /// Type of the object that will be cloned 
    /// A deep clone of the provided object 
    TCloned ICloneFactory.DeepClone(
      TCloned objectToClone, bool usePropertyBasedClone
    ) {
      return ExpressionTreeCloner.DeepClone(objectToClone, usePropertyBasedClone);
    }
    /// 
    ///   Creates a shallow clone of the specified object, reusing any referenced objects
    ///  
    /// Type of the object that will be cloned 
    /// A shallow clone of the provided object 
    TCloned ICloneFactory.ShallowClone(
      TCloned objectToClone, bool usePropertyBasedClone
    ) {
      return ExpressionTreeCloner.ShallowClone(objectToClone, usePropertyBasedClone);
    }
    /// 
    ///   Retrieves the existing clone method for the specified type or compiles one if
    ///   none exists for the type yet
    ///  
    /// The clone method for the specified type 
    private static Func getOrCreateDeepFieldBasedCloner(Type clonedType) {
      Func cloner;
      if(!deepCloners.TryGetValue(clonedType, out cloner)) {
        cloner = createDeepFieldBasedCloner(clonedType);
        deepCloners.TryAdd(clonedType, cloner);
      }
      return cloner;
    }
    /// 
    ///   Generates state transfer expressions to copy an array of primitive types
    ///  
    /// The variable holding the cloned array 
    private static ParameterExpression generatePrimitiveArrayTransferExpressions(
      Type clonedType,
      Expression original,
      ICollection variables,
      ICollection transferExpressions
    ) {
      // We need a temporary variable because the IfThen expression is not suitable
      // for returning values
      ParameterExpression clone = Expression.Variable(typeof(object));
      variables.Add(clone);
      // If the array referenced by 'original' is not null, call Array.Clone() on it
      // and assign the result to our temporary variable
      MethodInfo arrayCloneMethodInfo = typeof(Array).GetMethod("Clone");
      transferExpressions.Add(
        Expression.IfThen(
          Expression.NotEqual(original, Expression.Constant(null)),
          Expression.Assign(
            clone,
            Expression.Convert(
              Expression.Call(
                Expression.Convert(original, typeof(Array)), arrayCloneMethodInfo
              ),
              clonedType
            )
          )
        )
      );
      return clone;
    }
    /// 
    ///   Generates state transfer expressions to copy an array of complex types
    ///  
    /// The variable holding the cloned array 
    private static ParameterExpression generateComplexArrayTransferExpressions(
      Type clonedType,
      Expression original,
      IList variables,
      ICollection transferExpressions
    ) {
      // We need a temporary variable because the IfThen expression is not suitable
      // for returning values
      ParameterExpression clone = Expression.Variable(clonedType);
      variables.Add(clone);
      ParameterExpression typedOriginal = Expression.Variable(clonedType);
      variables.Add(typedOriginal);
      transferExpressions.Add(
        Expression.Assign(typedOriginal, Expression.Convert(original, clonedType))
      );
      int dimensionCount = clonedType.GetArrayRank();
      int baseVariableIndex = variables.Count;
      var arrayTransferExpressions = new List();
      Type elementType = clonedType.GetElementType();
      // Retrieve the length of each of the array's dimensions
      MethodInfo arrayGetLengthMethodInfo = typeof(Array).GetMethod("GetLength");
      for(int index = 0; index < dimensionCount; ++index) {
        ParameterExpression length = Expression.Variable(typeof(int));
        variables.Add(length);
        arrayTransferExpressions.Add(
          Expression.Assign(
            length,
            Expression.Call(
              typedOriginal, arrayGetLengthMethodInfo, Expression.Constant(index)
            )
          )
        );
      }
      // Create a new array of identical size
      switch(dimensionCount) {
        case 1: {
          MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod(
            "CreateInstance", new Type[] { typeof(Type), typeof(int) }
          );
          arrayTransferExpressions.Add(
            Expression.Assign(
              clone,
              Expression.Convert(
                Expression.Call(
                  arrayCreateInstanceMethodInfo,
                  Expression.Constant(elementType),
                  variables[baseVariableIndex]
                ),
                clonedType
              )
            )
          );
          break;
        }
        case 2: {
          MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod(
            "CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int) }
          );
          arrayTransferExpressions.Add(
            Expression.Assign(
              clone,
              Expression.Convert(
                Expression.Call(
                  arrayCreateInstanceMethodInfo,
                  Expression.Constant(elementType),
                  variables[baseVariableIndex],
                  variables[baseVariableIndex + 1]
                ),
                clonedType
              )
            )
          );
          break;
        }
        case 3: {
          MethodInfo arrayCreateInstanceMethodInfo = typeof(Array).GetMethod(
            "CreateInstance", new Type[] { typeof(Type), typeof(int), typeof(int), typeof(int) }
          );
          arrayTransferExpressions.Add(
            Expression.Assign(
              clone,
              Expression.Convert(
                Expression.Call(
                  arrayCreateInstanceMethodInfo,
                  Expression.Constant(elementType),
                  variables[baseVariableIndex],
                  variables[baseVariableIndex + 1],
                  variables[baseVariableIndex + 2]
                ),
                clonedType
              )
            )
          );
          break;
        }
        default: {
          throw new InvalidOperationException("Unsupported array dimension count");
        }
      }
      // Only execute the array transfer expressions if the array is not null
      transferExpressions.Add(
        Expression.IfThen(
          Expression.NotEqual(original, Expression.Constant(null)),
          Expression.Block(arrayTransferExpressions)
        )
      );
      return clone;
    }
    /// Generates state transfer expressions to copy a complex type 
    /// The variable holding the cloned array 
    private static ParameterExpression generateComplexTypeTransferExpressions(
      Type clonedType,
      Expression original,
      IList variables,
      ICollection transferExpressions
    ) {
      // Create a variable to hold the clone and begin by assigning a new instance of
      // the cloned type to it.
      ParameterExpression clone = Expression.Variable(clonedType);
      variables.Add(clone);
      transferExpressions.Add(Expression.Assign(clone, Expression.New(clonedType)));
      // To access the fields of the original type, we need it to be of the actual
      // type instead of an object, so perform a downcast
      ParameterExpression typedOriginal = Expression.Variable(clonedType);
      variables.Add(typedOriginal);
      transferExpressions.Add(
        Expression.Assign(typedOriginal, Expression.Convert(original, clonedType))
      );
      // Now enumerate all of the type's fields and generate transfer expressions for
      // each of them
      FieldInfo[] fieldInfos = clonedType.GetFields(
        BindingFlags.Public | BindingFlags.NonPublic |
        BindingFlags.Instance | BindingFlags.FlattenHierarchy
      );
      for(int index = 0; index < fieldInfos.Length; ++index) {
        FieldInfo fieldInfo = fieldInfos[index];
        Type fieldType = fieldInfo.FieldType;
        if(fieldType.IsPrimitive || (fieldType == typeof(string))) {
          // Primitive types and strings can be transferred by simple assignment
          transferExpressions.Add(
            Expression.Assign(
              Expression.Field(clone, fieldInfo),
              Expression.Field(typedOriginal, fieldInfo)
            )
          );
        } else if(fieldType.IsValueType) {
          // TODO: Copy field without null check
        } else {
          var fieldTransferExpressions = new List();
          var fieldVariables = new List();
          Expression fieldClone;
          if(fieldType.IsArray) {
            /*
            Type elementType = fieldType.GetElementType();
            if(elementType.IsPrimitive || (elementType == typeof(string))) {
              fieldClone = generatePrimitiveArrayTransferExpressions(
                fieldType,
                Expression.Field(typedOriginal, fieldInfo),
                fieldVariables,
                fieldTransferExpressions
              );
            } else {
              fieldClone = generateComplexArrayTransferExpressions(
                fieldType,
                Expression.Field(typedOriginal, fieldInfo),
                fieldVariables,
                fieldTransferExpressions
              );
            }
            */
            fieldClone = Expression.Field(typedOriginal, fieldInfo);
            fieldTransferExpressions.Add(fieldClone);
          } else {
            MethodInfo getOrCreateClonerMethodInfo = typeof(ExpressionTreeCloner).GetMethod(
              "getOrCreateDeepFieldBasedCloner",
              BindingFlags.NonPublic | BindingFlags.Static
            );
            MethodInfo getTypeMethodInfo = typeof(object).GetMethod("GetType");
            MethodInfo invokeMethodInfo = typeof(Func).GetMethod("Invoke");
            fieldTransferExpressions.Add(
              Expression.Assign(
                Expression.Field(clone, fieldInfo),
                Expression.Convert(
                  Expression.Call(
                    Expression.Call(
                      getOrCreateClonerMethodInfo,
                      Expression.Call(
                        Expression.Field(typedOriginal, fieldInfo), getTypeMethodInfo
                      )
                    ),
                    invokeMethodInfo,
                    Expression.Field(typedOriginal, fieldInfo)
                  ),
                  fieldType
                )
              )
            );
          }
          transferExpressions.Add(
            Expression.IfThen(
              Expression.NotEqual(
                Expression.Field(typedOriginal, fieldInfo), Expression.Constant(null)
              ),
              Expression.Block(fieldVariables, fieldTransferExpressions)
            )
          );
        }
      }
      return clone;
    }
    /// Compiles a method that creates a clone of an object 
    /// A method that clones an object of the provided type 
    private static Func createDeepFieldBasedCloner(Type clonedType) {
      ParameterExpression original = Expression.Parameter(typeof(object), "original");
      var transferExpressions = new List();
      var variables = new List();
      if(clonedType.IsPrimitive || (clonedType == typeof(string))) {
        // Primitives and strings are copied on direct assignment
        transferExpressions.Add(original);
      } else if(clonedType.IsArray) {
        ParameterExpression clone;
        Type elementType = clonedType.GetElementType();
        if(elementType.IsPrimitive || (elementType == typeof(string))) {
          clone = generatePrimitiveArrayTransferExpressions(
            clonedType, original, variables, transferExpressions
          );
        } else {
          clone = generateComplexArrayTransferExpressions(
            clonedType, original, variables, transferExpressions
          );
        }
        transferExpressions.Add(clone);
        //clone = original;
      } else {
        transferExpressions.Add(
          generateComplexTypeTransferExpressions(
            clonedType, original, variables, transferExpressions
          )
        );
      }
      Expression> expression;
      if(variables.Count > 0) {
        expression = Expression.Lambda>(
          Expression.Block(variables, transferExpressions), original
        );
      } else if(transferExpressions.Count == 1) {
        expression = Expression.Lambda>(
          transferExpressions[0], original
        );
      } else {
        expression = Expression.Lambda>(
          Expression.Block(transferExpressions), original
        );
      }
      return expression.Compile();
    }
#if false
    /// 
    ///   Transfers the state of one object into another, creating clones of referenced objects
    ///  
    /// Type of the object whose sate will be transferred 
    /// (TState original, TState target, bool propertyBased)
      where TState : class {
      throw new NotImplementedException();
    }
    /// 
    ///   Transfers the state of one object into another, creating clones of referenced objects
    ///  
    /// Type of the object whose sate will be transferred 
    /// (ref TState original, ref TState target, bool propertyBased)
      where TState : struct {
      throw new NotImplementedException();
    }
    /// Transfers the state of one object into another 
    /// Type of the object whose sate will be transferred 
    /// (TState original, TState target, bool propertyBased)
      where TState : class {
      throw new NotImplementedException();
    }
    /// Transfers the state of one object into another 
    /// Type of the object whose sate will be transferred 
    /// (ref TState original, ref TState target, bool propertyBased)
      where TState : struct {
      throw new NotImplementedException();
    }
    /// 
    ///   Compiles a method that copies the state of one object into another object
    ///  
    /// Type of object whose state will be copied 
    /// A method that copies the state from one object into another object 
    public static Action CreateReferenceCopier(bool deepClone)
      where TCloned : class {
      throw new NotImplementedException();
    }
    /// 
    ///   Compiles a method that copies the state of one object into another object
    ///  
    /// Type of object whose state will be copied 
    /// A method that copies the state from one object into another object 
    public static ReferenceAction CreateValueCopier(bool deepClone)
      where TCloned : struct {
      throw new NotImplementedException();
    }
    /// Compiles a method that creates a clone of an object 
    /// Type of object that will be cloned 
    /// A method that clones an object of the provided type 
    public static Func CreateCloner(bool deepClone)
      where TCloned : class, new() {
      throw new NotImplementedException();
    }
#endif
    /// Compiled cloners that perform shallow clone operations 
    private static ConcurrentDictionary> shallowCloners;
    /// Compiled cloners that perform deep clone operations 
    private static ConcurrentDictionary> deepCloners;
  }
} // namespace Nuclex.Support.Cloning
#endif // !(XBOX360 || WINDOWS_PHONE)