#undef GENERICS
//#define GENERICS
//#if NET_2_0

using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Reflection;
#if GENERICS
using System.Collections.Generic;
#endif


namespace FileHelpers.Mapping
{

	/// <summary>
    /// <para>A class to provide Record-DataTable operations.</para>
    /// <para>(BETA QUALITY, use it at your own risk :P, API can change in future version)</para>
	/// </summary>
#if ! GENERICS
    public sealed class DataMapper
#else
    /// <typeparam name="T">The record Type</typeparam>
    public sealed class DataMapper<T>
#endif
    {
		RecordInfo mRecordInfo;
		
#if ! GENERICS
        /// <summary>
        /// Create a new Mapping for the record Type 't'.
        /// </summary>
        /// <param name="recordType">The record class.</param>
        public DataMapper(Type recordType)
        {
            mRecordInfo = new RecordInfo(recordType);
        }

#else
        /// <summary>
		/// Create a new Mapping for the record Type 't'.
		/// </summary>
		/// <param name="t">The record class.</param>
        public DataMapper()
		{
			mRecordInfo = new RecordInfo(typeof(T));
		}

#endif
        private int mInitialColumnOffset = 0;

        /// <summary>
        /// Indicates the number of columns to discard in the result.
        /// </summary>
        public int InitialColumnOffset
        {
            get { return mInitialColumnOffset; }
            set { mInitialColumnOffset = value; }
        }

		/// <summary>
		/// Add a new mapping between column at <paramref>columnIndex</paramref> and the fieldName with the specified <paramref>fieldName</paramref> name.
		/// </summary>
		/// <param name="columnIndex">The index in the Datatable</param>
		/// <param name="fieldName">The name of a fieldName in the Record Class</param>
		public void AddMapping(int columnIndex, string fieldName)
		{
			MappingInfo map = new MappingInfo(mRecordInfo.mRecordType, fieldName);
			map.mDataColumnIndex = columnIndex + mInitialColumnOffset;
			mMappings.Add(map);
		}
		
		/// <summary>
		/// Add a new mapping between column with <paramref>columnName</paramref> and the fieldName with the specified <paramref>fieldName</paramref> name.
		/// </summary>
		/// <param name="columnName">The name of the Column</param>
		/// <param name="fieldName">The name of a fieldName in the Record Class</param>
		public void AddMapping(string columnName, string fieldName)
		{
			MappingInfo map = new MappingInfo(mRecordInfo.mRecordType, fieldName);
			map.mDataColumnName = columnName;
			mMappings.Add(map);
		}
		
		private ArrayList mMappings  = new ArrayList();
		
		/// <summary>
		/// For each row in the datatable create a record.
		/// </summary>
		/// <param name="dt">The source Datatable</param>
		/// <returns>The mapped records contained in the DataTable</returns>
#if ! GENERICS
		public object[] MapDataTable2Records(DataTable dt)
        {
            ArrayList arr = new ArrayList(dt.Rows.Count);
#else
		public T[] MapDataTable2Records(DataTable dt)
        {
			List<T> arr = new List<T>(dt.Rows.Count);
#endif

            mMappings.TrimToSize();
            foreach (DataRow row in dt.Rows)
			{
				arr.Add(MapRow2Record(row));
			}
			
#if ! GENERICS
			return (object[]) arr.ToArray(mRecordInfo.mRecordType);
#else
			return arr.ToArray();
#endif
        }


		/// <summary>
		/// Map a source row to a record.
		/// </summary>
		/// <param name="dr">The source DataRow</param>
		/// <returns>The mapped record containing the values of the DataRow</returns>
		[EditorBrowsable(EditorBrowsableState.Advanced)]
#if ! GENERICS
		public object MapRow2Record(DataRow dr)
        {
            object record = mRecordInfo.CreateRecordObject();
#else
		public T MapRow2Record(DataRow dr)
        {
			T record = (T) mRecordInfo.CreateRecordObject();
#endif
			
			for(int i = 0; i < mMappings.Count; i++)
			{
				((MappingInfo) mMappings[i]).DataToField(dr, record);
			}

			return record;
		}

		/// <summary>
		/// Map a source row to a record.
		/// </summary>
		/// <param name="dr">The already opened DataReader</param>
		/// <returns>The mapped record containing the values of the DataReader</returns>
		[EditorBrowsable(EditorBrowsableState.Advanced)]
#if ! GENERICS
		public object MapRow2Record(IDataReader dr)
		{
            object record = mRecordInfo.CreateRecordObject();
#else
		public T MapRow2Record(IDataReader dr)
		{
            T record = (T) mRecordInfo.CreateRecordObject();
#endif
            //TypedReference t = TypedReference.MakeTypedReference(record, new FieldInfo[]) null);
				
			for(int i = 0; i < mMappings.Count; i++)
			{
				((MappingInfo) mMappings[i]).DataToField(dr, record);
			}

			return record;
		}

		/// <summary>
		/// Create an automatic mapping for each column in the dt and each record field 
		/// (the mapping is made by Index)
		/// </summary>
		/// <param name="dt">The source Datatable</param>
		/// <returns>The mapped records contained in the DataTable</returns>
#if ! GENERICS
        public object[] AutoMapDataTable2RecordsByIndex(DataTable dt)
        {
            ArrayList arr = new ArrayList(dt.Rows.Count);
#else
        public T[] AutoMapDataTable2RecordsByIndex(DataTable dt)
		{
            List<T> arr = new List<T>(dt.Rows.Count);
#endif

            FieldInfo[] fields =
				mRecordInfo.mRecordType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.GetField |
				BindingFlags.Instance | BindingFlags.IgnoreCase);
			

			if (fields.Length > dt.Columns.Count)
				throw new FileHelpersException("The data table has less fields than fields in the Type: " +
					mRecordInfo.mRecordType.Name);
			
			for(int i = 0; i < fields.Length; i++)
			{
				MappingInfo map = new MappingInfo(fields[i]);
				map.mDataColumnIndex = i;
				mMappings.Add(map);
			}
			

			foreach (DataRow row in dt.Rows)
			{
#if ! GENERICS
				object record = mRecordInfo.CreateRecordObject();
#else
				T record = (T) mRecordInfo.CreateRecordObject();
#endif
                //TypedReference t = TypedReference.MakeTypedReference(record, new FieldInfo[]) null);
				
				for(int i = 0; i < mMappings.Count; i++)
				{
					((MappingInfo) mMappings[i]).DataToField(row, record);
				}
				
				arr.Add(record);
			}
			
#if ! GENERICS
            return (object[])arr.ToArray(mRecordInfo.mRecordType);
#else
            return arr.ToArray();
#endif
        }

	
		/// <summary>
		/// For each row in the datatable create a record.
		/// </summary>
		/// <param name="connection">A valid connection (Opened or not)</param>
		/// <param name="selectSql">The Sql statement used to return the records.</param>
		/// <returns>The mapped records contained in the DataTable</returns>
#if ! GENERICS
        public object[] MapDataReader2Records(IDbConnection connection, string selectSql)
        {
            object[] res;
#else
        public T[] MapDataReader2Records(IDbConnection connection, string selectSql)
        {
            T[] res;
#endif
            
            ExHelper.CheckNullParam(connection, "connection");
			ExHelper.CheckNullOrEmpty(selectSql, "selectSql");

			IDbCommand cmd = connection.CreateCommand();
			cmd.CommandText = selectSql;

			IDataReader dr = null;
			bool connectionOpened = false;

			try
			{
				if (connection.State == ConnectionState.Closed)
				{
					connection.Open();
					connectionOpened = true;
				}

				dr = cmd.ExecuteReader(CommandBehavior.SingleResult);
				res = MapDataReader2Records(dr);
			}
			finally
			{
				if (dr != null)
					dr.Close();

				if (connectionOpened)
					connection.Close();
			}

			return res;
		}
	
		/// <summary>
		/// For each row in the datatable create a record.
		/// </summary>
		/// <param name="dr">The source DataReader</param>
		/// <returns>The mapped records contained in the DataTable</returns>
#if ! GENERICS
		public object[] MapDataReader2Records(IDataReader dr)
        {
            ArrayList arr = new ArrayList();
#else
		public T[] MapDataReader2Records(IDataReader dr)
        {
            List<T> arr = new List<T>();
#endif

            ExHelper.CheckNullParam(dr, "dr");

			mMappings.TrimToSize();
			

			bool hasRows = true;
			if (dr is System.Data.SqlClient.SqlDataReader)
				hasRows = ((System.Data.SqlClient.SqlDataReader) dr).HasRows;
			else if (dr is System.Data.OleDb.OleDbDataReader)
				hasRows = ((System.Data.OleDb.OleDbDataReader) dr).HasRows;
			

			if (hasRows)
			{
				while (dr.Read())
				{
					arr.Add(MapRow2Record(dr));
				}
			}
#if ! GENERICS			
            return (object[])arr.ToArray(mRecordInfo.mRecordType);
#else
			return arr.ToArray();
#endif
        }

	
	
	}


}
//#endif