using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.ComponentModel;
using System.Reflection;
using Csla.Properties;
namespace Csla.Data
{
  /// 
  /// An ObjectAdapter is used to convert data in an object 
  /// or collection into a DataTable.
  /// 
  public class ObjectAdapter
  {
    /// 
    /// Fills the DataSet with data from an object or collection.
    /// 
    /// 
    /// The name of the DataTable being filled is will be the class name of
    /// the object acting as the data source. The
    /// DataTable will be inserted if it doesn't already exist in the DataSet.
    /// 
    /// A reference to the DataSet to be filled.
    /// A reference to the object or collection acting as a data source.
    public void Fill(DataSet ds, object source)
    {
      string className = source.GetType().Name;
      Fill(ds, className, source);
    }
    /// 
    /// Fills the DataSet with data from an object or collection.
    /// 
    /// 
    /// The name of the DataTable being filled is specified as a parameter. The
    /// DataTable will be inserted if it doesn't already exist in the DataSet.
    /// 
    /// A reference to the DataSet to be filled.
    /// 
    /// A reference to the object or collection acting as a data source.
    public void Fill(DataSet ds, string tableName, object source)
    {
      DataTable dt;
      bool exists;
      dt = ds.Tables[tableName];
      exists = (dt != null);
      if (!exists)
        dt = new DataTable(tableName);
      Fill(dt, source);
      if (!exists)
        ds.Tables.Add(dt);
    }
    /// 
    /// Fills a DataTable with data values from an object or collection.
    /// 
    /// A reference to the DataTable to be filled.
    /// A reference to the object or collection acting as a data source.
    public void Fill(DataTable dt, object source)
    {
      if (source == null)
        throw new ArgumentException(Resources.NothingNotValid);
      // get the list of columns from the source
      List columns = GetColumns(source);
      if (columns.Count < 1) return;
      // create columns in DataTable if needed
      foreach (string column in columns)
        if (!dt.Columns.Contains(column))
          dt.Columns.Add(column);
      // get an IList and copy the data
      CopyData(dt, GetIList(source), columns);
    }
    #region DataCopyIList
    private IList GetIList(object source)
    {
      if (source is IListSource)
        return ((IListSource)source).GetList();
      else if (source is IList)
        return source as IList;
      else
      {
        // this is a regular object - create a list
        ArrayList col = new ArrayList();
        col.Add(source);
        return col;
      }
    }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
    private void CopyData(
      DataTable dt, IList ds, List columns)
    {
      // load the data into the DataTable
      dt.BeginLoadData();
      for (int index = 0; index < ds.Count; index++)
      {
        DataRow dr = dt.NewRow();
        foreach (string column in columns)
        {
          try
          {
            dr[column] = GetField(ds[index], column);
          }
          catch (Exception ex)
          {
            dr[column] = ex.Message;
          }
        }
        dt.Rows.Add(dr);
      }
      dt.EndLoadData();
    }
    #endregion
    #region GetColumns
    private List GetColumns(object source)
    {
      List result;
      // first handle DataSet/DataTable
      object innerSource;
      IListSource iListSource = source as IListSource;
      if (iListSource != null)
        innerSource = iListSource.GetList();
      else
        innerSource = source;
      DataView dataView = innerSource as DataView;
      if (dataView != null)
        result = ScanDataView(dataView);
      else
      {
        // now handle lists/arrays/collections
        IEnumerable iEnumerable = innerSource as IEnumerable;
        if (iEnumerable != null)
        {
          Type childType = Utilities.GetChildItemType(
            innerSource.GetType());
          result = ScanObject(childType);
        }
        else
        {
          // the source is a regular object
          result = ScanObject(innerSource.GetType());
        }
      }
      return result;
    }
    private List ScanDataView(DataView ds)
    {
      List result = new List();
      for (int field = 0; field < ds.Table.Columns.Count; field++)
        result.Add(ds.Table.Columns[field].ColumnName);
      return result;
    }
    private List ScanObject(Type sourceType)
    {
      List result = new List();
      if (sourceType != null)
      {
        // retrieve a list of all public properties
        PropertyInfo[] props = sourceType.GetProperties();
        if (props.Length >= 0)
          for (int column = 0; column < props.Length; column++)
            if (props[column].CanRead)
              result.Add(props[column].Name);
        // retrieve a list of all public fields
        FieldInfo[] fields = sourceType.GetFields();
        if (fields.Length >= 0)
          for (int column = 0; column < fields.Length; column++)
            result.Add(fields[column].Name);
      }
      return result;
    }
    #endregion
    #region GetField
    private static string GetField(object obj, string fieldName)
    {
      string result;
      DataRowView dataRowView = obj as DataRowView;
      if (dataRowView != null)
      {
        // this is a DataRowView from a DataView
        result = dataRowView[fieldName].ToString();
      }
      else if (obj is ValueType && obj.GetType().IsPrimitive)
      {
        // this is a primitive value type
        result = obj.ToString();
      }
      else
      {
        string tmp = obj as string;
        if (tmp != null)
        {
          // this is a simple string
          result = (string)obj;
        }
        else
        {
          // this is an object or Structure
          try
          {
            Type sourceType = obj.GetType();
            // see if the field is a property
            PropertyInfo prop = sourceType.GetProperty(fieldName);
            if ((prop == null) || (!prop.CanRead))
            {
              // no readable property of that name exists - 
              // check for a field
              FieldInfo field = sourceType.GetField(fieldName);
              if (field == null)
              {
                // no field exists either, throw an exception
                throw new DataException(
                  Resources.NoSuchValueExistsException +
                  " " + fieldName);
              }
              else
              {
                // got a field, return its value
                result = field.GetValue(obj).ToString();
              }
            }
            else
            {
              // found a property, return its value
              result = prop.GetValue(obj, null).ToString();
            }
          }
          catch (Exception ex)
          {
            throw new DataException(
              Resources.ErrorReadingValueException +
              " " + fieldName, ex);
          }
        }
      }
      return result;
    }
    #endregion
  }
}