package sac.pessoa.juridica.jdbc;

/**
 * Projeto: SAC
 *
 * Tipo: DBPessoaJuridica
 *
 */

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.ArrayList;

import sac.exception.ItemNaoCadastradoException;
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.juridica.FiltroBuscaPessoaJuridica;
import sac.pessoa.juridica.OfertaTrabalho;
import sac.pessoa.juridica.PessoaJuridica;
import sac.pessoa.juridica.RepositorioOfertaTrabalho;
import sac.pessoa.juridica.RepositorioPessoaJuridica;
import sac.pessoa.util.jdbc.DBRepository_Abstract;
import sac.pessoa.util.jdbc.PersistenteFactory;
import sac.pessoa.util.jdbc.PersistenteFactory_OID;
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;
 *
 * @author Edmo Sergio
 * @author Milena Rocha
 * @author Rodrigo Teixeira
 */
 public class DBPessoaJuridica extends DBRepository_Abstract
                 implements RepositorioPessoaJuridica {
    // instancia unica do repositorio
    private static DBPessoaJuridica instancia;
    private DBCategoria repositorioCategoria;

    //Atributos para controle de acesso ao BD.
    private PoolConexoes pool;
    private CacheObjetos cache;
    private OIDFactory oidFactory;

    private RepositorioEndereco repositorioEndereco;
    //Fabricas de objetos obtidos do banco
    private PersistenteFactory_PessoaJuridica factoryPessoaJuridica;

    private RepositorioOfertaTrabalho repositorioOferta;

    //Atributos para geraçao de SQLs para acesso ao BD.
    // inseri um endereco
    private final String preparedInsertRelEndereco =
        "insert into rel_pj_endereco (cd_pj, cd_endereco) values ( ?, ?)";

    private final String preparedInsertPersonSql =
        "insert into pessoa_juridica (cd_pj , tx_cnpj , nm_pj, "+
        "ds_servicos,tx_insc_municipal,"+
        "tx_homepage, tx_insc_estadual,tx_senha) values "+
        "(?, ?, ?, ?,?, ?, ?, ?)";
    //Insere uma oferta na Tabela de relacionamento rel_pj_oferta
    private final String preparedInsertRelOferta =
        "insert into rel_pj_oferta (cd_pj, cd_oferta)" +
        "values (?,?)";

    private final String preparedInsertRelPjCat= "insert into rel_pj_categoria (cd_pj,cd_categoria,"+
        "cd_sub_categoria) values (?,?,?)";

    private final String preparedSelectExisteRelSubCat = "select * from rel_categoria_sub_categoria "+
        "where cd_categoria = ? and cd_sub_categoria = ? ";

    // seleciona o codigo de um tipo de servico de acordo com o nome do tipo de servico
    private final String preparedSelectNomeTipoServicoSql =
        "select cd_tipo_servico as id" +
        " from tipo_servico "+
        " where nm_tipo_servico  = ? ";

    private final String preparedSelectTodasPessoaJuridica =
        "select cd_pj, tx_cnpj, nm_pj, ds_servicos, "+
        " tx_insc_municipal, tx_homepage, tx_insc_estadual, tx_senha "+
        " from pessoa_juridica ";

    private final String preparedSelectPessoaCNPJSql =
        "select cd_pj as id from pessoa_juridica where tx_cnpj = ? ";

    private final String preparedRemovePessoa =
        "delete from pessoa_juridica where cd_pj = ?";
    private final String preparedRemoveCategoria =
        "delete from categoria where cd_pj = ?";
    private final String preparedRemovePessoaRelPesOferta =
        "delete from rel_pj_oferta where cd_pj = ?";
    private  final String preparedRemovePessoaRelPesEndereco =
        "delete from rel_pj_endereco where cd_pj = ?";
    private final String preparedRemovePessoaRelPesCategoria =
        "delete from rel_pj_categoria where cd_pj = ?";

    private final String preparedAteraPersonJuridica  =
        "update pessoa_juridica set nm_pj= ?, ds_servicos = ?, "+
        "tx_insc_municipal = ?, tx_insc_estadual = ?, tx_homepage= ?, " +
        "tx_senha = ? where cd_pj = ?";

   /**
    * Construtor da classe
    */
   public DBPessoaJuridica() throws PersistenceException{
      pool = PoolConexoes.getInstancia();
      cache = CacheObjetos.getInstancia();
      oidFactory   = OIDFactory.getInstancia();

      repositorioCategoria = DBCategoria.getInstancia();
      repositorioEndereco = DBEndereco.getInstancia();
      repositorioOferta = DBOfertaTrabalho.getInstancia();

      factoryPessoaJuridica = new PersistenteFactory_PessoaJuridica();
   }

    public static synchronized DBPessoaJuridica getInstancia()
        throws PersistenceException {
        if (instancia == null) {
            instancia = new DBPessoaJuridica();
        }
        return instancia;
    }

    private long insertEndereco( OID oidPessoa, List listaEndereco,
        Connection con) throws PersistenceException{
        long codEndereco = -1;
        PreparedStatement preEndereco = createPreparedStatement(con, preparedInsertRelEndereco);
        try{
            for (int count = 0; count < listaEndereco.size();count++){
                Endereco endereco = (Endereco) listaEndereco.get(count);
                repositorioEndereco.inserir(endereco);
                preEndereco.clearParameters();

                preEndereco.setLong(1, oidPessoa.getLongValue());
                preEndereco.setLong(2, endereco.getId().getLongValue());
                preEndereco.execute();
            }
        }catch(SQLException e){
            throw new PersistenceException ("Problemas ao acessa o Endereco no banco", e) ;
        }finally{
            closeStatement(preEndereco);
        }
        return codEndereco;
    }

    public void insertOferta (OID oidPessoa, List listaOferta)
        throws PersistenceException{

        Connection con = getConnection();
        try {
           insertOferta (oidPessoa, listaOferta, con);
        } finally {
           freeConnection(con);
        }
    }


    /**
     * Método usado para inserir uma oferta que ainda nao estah no bd
     *
     * @param PessoaJuridica objeto do qual vai se obter o codigo
     * @param long codigoPessoa  o codigo dessa pessoa
     * @param List listaOferta
     * @param Connection
     */

    private void insertOferta (OID oidPessoa, List listaOferta, Connection con)
        throws PersistenceException{

        PreparedStatement preRelOferta = createPreparedStatement(con,
            preparedInsertRelOferta);
    	try{
            for (Iterator ite = listaOferta.iterator(); ite.hasNext();) {
                OfertaTrabalho oferta = (OfertaTrabalho) ite.next();
                repositorioOferta.inserir( oferta);

                preRelOferta.clearParameters();
                preRelOferta.setLong (1, oidPessoa.getLongValue());
                preRelOferta.setLong(2, oferta.getId().getLongValue());
                preRelOferta.executeUpdate();
            }

	    }catch(SQLException e){
            throw new PersistenceException("Nao existe esse tipo de servico",e);
	    }finally{
            closeStatement(preRelOferta);
        }
    }

    public void insertCategoria(OID oidPerson, Map mapCategorias, Connection con)
        throws PersistenceException {
        PreparedStatement preExisteRelSubCat =
            createPreparedStatement(con,preparedSelectExisteRelSubCat);
        PreparedStatement preInsert  =
            createPreparedStatement(con,preparedInsertRelPjCat);
        try{
            OID oidCategoria, oidSubCategoria;
            Set chaves_categorias = mapCategorias.keySet();
            Iterator ite_categoria = chaves_categorias.iterator();
            while( ite_categoria.hasNext()){
                String categoria = (String) ite_categoria.next();
                Set subcaterias = (Set) mapCategorias.get(categoria);
                Iterator ite_subcategoria = subcaterias.iterator();
                while (ite_subcategoria.hasNext() ){
                    String subcategoria = (String) ite_subcategoria.next();

                    oidCategoria = repositorioCategoria.consultarOIDCategoria(
                        categoria, con);
                    oidSubCategoria =
                        repositorioCategoria.consultarOIDSubCategoria(
                        subcategoria, con);

                    preExisteRelSubCat.setLong(1, oidCategoria.getLongValue());
                    preExisteRelSubCat.setLong(2, oidSubCategoria.getLongValue());
                    ResultSet rsExiste = preExisteRelSubCat.executeQuery();
                    if(!rsExiste.next()){
                        throw new PersistenceException("Nao existe relacionamento "+
                            "entre a categoria "+categoria+ " e a subCategoria " +
                            subcategoria);
                    }
                    preInsert.clearParameters();
                    preInsert.setLong(1,oidPerson.getLongValue());
                    preInsert.setLong(2,oidCategoria.getLongValue());
                    preInsert.setLong(3,oidSubCategoria.getLongValue());
                    preInsert.executeUpdate();
                }
            }
        }catch(ItemNaoCadastradoException iteme){
            throw new PersistenceException("Categorias invalidas.", iteme);

        }catch(SQLException e){
            throw new PersistenceException("Problema no Banco", e);
        }finally{
          closeStatement( preExisteRelSubCat);
          closeStatement( preInsert);
        }
    }

    public void inserir(PessoaJuridica pessoa) throws PersistenceException{
        Connection con = getConnection();
        PreparedStatement prePessoa = createPreparedStatement(con,
            preparedInsertPersonSql);
        try {
            OID oid = oidFactory.novoOID();

            prePessoa.setLong(1, oid.getLongValue());
            prePessoa.setString(2, pessoa.getCnpj());
            prePessoa.setString(3, pessoa.getNome());
            prePessoa.setString(4, pessoa.getServicosOferecidos());
            prePessoa.setString(5, pessoa.getInscricaoMunicipal());
            prePessoa.setString(6, pessoa.getHomePage());
            prePessoa.setString(7, pessoa.getInscricaoEstadual());
            prePessoa.setString(8, pessoa.getSenha());
            prePessoa.executeUpdate();

            insertEndereco(oid, pessoa.getEnderecos(), con );
            insertOferta(oid ,pessoa.getOferta(), con);
            insertCategoria(oid, pessoa.getCategorias() ,con );
            con.commit();
            pessoa.setId(oid);

        } 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. ", ex);
            }
            throw pe;
        } finally {
            closeStatement(prePessoa);
            freeConnection(con);
        }
   }

   /**
    * Método para remover uma pessoa juridica do bd
    *
    * @param pessoa   Pessoa Juridica a ser removida do BD.
    */
    public void remover(PessoaJuridica pessoa)
        throws PersistenceException, PessoaNaoCadastradaException{

        PreparedStatement preRemoverPessoaJuridica =  null;
        PreparedStatement preRemoveRelCategorias =  null;
        PreparedStatement preRemoveRelEnderecos =  null;
        PreparedStatement preRemoveRelOfertas =  null;

        Connection con = getConnection();
        try{
            preRemoverPessoaJuridica = createPreparedStatement(con,
                preparedRemovePessoa);
            preRemoveRelEnderecos = createPreparedStatement(con,
                preparedRemovePessoaRelPesEndereco);
            preRemoveRelOfertas = createPreparedStatement(con,
                preparedRemovePessoaRelPesOferta);
            preRemoveRelCategorias = createPreparedStatement(con,
                preparedRemovePessoaRelPesCategoria);

            OID oid =  pessoa.getId();
            if ( oid == null){
                oid = selectPersonOID(pessoa.getCnpj());
            }

            preRemoveRelEnderecos.setLong(1,oid.getLongValue());
            preRemoveRelOfertas.setLong(1, oid.getLongValue());
            preRemoverPessoaJuridica.setLong(1, oid.getLongValue());
            preRemoveRelCategorias.setLong(1, oid.getLongValue());

            preRemoveRelEnderecos.executeUpdate();
            preRemoveRelOfertas.executeUpdate();
            preRemoveRelCategorias.executeUpdate();
            preRemoverPessoaJuridica.executeUpdate();

            con.commit();
            cache.invalidarObjeto (oid);
        }catch(SQLException e){
            try {
              con.rollback();
            } catch(SQLException e1){
                throw new PersistenceException ("Problemas no BD, não foi possivel dar roolback", e);
            }
            throw new PersistenceException ("Problemas no BD.", e);
        }finally{
            closeStatement(preRemoveRelEnderecos);
            closeStatement(preRemoveRelOfertas);
            closeStatement(preRemoverPessoaJuridica);
            closeStatement(preRemoveRelCategorias);
            freeConnection(con);
        }
   }

   /**
   @roseuid 39EBB61C0398
   */
    public void alterar(PessoaJuridica 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.
        Connection con = getConnection();
        PreparedStatement preAlterPessoaJuridica = createPreparedStatement(con,preparedAteraPersonJuridica);
        PreparedStatement preRemoveRelEndereco = createPreparedStatement(con,preparedRemovePessoaRelPesEndereco);
        PreparedStatement preRemoveRelOferta = createPreparedStatement(con,preparedRemovePessoaRelPesOferta);
        PreparedStatement preRemoveRelCategoria = createPreparedStatement(con,preparedRemovePessoaRelPesCategoria);

        try {
            OID oid =  pessoa.getId();
            if ( oid == null){
                oid = selectPersonOID(pessoa.getCnpj());
            }

            preAlterPessoaJuridica.setString(1,pessoa.getNome());
            preAlterPessoaJuridica.setString(2, pessoa.getServicosOferecidos());
            preAlterPessoaJuridica.setString(3, pessoa.getInscricaoMunicipal());
            preAlterPessoaJuridica.setString(4, pessoa.getInscricaoEstadual());
            preAlterPessoaJuridica.setString(5, pessoa.getHomePage());
            preAlterPessoaJuridica.setString(6, pessoa.getSenha());
            preAlterPessoaJuridica.setLong(7, oid.getLongValue());
            preAlterPessoaJuridica.executeUpdate();

            preRemoveRelEndereco.setLong(1, oid.getLongValue());
            preRemoveRelEndereco.executeUpdate();

            preRemoveRelOferta.setLong(1, oid.getLongValue());
            preRemoveRelOferta.executeUpdate();

            preRemoveRelCategoria.setLong(1, oid.getLongValue());
            preRemoveRelCategoria.executeUpdate();

            insertCategoria(oid, pessoa.getCategorias(),con);
            insertEndereco (oid, pessoa.getEnderecos(), con);
            insertOferta(oid, pessoa.getOferta() ,con);

            con.commit();
            cache.inserirObjeto( pessoa.getId(), pessoa);
        }catch(SQLException e){
            e.printStackTrace();
            throw new PersistenceException ("Problemas no BD.", e);
        }finally{
            closeStatement( preAlterPessoaJuridica);
            closeStatement( preRemoveRelEndereco);
            closeStatement( preRemoveRelOferta);
            closeStatement( preRemoveRelCategoria);
            freeConnection(con);
        }
   }

    private OID selectPersonOID (String cnpj)
        throws PersistenceException, PessoaNaoCadastradaException{
      Connection con = getConnection();
      PreparedStatement  prepPerson = null;
      try {
            // preapara busca por pessoa
            prepPerson = createPreparedStatement(con, preparedSelectPessoaCNPJSql);
            prepPerson.setString ( 1, cnpj );

            OID oid =  (OID) selectObjectFromBd (prepPerson, getCodeFactory());
            if ( oid == null) {
                throw new PessoaNaoCadastradaException ("Nao existe pessoa com cnpj ="+
                    cnpj+" cadastrado no sistema.");
            }
            return oid;
      } catch (SQLException sqle){
              throw new PersistenceException ("Problemas no BD.");
      } finally {
              closeStatement (prepPerson);
              freeConnection(con);
      }
   }

   /**
   @roseuid 39EBB61B0302
   */
    public boolean existe(PessoaJuridica pessoa) throws PersistenceException{
        try {
            selectPersonOID (pessoa.getCnpj());
            return true;
        } catch (ItemNaoCadastradoException iteme){
            return false;
        }
    }

    /**
     * Método para procurar pessoa juridica por CNPJ.
     *
     * @param cnpj   String contendo cnpj a ser procurado
     *
     * @return PessoaJuridica  pessoa encontrada pela busca.
     */
    public PessoaJuridica procurar(String cnpj)
        throws PersistenceException, PessoaNaoCadastradaException{

        OID oid = selectPersonOID(cnpj);
        return procurar(oid);
    }

   /**
    * Método para procurar pessoa juridica por ID.
    *
    * @param id   Long contendo indice da pessoa procurada no BD.
    *
    * @return PessoaJuridica  pessoa encontrada pela busca.
    */
    public PessoaJuridica procurar(OID oid)
        throws PersistenceException, PessoaNaoCadastradaException {
        Connection con = getConnection();
        PreparedStatement  prepPerson = null;
        try {
            // preapara busca por pessoa
            String sql = preparedSelectTodasPessoaJuridica + " where cd_pj = ? ";
            prepPerson = createPreparedStatement(con, sql);

            prepPerson.setLong ( 1, oid.getLongValue());
            PessoaJuridica pessoa = (PessoaJuridica) selectObject( oid, prepPerson,
                factoryPessoaJuridica);
            if (pessoa == null ){
                throw new PersistenceException("Inconsitencia, oid = "+
                 oid +" de Pessoa Juridica invalido");
            }
            return pessoa;
        } catch (SQLException sqle){
         throw new PersistenceException ("Problemas no BD.");
        } finally {
          closeStatement (prepPerson);
          freeConnection(con);
        }
    }

    /**
     * Método para procurar pessoa juridica por pessoaJuridica.
     * (Usada para ajuda interna de busca no BD)
     *
     * @param pessoa   PessoaJuridica a ser procurada
     *
     * @return PessoaJuridica  pessoa encontrada pela busca.
     */
    public PessoaJuridica procurar(PessoaJuridica pessoa)
        throws PersistenceException, PessoaNaoCadastradaException {
      return procurar(pessoa.getCnpj());
    }

    /**
     * Método para procurar pessoa juridica por filtro.
     *
     * @param filtro   Informando os campos que devem cobrir a pesquisa
     *                 como: nome, pais, cidade...
     *
     * @return List    Lista contendo as pessoas juridicas encontradas na busca.
     */
    public List procurar(FiltroBuscaPessoaJuridica filtro, int startIndex,
        int size) throws PersistenceException{

        String strCategoria = filtro.getCategoria();
        String strSubCategoria = filtro.getSubcategoria();
        String strCidade = filtro.getCidade();
        String strEstado = filtro.getEstado();
        String strPais = filtro.getPais();
        String strNome = filtro.getNome();
        String strConectivo = ( filtro.getConectivo() == filtro.OR)? "or" : "and";

        String sqlCodes = "select DISTINCT pessoa_juridica.cd_pj as id from pessoa_juridica ";
        String sqlPerson  = preparedSelectTodasPessoaJuridica + " where cd_pj = ? ";

        boolean existClausula = false;

        List tabelas = new ArrayList();
        List condicoes = new ArrayList();
        List joins = new ArrayList();

        if ( strNome != null ){
            StringTokenizer st = new StringTokenizer (strNome);
            StringBuffer sb = new StringBuffer();
            boolean alredywhite;
            if (st.hasMoreTokens()) {
              sb.append('%');
            }
            while ( st.hasMoreTokens()){
                sb.append(st.nextToken());
                sb.append('%');
            }
            condicoes.add( "( nm_pj like '"+sb.toString()+"' )");
        }

        if ( strCategoria != null && strSubCategoria != null){
            tabelas.add("categoria, sub_categoria, rel_pj_categoria" );
            condicoes.add( "( nm_categoria = '"+strCategoria+"' and "+
                " nm_sub_categoria = '"+strSubCategoria+"') ");

            joins.add("(rel_pj_categoria.cd_categoria = categoria.cd_categoria and "+
                "rel_pj_categoria.cd_sub_categoria = sub_categoria.cd_sub_categoria and "+
                "rel_pj_categoria.cd_pj = pessoa_juridica.cd_pj )");
        }

        if ( strCidade != null || strEstado != null || strPais != null){
            tabelas.add("endereco, rel_pj_endereco" );
            joins.add("(rel_pj_endereco.cd_endereco = endereco.cd_endereco)");


            if ( strCidade != null){
                condicoes.add( "(tx_cidade  = '"+strCidade+"') ");
            }
            if ( strEstado != null){
                condicoes.add("(tx_estado  = '"+strEstado+"') ");
            }
            if ( strPais != null){
                condicoes.add("(tx_pais  = '"+strPais+"') ");
            }
        }

        for (int i = 0; i < tabelas.size(); i++) {
            sqlCodes +=',' + (String) tabelas.get(i);
        }

        if (condicoes.size() > 0 ){
            sqlCodes +=" where (";
            for (int i = 0; i < condicoes.size(); i++) {
                if (i != 0){
                    sqlCodes +=" "+strConectivo+" ";
                }
                sqlCodes+= (String) condicoes.get(i);
            }
            sqlCodes +=" ) ";
        }

        if (joins.size() > 0 ){
            sqlCodes +=" and (";
            for (int i = 0; i < joins.size(); i++) {
                if (i != 0){
                    sqlCodes +=" and ";
                }
                sqlCodes+= (String) joins.get(i);
            }
            sqlCodes +=" ) ";
        }
        Connection con = getConnection();

        System.out.println("Codes" + sqlCodes);
        System.out.println("person" + sqlPerson);

        System.out.println("codes : " + sqlCodes);
        System.out.println("person : " + sqlPerson);

        PreparedStatement preCodes = createPreparedStatement(con, sqlCodes);
        PreparedStatement prePersons = createPreparedStatement(con, sqlPerson );
        try {
            List lstcodes = selectListFromBD(preCodes, getCodeFactory(), startIndex,
                size);
            return selectList(lstcodes, prePersons, factoryPessoaJuridica);
        } finally {
          closeStatement(preCodes);
          closeStatement(prePersons);
          freeConnection(con);
        }
  }

    /**
     * Método para procurar todas as pessoas juridicas.
     *
     * @return List    Lista contendo todas as pessoas juridicas cadastradas
     */
    public List getTodasPessoasJuridicas() throws PersistenceException{
        Connection con = getConnection();
        PreparedStatement  prepPerson = null;
        try {
            // preapara busca por pessoa
            prepPerson = createPreparedStatement(con, preparedSelectTodasPessoaJuridica);
            return selectListFromBD(prepPerson, getCodeFactory());
       //} catch (SQLException sqle){
//         throw new PersistenceException ("Problemas no BD.");
        } finally {
          closeStatement (prepPerson);
          freeConnection(con);
        }


    }

}
