package sac.pessoa.util.jdbc;


import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
import java.sql.ResultSet;
import java.sql.Connection;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import sac.persistencia.OID;
import sac.persistencia.PoolConexoes;
import sac.persistencia.CacheObjetos;
import sac.persistencia.PersistenceException;
import sac.util.PersistenteObject;


public class DBRepository_Abstract {
    private PoolConexoes pool;
    private CacheObjetos cache;
    private PersistenteFactory codeFactory;

    public final int ALL_RESPONSES = -1;

    public DBRepository_Abstract () throws PersistenceException{
        pool = PoolConexoes.getInstancia();
        cache = CacheObjetos.getInstancia();

        codeFactory = PersistenteFactory_OID.getInstancia();
    }

    protected PersistenteFactory getCodeFactory(){
        return codeFactory;
    }

   /**
   * Método para selecionar uma lista de objetos do banco realizando inicialmente
   * uma por seus codigos no banco, e a partir deles buscando o objeto.
   *
   * @param codeStatement   Statement utilizado para buscar os codigos dos objetos.
   * @param codeFactory   FactoryObjectFromBD utilizado para construir os objetos buscados.
   * @param codeQueryMessage    String com menssagem a ser retornada em caso de erro,
   *                            na busca pelos codigos.
   * @param objectStatement   Statement utilizado para buscar dos objetos consultados.
   * @param objectFactory   FactoryObjectFromBD utilizado para construir os objetos buscados.
   * @param objectQueryMessage   String com menssagem a ser retornada em caso de erro,
   *                             na busca pelos objetos a serem construidos.
   *
   * @return Object   Objeto encontrado na busca.
   */
    protected List selectList ( List lstOID, PreparedStatement objectStatement,
        PersistenteFactory objectFactory)
        throws PersistenceException{

        try {
            List resposta = new ArrayList();
            for (Iterator ite = lstOID.iterator(); ite.hasNext();) {
                objectStatement.clearParameters();
                OID oid = (OID) ite.next();
                objectStatement.setLong(1, oid.getLongValue());

                Object obj = selectObject( oid, objectStatement,
                        objectFactory) ;
                resposta.add(obj);
            }
            return resposta;
        } catch (SQLException sqle){
            throw new PersistenceException ("Problemas no BD.", sqle);
        } finally {
            closeStatement(objectStatement);
        }
   }


   /**
   * Método para selecionar objetos para retorno das pesquisas executadas no BD.
   *
   * @param objectId  OID do objeto do BD.
   * @param pre       PreparedStatment com Sql a ser pesquisada no BD.
   * @param factory   Tipo do objeto a ser retornado pela consulta no BD.
   * @param message   String com menssagem a ser retornada em caso de erro.
   *
   * @return Object   Objeto encontrado na busca.
   */
    protected Object selectObject( OID objectId, PreparedStatement pre,
            PersistenteFactory factory)
            throws PersistenceException{
        Object resposta = null;
        if (objectId != null){
            resposta = cache.pegarObjeto(objectId);
        }
        if (resposta == null){
              resposta = selectObjectFromBd(  pre, factory);
        }
        return resposta;
    }

    public List selectListFromBD (PreparedStatement pre,
        PersistenteFactory factory) throws PersistenceException {
        return selectListFromBD ( pre, factory, 0, ALL_RESPONSES);
    }

    public List selectListFromBD (PreparedStatement pre,
        PersistenteFactory factory,  int startIndex, int size )
        throws PersistenceException {

        List resposta = (size == ALL_RESPONSES) ? new ArrayList() : new ArrayList(size);

        ResultSet rs = null;
        try {
            rs = pre.executeQuery();
            if ( startIndex != 0 ) {
              rs.absolute(startIndex);
            }
            Object obj = null;
            int count = 0;
            while ( (obj =  factory.produce (pre.getConnection(), rs)) != null &&
                   (size == ALL_RESPONSES ||  count < size) ) {
                resposta.add( obj);
                count++;
            }
        } catch (SQLException sqle){
           throw new PersistenceException ("Houve problemas no Banco.", sqle);
        } finally{
            try {
                if (rs != null) rs.close();
            } catch (SQLException sqle){
                throw new PersistenceException ("Houve Problemas no BD." , sqle);
            }
        }
        return resposta;
    }

   /**
   * Método para selecionar objetos para retorno das pesquisas executadas no BD.
   *
   * @param objectId  OID do objeto do BD.
   * @param pre       PreparedStatment com Sql a ser pesquisada no BD.
   * @param factory   Tipo do objeto a ser retornado pela consulta no BD.
   * @param message   String com menssagem a ser retornada em caso de erro.
   *
   * @return Object   Objeto encontrado na busca.
   */
    protected Object selectObjectFromBd( PreparedStatement pre,
                   PersistenteFactory factory) throws PersistenceException{
        Object resposta = null;
        ResultSet rs = null;
        try {
           rs = pre.executeQuery();

           resposta =  factory.produce (pre.getConnection(), rs);
        } catch (SQLException sqle){
           throw new PersistenceException ("Houve problemas no Banco.", sqle);
        } finally{
            try {
                if (rs != null) rs.close();
            } catch (SQLException sqle){
                throw new PersistenceException ("Problemas no BD." , sqle);
            }
        }
        inserirObjetoCache (resposta);
        return resposta;
    }

    private void inserirObjetoCache (List lst ) {
        for (int i =0; i < lst.size(); i++) {
          inserirObjetoCache (lst.get(i));
        }
    }

    private void inserirObjetoCache (Object obj ) {
        if ( obj instanceof PersistenteObject) {
            cache.inserirObjeto( ((PersistenteObject) obj).getId(), obj);
        }
    }

   /**
   * Método para gerar conexão com o BD.
   *
   * @return Connection   Conexão aberta para o BD.
   */
   protected Connection getConnection() throws PersistenceException{
      try {
        Connection con = pool.obterConexao();

        con.setAutoCommit(false);
        return con;
      } catch (SQLException sqle) {
        throw new PersistenceException ("Não foi possivel criar um conecção com o Banco.", sqle);
      }
   }

   /**
   * Método para criar um PeparedStatement para uma dada conexao.
   *
   * @param Connection   Conexão aberta para o BD.
   * @param String       sql para geraro statement.
   * @return PreparedStatement  Statement gerado a partir do sql dado.
   */
    protected PreparedStatement createPreparedStatement(Connection con, String prepearedSql) throws PersistenceException{
        try {

            return con.prepareStatement( prepearedSql,
                                     ResultSet.TYPE_SCROLL_INSENSITIVE,
                                     ResultSet.CONCUR_READ_ONLY);
        } catch (SQLException sqle ){
            throw new PersistenceException ("Não foi criar o preparedStatement para a sql = "+ prepearedSql, sqle);
        }
    }

   /**
   * Método para fachar um Statement.
   *
   * @param statement  <code>Statement</code> a ser fechado.
   */
    protected void closeStatement(Statement statement) throws PersistenceException{
        try {
            if (statement !=  null){
                statement.close();
            }
        } catch (SQLException sqle ){
            throw new PersistenceException ("Não foi possivel fechar o Statement.", sqle);
        }
    }

   /**
   * Método para fachar um Statement.
   *
   * @param statement  <code>Statement</code> a ser fechado.
   */
    protected void freeConnection(Connection con) throws PersistenceException{
        try {
            if (con !=  null){
                con.commit();
                pool.liberarConexao(con);
            }
        } catch (SQLException sqle ){
            throw new PersistenceException ("Não foi possivel liberar a conexao.", sqle);
        }
    }

}