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

using FourLayer.BusinessEntity;

namespace FourLayer.DataAccessObject
{

   /// <summary>
   /// Ogólny opis AbstractDAO
   /// </summary>
   public abstract class AbstractDAO<T> where T : AbstractEntity
   {
      /// <summary>
      /// Definiuje podstawową instrukcję pobierania danych
      /// bez kryteriów.
      /// 
      /// Ta właściwość jest zaimplementowana przez konkretną podklasę.
      /// </summary>
      protected abstract string SelectStatement
      {
         get;
      }

      /// <summary>
      /// Definiuje podstawową instrukcję pobierania danych
      /// bez kryteriów.
      /// 
      /// Ta właściwość jest zaimplementowana przez konkretne podklasy.
      /// </summary>
      protected abstract string PrimaryKeyName
      {
         get;
      }

      /// <summary>
      /// Abstrakcyjna metoda szablonowa do wypełniania obiektu encji na podstawie rekordu danych
      /// </summary>
      /// <param name="dr">Data reader containing data</param>
      /// <returns></returns>
      protected abstract T CreateAndFillEntity(DbDataReader reader);

      /// <summary>
      /// Adaptuje przekazany silnie typizowany zbiór do klasy DataTable 
      /// </summary>
      public abstract DataTable AdaptCollectionToDataTable(EntityCollection<T> collection);

      /// <summary>
      /// Aktualizuje źródło danych dla danej encji
      /// </summary>
      public abstract void Update(T entity);

      /// <summary>
      /// Wstawia encję do źródła danych
      /// </summary>
      public abstract void Insert(T entity);

      /// <summary>
      /// Usuwa encję ze źródła danych
      /// </summary>
      public abstract void Delete(T entity);

      /// <summary>
      /// Czy metoda GetAll powinna buforować zbiór?
      /// </summary>
      public abstract bool IsGetAllCached
      {
         get;
      }

      /// <summary>
      /// Nazwa klucza, która zostanie użyta przez warstwę danych w celu buforowania
      /// </summary>
      public abstract string CacheName
      {
         get;
      }

      /// <summary>
      /// Zwraca wszystkie rekordy danej encji
      /// </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>
      /// Zwraca encję dla danego klucza
      /// </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>
      /// Zwraca zbiór encji na podstawie danego kryterium
      /// </summary>
      public EntityCollection<T> GetByCriteria(string criteriaField, string criteriaOperator, object criteriaValue)
      {
         if (criteriaValue == null)
            return null;

         // utwórz sparametryzowane zapytanie
         string sql = SelectStatement + " WHERE " + criteriaField + criteriaOperator + "@condition";

         // utwórz tablicę parametrów
         DbParameter[] parameters = new DbParameter[] {
			   DatabaseActions.MakeParameter("@condition", criteriaValue)
			};

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

         return collection;
      }

      /// <summary>
      /// Zwraca zbiór wypełniony danymi określonymi przez nazwę i parametry
      /// </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;

               // dodaj parametry
               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)
            {
               // wszystkie błędy będą obsługiwane przez niestandardową procedurę obsługi wyjątków
               TwoLayer.DataAccess.DataAccessExceptionHandler.HandleException(ex.Message);
            }
         }
         return collection;
      }

   }
}
