package sac.pessoa.fisica.jdbc;

/**
 * Projeto: SAC
 *
 * Tipo: DBPessoaFisica
 *
 */
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Iterator;
import java.util.StringTokenizer;

import sac.doenca.Doenca;
import sac.doenca.RepositorioDoenca;
import sac.doenca.DBDoenca;

import sac.persistencia.PoolConexoes;
import sac.persistencia.CacheObjetos;
import sac.persistencia.OIDFactory;
import sac.persistencia.OID;
import sac.persistencia.PersistenceException;
import sac.pessoa.PessoaNaoCadastradaException;
import sac.pessoa.endereco.RepositorioEndereco;
import sac.pessoa.endereco.jdbc.DBEndereco;
import sac.pessoa.fisica.FiltroBuscaPessoaFisica;
import sac.pessoa.fisica.PessoaFisica;
import sac.pessoa.fisica.RepositorioPessoaFisica;
import sac.pessoa.util.jdbc.PersistenteFactory;
import sac.pessoa.util.jdbc.PersistenteFactory_OID;
import sac.pessoa.util.jdbc.DBRepository_Abstract;
import sac.pessoa.endereco.Endereco;

/**
 * Classe que implementa o repositório e promove acesso dos elementos ao BD.
 *
 * @see sac.persistence.ConnectionPool;
 * @see sac.persistence.ObjectCache;
 * @see sac.persistence.ObjectFactory;
 * @see sac.persistence.OID;
 * @see sac.persistence.PersistenceException;
 * @see sac.pessoa.PessoaNaoCadastradaException;
 *
 * @author Edmo Sergio
 * @author Milena Rocha
 * @author Rodrigo Teixeira
 */

public class DBPessoaFisica extends DBRepository_Abstract implements RepositorioPessoaFisica {
  // instancia unica do repositorio
  private static DBPessoaFisica instancia;

   //Atributos para controle de acesso ao BD.
   private PoolConexoes pool;
   private CacheObjetos cache;
   private OIDFactory oidfactory;


   private RepositorioEndereco repositorioEndereco;
   private RepositorioDoenca repositorioDoenca;
   //Fabricas de objetos obtidos do banco
    private PersistenteFactory personFactory;

   //Atributos para geraçao de SQLs para acesso ao BD.
   private final String preparedSelectPersonSql =
            "select *  from pessoa_fisica "+
            " where cd_pf = ?";
   private final String preparedSelectPersonIdWithCpfSql =
            "select cd_pf as id from pessoa_fisica where tx_cpf = ? ";
   private final String preparedSelectPersonIdWithNameSql =
            "select cd_pf as id from pessoa_fisica where nm_pf like ?";
   private final String preparedSelectPersonIdWithTypeServiceSql =
            "select cd_pf as id from rel_pf_servico, tipo_servico "+
            " where "+
            "rel_pf_servico.cd_tipo_servico = tipo_servico.cd_tipo_servico "+
            " and nm_tipo_servico = ?";
   private final String preparedSelectPersonIdWithDeceaseSql =
            "select cd_pf as id from rel_pf_doenca, doenca "+
            " where rel_pf_doenca.cd_doenca = doenca.cd_doenca "+
            " and nm_doenca = ?";

   private final String preparedInsertPersonSql =
            "insert into pessoa_fisica (cd_pf , tx_cpf , nm_pf, "+
            "cd_endereco, ds_atividades, tx_senha, ch_identificador) "+
            "values (?, ?, ?, ?, ?, ?, 'f')";

   private final String preparedAteraPerson  =
            "update pessoa_fisica set nm_pf= ?, cd_endereco = ? , "+
            "ds_atividades = ?, tx_senha = ? where cd_pf = ?";


   private final String preparedRemoveCurriculum =
            "delete from curriculum where cd_pf = ?";
   private final String preparedRemovePessoa =
            "delete from pessoa_fisica where cd_pf = ?";
   // rel_pf_tipo_servico
   private final String preparedSelectNameTypeServiceSql =
            "select cd_tipo_servico "+
            "from tipo_servico where nm_tipo_servico  = ? ";
   private final String preparedInsertTipoServico =
            "insert into rel_pf_servico (cd_pf, cd_tipo_servico) " +
			"values (?,?)" ;
   private final String preparedRemovePessoaRelPesTipServ =
            "delete from rel_pf_servico where cd_pf = ?";
   // rel_pf_doenca
   private final String preparedInsertDoenca =
            "insert into rel_pf_doenca (cd_pf,cd_doenca) values (?,?)";
   private final String preparedRemovePessoaRelPesDoenca =
            "delete from rel_pf_doenca where cd_pf = ?";

   /**
   * Construtos da classe
   */
   public DBPessoaFisica() throws PersistenceException{
	  pool = PoolConexoes.getInstancia();
	  cache = CacheObjetos.getInstancia();
          oidfactory = OIDFactory.getInstancia();

          repositorioEndereco = DBEndereco.getInstancia();
          repositorioDoenca = DBDoenca.getInstancia();

	  personFactory =  PersistenteFactory_Person.getInstancia();
   }

    public static synchronized DBPessoaFisica getInstancia()
        throws PersistenceException {

        if (instancia == null) {
            instancia = new DBPessoaFisica();
        }
        return instancia;
    }

   /**
    * Metodo que altera uma pessoa no bd.
    * para isso tb precisa altera os relacionamentos
    */
    public void alterar(PessoaFisica pessoa)
        throws PersistenceException, PessoaNaoCadastradaException {
        // Este metodo altera as informacoes de Pessoa Fisica alocada na tabela
        // pessoa fisica e nas tabelas que fazem relacionamento com ela
        // deve-se posteriormente remover o Endereco anterior utilizado pela
        // Pessoa Fisica caso este nao seja usado por mais ninguem.
        PreparedStatement preAlterPessoaFisica =  null;
        PreparedStatement preRemoveRelDoenca =  null;
        PreparedStatement preRemoveRelTipoServico =  null;

        Connection con = null;

        try{
            con = getConnection();

            preAlterPessoaFisica = createPreparedStatement(con,
                preparedAteraPerson);
            preRemoveRelDoenca = createPreparedStatement(con,
                preparedRemovePessoaRelPesDoenca);
            preRemoveRelTipoServico = createPreparedStatement(con,
                preparedRemovePessoaRelPesTipServ);
            long id = -1;
            if ( pessoa.getId() == null){
                OID oid = selectPersonOID( pessoa.getCpf());
                pessoa.setId(oid);
            }
            id = pessoa.getId().getLongValue();

            Endereco end = pessoa.getEndereco();
            repositorioEndereco.inserir ( end, con);

            preAlterPessoaFisica.setString(1,pessoa.getNome());
            preAlterPessoaFisica.setLong(2, end.getId().getLongValue());
            preAlterPessoaFisica.setString(3, pessoa.getDescricaoAtividades());
            preAlterPessoaFisica.setString(4, pessoa.getSenha());
            preAlterPessoaFisica.setLong(5, id);
            preAlterPessoaFisica.executeUpdate();

            preRemoveRelDoenca.setLong(1,id);
            preRemoveRelTipoServico.setLong(1,id);
            preRemoveRelDoenca.executeUpdate();
            preRemoveRelTipoServico.executeUpdate();

            insertRelDoenca ( id,  pessoa.getDoencas(), con);
            insertRelTipoServico(id, pessoa.getTiposServicos(),con);

            con.commit();
            cache.inserirObjeto( pessoa.getId(), pessoa);
        }catch(SQLException e){
            throw new PersistenceException ("Problemas no BD.", e);
        }finally{
            closeStatement( preAlterPessoaFisica);
            closeStatement( preRemoveRelDoenca);
            closeStatement( preRemoveRelTipoServico);
            freeConnection(con);
        }
   }

    private OID selectPersonOID (String cpf)
        throws PersistenceException, PessoaNaoCadastradaException{
      Connection con = getConnection();
      PreparedStatement  prepPerson = null;
      try {
            // preapara busca por pessoa
            prepPerson = createPreparedStatement(con, preparedSelectPersonIdWithCpfSql);
            prepPerson.setString ( 1, cpf );

            OID oid =  (OID) selectObjectFromBd (prepPerson, getCodeFactory());
            if ( oid == null) {
                throw new PessoaNaoCadastradaException ("Nao existe pessoa com cpf ="+
                    cpf+" cadastrado no sistema.");
            }
            return oid;
      } catch (SQLException sqle){
              throw new PersistenceException ("Problemas no BD.");
      } finally {
              closeStatement (prepPerson);
              freeConnection(con);
      }
   }
   /**
   * Método para consultar pessoa física no BD através de um filtro e o do sql
   * relacionado a ela.
   *
   * @param PreparedStatementSql    <code>String</code> da sql a ser usada para
   *    criar um PreparedStatement que tenha como paramentro o filtro passado
   *    como paramentro.
   * @param filtro    <code>String</code> do filtro a ser utilizado.
   * @param message    <code>String</code> utilizada para ser levantada em caso
   *    de erro.
   *
   * @return List  Lista de Pessoa fisica.
   *
   */
    private List consultaPessoa(String strPreparedStatementSql, String filtro,
        int startIndex, int size) throws PersistenceException{

        Connection con = getConnection();
        PreparedStatement  prepPerson = null;
        PreparedStatement  preCodigo = null;
        try {
            prepPerson = createPreparedStatement(con, preparedSelectPersonSql);
            preCodigo = createPreparedStatement(con, strPreparedStatementSql);
            preCodigo.setString(1, filtro);
            List lstcodigos = selectListFromBD(preCodigo, getCodeFactory(),
                startIndex, size);
            return selectList(lstcodigos, prepPerson, personFactory);
        } catch (SQLException sqle){
            throw new PersistenceException ("Problemas no BD.", sqle);
        } finally {
            closeStatement (prepPerson);
            closeStatement (preCodigo);
            freeConnection(con);
        }
    }

   /**
   * Método para verificar se determinada pessoa está cadastrasda no sistema.
   *
   * @param pessoa    Pessoa a ser pesquisada.
   *
   * @return boolean  Indicador de existencia da pessoa física.
   */
    public boolean existe(PessoaFisica pessoa) throws PersistenceException{
        try {
            selectPersonOID( pessoa.getCpf());
            return true;
        } catch (PessoaNaoCadastradaException pessoae){
            return false;
        }
    }

   /**
   @roseuid 39EBB3DF0302
   */
    public void inserir(PessoaFisica pessoa) throws PersistenceException {
        Connection con = getConnection();
        PreparedStatement pre = null;
        try {
            pre = createPreparedStatement(con, preparedInsertPersonSql);
            OID oid = oidfactory.novoOID();
            pessoa.setId(oid);
            long idPerson = oid.getLongValue();
            Endereco end = pessoa.getEndereco();
            repositorioEndereco.inserir(end, con);

            pre.setLong(1, idPerson);
            pre.setString(2, pessoa.getCpf());
            pre.setString(3, pessoa.getNome());
            pre.setLong(4, end.getId().getLongValue());
            pre.setString(5, pessoa.getDescricaoAtividades());
            pre.setString(6, pessoa.getSenha());
            pre.executeUpdate();

            insertRelTipoServico(idPerson, pessoa.getTiposServicos(), con);
            insertRelDoenca(idPerson, pessoa.getDoencas(), con);

            con.commit();
        } catch (SQLException sqle){
            try {
            con.rollback();
            } catch (Exception ex) {
                throw new PersistenceException ("Nao foi possivel realizar "+
                    "RollBack. ", ex);
            }
            throw new PersistenceException ("Erro no Banco de Dados. ", sqle);
        } catch (PersistenceException pe){
            try {
            con.rollback();
            } catch (Exception ex) {
                throw new PersistenceException ("Nao foi possivel realizar "+
                "RollBack. ", pe);
            }
            throw pe;
        } finally {
            closeStatement(pre);
            freeConnection(con);
        }
   }

    private void insertRelDoenca ( long codigoPessoa, List listaDoencas,
        Connection con) throws PersistenceException,SQLException{

        PreparedStatement preRelacionamento = createPreparedStatement(con,
            preparedInsertDoenca);
        try{
            for (int count = 0; count < listaDoencas.size();count++){
                Doenca doenca = (Doenca)listaDoencas.get(count);
                OID oidDoenca = doenca.getId();
                if ( oidDoenca == null){
                    List lstDoencas =
                        repositorioDoenca.procurarPeloNomeParecido(doenca.getNome(), 0, 100);
                    if (lstDoencas.size() <= 0 ){
                        throw new PersistenceException("Nao existe Doenca :"+
                            doenca.getNome());
                  }
                  oidDoenca  = ((Doenca) lstDoencas.get(0)).getId();
                }

                preRelacionamento.clearParameters();
                preRelacionamento.setLong(1, codigoPessoa);
                preRelacionamento.setLong(2, oidDoenca.getLongValue());
                preRelacionamento.executeUpdate();
            }
        }catch(SQLException e){
            throw new PersistenceException("Problema no Banco");
        }finally{
            closeStatement(preRelacionamento);
        }
    }


    private void insertRelTipoServico ( long codigoPessoa, List listaTipoServico,
        Connection con) throws PersistenceException,SQLException{

        PreparedStatement preCodigo = createPreparedStatement(con,
            preparedSelectNameTypeServiceSql);
        PreparedStatement preRelacionamento = createPreparedStatement(con,
            preparedInsertTipoServico);
        try{
            for (int count = 0; count < listaTipoServico.size();count++){
                preCodigo.clearParameters();
                String nomeTipoServico = (String) listaTipoServico.get(count);
                preCodigo.setString(1, nomeTipoServico);
                ResultSet rsCodigo = preCodigo.executeQuery();
                if(rsCodigo.next()){
                    preRelacionamento.clearParameters();
                    long codigoTipoServico = rsCodigo.getLong("cd_tipo_servico");
                    preRelacionamento.setLong(1, codigoPessoa);
                    preRelacionamento.setLong(2, codigoTipoServico);
                    preRelacionamento.executeUpdate();
                }else {
                    throw new PersistenceException("Nao existe Tipo Servico :"+
                        nomeTipoServico);
                }
                rsCodigo.close();
            }
        }catch(SQLException e){
            throw new PersistenceException("Problema no Banco", e);
        }finally{
            closeStatement(preCodigo);
            closeStatement(preRelacionamento);
        }
   }

    /**
     * Método para consultar pessoa física no BD através do CPF.
     *
     * @param cpf   String contendo CPF a se procurado.
     *
     * @return PessoaFisica  Pessoa fisica correspondente ao CPF procurado
     *
     */
    public PessoaFisica procurar(String cpf) throws PersistenceException,
    PessoaNaoCadastradaException{
        OID oid = selectPersonOID(cpf);
        return procurar(oid);
   }
   /**
   @roseuid 39EBB3E002BC
   */
   public PessoaFisica procurar(OID oid)
        throws PersistenceException, PessoaNaoCadastradaException{

        Connection con = getConnection();
        PreparedStatement  prepPerson = null;
        try {
            // preapara busca por pessoa
            prepPerson = createPreparedStatement(con, preparedSelectPersonSql);
            prepPerson.setLong ( 1, oid.getLongValue());
            PessoaFisica pessoa = (PessoaFisica) selectObject( oid, prepPerson,
                            personFactory ) ;
            if (pessoa == null ){
                throw new PessoaNaoCadastradaException("Pessoa não cadastrada, "+
                    "com code = "+ oid.getLongValue());
            }
            return pessoa;
        } catch (SQLException sqle){
                throw new PersistenceException ("Problemas no BD.", sqle);
        } finally {
                closeStatement (prepPerson);
                freeConnection(con);
        }
   }
   /**
   * Método para consultar pessoa física no BD através do tipo de serviço.
   *
   * @param servico   String contendo tipo de servico a ser procurado.
   *
   * @return PessoaFisica  Pessoa fisica correspondente ao nome procurado
   *
   */
   public List procurar(FiltroBuscaPessoaFisica filtroBusca, int startIndex,
        int size) throws PersistenceException{
        String  strPreCodigo= null;
        String filtro = filtroBusca.getFiltro().trim();
        switch ( filtroBusca.getTipoFiltro()){
                case FiltroBuscaPessoaFisica.consultaPorCpf :
                        strPreCodigo = preparedSelectPersonIdWithCpfSql;
                        break;
                case FiltroBuscaPessoaFisica.consultaPorNome :
                        strPreCodigo = preparedSelectPersonIdWithNameSql;
                        StringTokenizer st = new StringTokenizer (filtro);
                        StringBuffer sb = new StringBuffer();
                        boolean alredywhite;
                        if (st.hasMoreTokens()) {
                          sb.append('%');
                        }
                        while ( st.hasMoreTokens()){
                            sb.append(st.nextToken());
                            sb.append('%');
                        }
                        filtro = sb.toString();
                        break;
                case FiltroBuscaPessoaFisica.consultaPorTipoServico:
                        strPreCodigo = preparedSelectPersonIdWithTypeServiceSql;
                        break;
                case FiltroBuscaPessoaFisica.consultaPorDoenca:
                        strPreCodigo = preparedSelectPersonIdWithDeceaseSql;
                        break;

        }
        System.out.println("Filtro = " + filtro);
        return consultaPessoa(strPreCodigo, filtro, startIndex, size);
   }
   /**
   @roseuid 39EBB3E002BC
   */
    public PessoaFisica procurar(PessoaFisica pessoa)
        throws PersistenceException, PessoaNaoCadastradaException {

        Connection con = getConnection();
        PreparedStatement  prepPerson = null;
        try {
            // preapara busca por pessoa
            prepPerson = createPreparedStatement(con, preparedSelectPersonSql);
            OID oid = pessoa.getId();
            if ( oid == null) {
                oid = selectPersonOID( pessoa.getCpf());
            }
            prepPerson.setLong( 1, oid.getLongValue());
            pessoa = (PessoaFisica)  selectObject (oid, prepPerson,
                personFactory);
            if (pessoa == null){
                throw new PersistenceException("Incosistencia no Banco "+
                    " oid inexistente = " + oid.getLongValue());
            }
            return pessoa;
        } catch (SQLException sqle){
                throw new PersistenceException ("Problemas no BD.", sqle);
        } finally {
                closeStatement (prepPerson);
                freeConnection(con);
        }
    }
   /**
	* Método usado para remover uma pessoa fisica do bd
	*/
    public void remover(PessoaFisica pessoa)
        throws PersistenceException, PessoaNaoCadastradaException {

        // Este metodo remove as informacoes de Pessoa Fisica alocada na tabela
        // pessoa fisica e nas tabelas que fazem relacionamento com ela
        // deve-se posteriormente remover o Endereco utilizado pela Pessoa Fisica
        // caso este nao seja usado por mais ninguem.
        PreparedStatement preRemoverPessoaFisica =  null;
        PreparedStatement preRemoveRelDoenca =  null;
        PreparedStatement preRemoveCurriculo =  null;
        PreparedStatement preRemoveRelTipoServico =  null;

        Connection con = getConnection();
	    try{
            preRemoverPessoaFisica = createPreparedStatement(con,
                preparedRemovePessoa);
            preRemoveCurriculo = createPreparedStatement(con,
                preparedRemoveCurriculum);
            preRemoveRelDoenca = createPreparedStatement(con,
                preparedRemovePessoaRelPesTipServ);
            preRemoveRelTipoServico = createPreparedStatement(con,
                preparedRemovePessoaRelPesDoenca);

            long id = -1;
            if ( pessoa.getId() == null){
                OID oid = selectPersonOID(pessoa.getCpf());
                pessoa.setId(oid);
            }
            id = pessoa.getId().getLongValue();

            preRemoveCurriculo.setLong(1,id);
            preRemoveRelDoenca.setLong(1,id);
            preRemoveRelTipoServico.setLong(1,id);
            preRemoverPessoaFisica.setLong(1,id);

            preRemoveCurriculo.executeUpdate();
            preRemoveRelDoenca.executeUpdate();
            preRemoveRelTipoServico.executeUpdate();
            preRemoverPessoaFisica.executeUpdate();

            con.commit();

            cache.invalidarObjeto(new OID (id));

        }catch(SQLException e){
            try {
                con.rollback();
            } catch (SQLException ex) {
                throw new PersistenceException ("Nao foi dar possível dar "+
                    "rolback.", e);
            }
            throw new PersistenceException ("Problemas no BD.", e);
        }finally{
            closeStatement(preRemoveCurriculo);
            closeStatement(preRemoveRelDoenca);
            closeStatement(preRemoveRelTipoServico);
            closeStatement(preRemoverPessoaFisica);
            freeConnection(con);
        }
   }



}
