using System;
using System.Data;
using System.Data.Common;
using System.Collections.Generic;
using System.Reflection;

using FourLayer.BusinessEntity;

namespace FourLayer.DataAccessObject
{

   /// <summary>
   /// Summary description for AbstractDAO
   /// </summary>
   public abstract class AbstractDAO<T> where T : AbstractEntity
   {
      /// <summary>
      /// Defines the basic select statement for retrieving data 
      /// without criteria.
      /// 
      /// This property is implemented by the concrete subclasses.
      /// </summary>
      protected abstract string SelectStatement
      {
         get;
      }

      /// <summary>
      /// Defines the basic select statement for retrieving data 
      /// without criteria.
      /// 
      /// This property is implemented by the concrete subclasses.
      /// </summary>
      protected abstract string PrimaryKeyName
      {
         get;
      }

      /// <summary>
      /// Abstract template method for filling an entity object from a data record
      /// </summary>
      /// <param name="dr">Data reader containing data</param>
      /// <returns></returns>
      protected abstract T CreateAndFillEntity(DbDataReader reader);

      /// <summary>
      /// Adapts the passed in strongly-typed collection to a DataTable 
      /// </summary>
      public abstract DataTable AdaptCollectionToDataTable(EntityCollection<T> collection);

      /// <summary>
      /// Updates the data source for this entity
      /// </summary>
      public abstract void Update(T entity);

      /// <summary>
      /// Inserts this entity into the data source
      /// </summary>
      public abstract void Insert(T entity);

      /// <summary>
      /// Deletes this entity from the data source
      /// </summary>
      public abstract void Delete(T entity);

      /// <summary>
      /// Should the GetAll method cache the collection?
      /// </summary>
      public abstract bool IsGetAllCached
      {
         get;
      }

      /// <summary>
      /// The key name to be used by this data layer for caching
      /// </summary>
      public abstract string CacheName
      {
         get;
      }

      /// <summary>
      /// Returns all the records for this entity
      /// </summary>
      public EntityCollection<T> GetAll()
      {
         EntityCollection<T> collection = null;
         if (IsGetAllCached)
            collection = DataCache<T>.RetrieveCollection(CacheName);
         if (collection == null)
         {
            collection = GetCollection(SelectStatement, CommandType.Text, null);
            if (IsGetAllCached)
               DataCache<T>.AddCollection(CacheName, collection);
         }
         return collection;
      }


      /// <summary>
      /// Returns an entity for this key
      /// </summary>
      public T GetByKey(object key)
      {
         EntityCollection<T> collection = GetByCriteria(PrimaryKeyName, "=", key);

         if (collection == null)
            return null;

         if (collection.Count == 0)
            return null;

         return collection[0];
      }

      /// <summary>
      /// Returns entity collection for this criteria
      /// </summary>
      public EntityCollection<T> GetByCriteria(string criteriaField, string criteriaOperator, object criteriaValue)
      {
         if (criteriaValue == null)
            return null;

         // set up parameterized query statement
         string sql = SelectStatement + " WHERE " + criteriaField + criteriaOperator + "@condition";

         // construct array of parameters
         DbParameter[] parameters = new DbParameter[] {
			   DatabaseActions.MakeParameter("@condition", criteriaValue)
			};

         EntityCollection<T> collection = GetCollection(sql, CommandType.Text, parameters);

         return collection;
      }

      /// <summary>
      /// Returns a collection filled with data specified by name and parameters
      /// </summary>
      protected EntityCollection<T> GetCollection(string name, CommandType cmdType, DbParameter[] parameters) 
      {
         EntityCollection<T> collection = new EntityCollection<T>();

         using (DbConnection conn = DatabaseActions.Factory.CreateConnection())
         {
            try
            {
               conn.ConnectionString = DatabaseActions.ConnectionSetting.ConnectionString;
               conn.Open();

               DbCommand cmd = DatabaseActions.Factory.CreateCommand();
               cmd.Connection = conn;
               cmd.CommandText = name;
               cmd.CommandType = cmdType;

               // add parameters
               if (parameters != null)
               {
                  foreach (DbParameter p in parameters)
                     cmd.Parameters.Add(p);
               }

               DbDataReader reader = cmd.ExecuteReader();
               if (reader != null)
               {
                  while (reader.Read())
                  {
                     collection.Add(CreateAndFillEntity(reader));
                  }
                  reader.Close();
                  
               }
               conn.Close();
            }
            catch (Exception ex)
            {
               // any errors will be handled by our custom exception handler
               DataAccessExceptionHandler.HandleException(ex.Message);
            }
         }
         return collection;
      }

   }
}
