using System;
using System.Collections.Generic;
using System.Reflection;
using System.ComponentModel;
using Csla.Properties;
namespace Csla.Data
{
  /// 
  /// Map data from a source into a target object
  /// by copying public property values.
  /// 
  /// 
  public static class DataMapper
  {
    #region Map from IDictionary
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// A name/value dictionary containing the source values.
    /// An object with properties to be set from the dictionary.
    /// 
    /// The key names in the dictionary must match the property names on the target
    /// object. Target properties may not be readonly or indexed.
    /// 
    public static void Map(System.Collections.IDictionary source, object target)
    {
      Map(source, target, false);
    }
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// A name/value dictionary containing the source values.
    /// An object with properties to be set from the dictionary.
    /// A list of property names to ignore. 
    /// These properties will not be set on the target object.
    /// 
    /// The key names in the dictionary must match the property names on the target
    /// object. Target properties may not be readonly or indexed.
    /// 
    public static void Map(System.Collections.IDictionary source, object target, params string[] ignoreList)
    {
      Map(source, target, false, ignoreList);
    }
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// A name/value dictionary containing the source values.
    /// An object with properties to be set from the dictionary.
    /// A list of property names to ignore. 
    /// These properties will not be set on the target object.
    /// If , any exceptions will be supressed.
    /// 
    /// The key names in the dictionary must match the property names on the target
    /// object. Target properties may not be readonly or indexed.
    /// 
    public static void Map(
      System.Collections.IDictionary source, 
      object target, bool suppressExceptions, 
      params string[] ignoreList)
    {
      List ignore = new List(ignoreList);
      foreach (string propertyName in source.Keys)
      {
        if (!ignore.Contains(propertyName))
        {
          try
          {
            SetPropertyValue(target, propertyName, source[propertyName]);
          }
          catch (Exception ex)
          {
            if (!suppressExceptions)
              throw new ArgumentException(
                String.Format("{0} ({1})", 
                Resources.PropertyCopyFailed, propertyName), ex);
          }
        }
      }
    }
    #endregion
    #region Map from Object
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// An object containing the source values.
    /// An object with properties to be set from the dictionary.
    /// 
    /// The property names and types of the source object must match the property names and types
    /// on the target object. Source properties may not be indexed. 
    /// Target properties may not be readonly or indexed.
    /// 
    public static void Map(object source, object target)
    {
      Map(source, target, false);
    }
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// An object containing the source values.
    /// An object with properties to be set from the dictionary.
    /// A list of property names to ignore. 
    /// These properties will not be set on the target object.
    /// 
    /// The property names and types of the source object must match the property names and types
    /// on the target object. Source properties may not be indexed. 
    /// Target properties may not be readonly or indexed.
    /// 
    public static void Map(object source, object target, params string[] ignoreList)
    {
      Map(source, target, false, ignoreList);
    }
    /// 
    /// Copies values from the source into the
    /// properties of the target.
    /// 
    /// An object containing the source values.
    /// An object with properties to be set from the dictionary.
    /// A list of property names to ignore. 
    /// These properties will not be set on the target object.
    /// If , any exceptions will be supressed.
    /// 
    /// 
    /// The property names and types of the source object must match the property names and types
    /// on the target object. Source properties may not be indexed. 
    /// Target properties may not be readonly or indexed.
    /// 
    /// Properties to copy are determined based on the source object. Any properties
    /// on the source object marked with the  equal
    /// to false are ignored.
    /// 
    /// 
    public static void Map(
      object source, object target, 
      bool suppressExceptions, 
      params string[] ignoreList)
    {
      List ignore = new List(ignoreList);
      PropertyInfo[] sourceProperties =
        GetSourceProperties(source.GetType());
      foreach (PropertyInfo sourceProperty in sourceProperties)
      {
        string propertyName = sourceProperty.Name;
        if (!ignore.Contains(propertyName))
        {
          try
          {
            SetPropertyValue(
              target, propertyName, 
              sourceProperty.GetValue(source, null));
          }
          catch (Exception ex)
          {
            if (!suppressExceptions)
              throw new ArgumentException(
                String.Format("{0} ({1})", 
                Resources.PropertyCopyFailed, propertyName), ex);
          }
        }
      }
    }
    private static PropertyInfo[] GetSourceProperties(Type sourceType)
    {
      List result = new List();
      PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties(sourceType);
      foreach (PropertyDescriptor item in props)
        if (item.IsBrowsable)
          result.Add(sourceType.GetProperty(item.Name));
      return result.ToArray();
    }
    #endregion
    /// 
    /// Sets an object's property with the specified value,
    /// coercing that value to the appropriate type if possible.
    /// 
    /// Object containing the property to set.
    /// Name of the property to set.
    /// Value to set into the property.
    public static void SetPropertyValue(
      object target, string propertyName, object value)
    {
      PropertyInfo propertyInfo =
        target.GetType().GetProperty(propertyName);
      if (value == null)
        propertyInfo.SetValue(target, value, null);
      else
      {
        Type pType =
          Utilities.GetPropertyType(propertyInfo.PropertyType);
        Type vType =
          Utilities.GetPropertyType(value.GetType());
        if (pType.Equals(vType))
        {
          // types match, just copy value
          propertyInfo.SetValue(target, value, null);
        }
        else
        {
          // types don't match, try to coerce
          if (pType.Equals(typeof(Guid)))
            propertyInfo.SetValue(
              target, new Guid(value.ToString()), null);
          else if (pType.IsEnum && vType.Equals(typeof(string)))
            propertyInfo.SetValue(target, Enum.Parse(pType, value.ToString()), null);
          else
            propertyInfo.SetValue(
              target, Convert.ChangeType(value, pType), null);
        }
      }
    }
  }
}