package util;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.HashMap;

public class MecanismoPersistenciaBDR implements IMecanismoPersistencia {

    private static MecanismoPersistenciaBDR singleton;
    private static int numConexoes = 2;
        
    private Connection conexoesCriadas[];
    private Connection conexoesLivres[];
    private HashMap conexoesAlocadas;
    private String classeDoDriver;
    private String url;
    private String login;
    private String senha;
    private boolean indisponivel;

    private MecanismoPersistenciaBDR(String url, String login, String senha, String classeDoDriver)
            throws RepositorioException {
        conexoesAlocadas = new HashMap();
        this.classeDoDriver = classeDoDriver;
        this.url = url;
        this.login = login;
        this.senha = senha;
        indisponivel = false;
        try {
            Class.forName(classeDoDriver);
        } catch (ClassNotFoundException e) {
            throw new RepositorioException(e);
        }
    }
    
    /**
     * Confira se o login, a senha e a url de conexão estão corretos.
     */
    public static synchronized MecanismoPersistenciaBDR getInstance() throws RepositorioException {
        if (singleton==null) {
            singleton = new MecanismoPersistenciaBDR(
                "jdbc:oracle:thin:@iguaraci.cin.ufpe.br:1521:dbgeral",
                "phmb", "phmb", "oracle.jdbc.driver.OracleDriver");
        }
        return singleton;
    }            
    
    public synchronized void conectar() throws RepositorioException {
        if (conexoesCriadas == null) {
            try {
                conexoesLivres = new Connection[MecanismoPersistenciaBDR.numConexoes];
                conexoesCriadas = new Connection[MecanismoPersistenciaBDR.numConexoes];
                for (int i = 0; i < MecanismoPersistenciaBDR.numConexoes; i++) {
                    conexoesCriadas[i] = DriverManager.getConnection(url, login, senha);
                    conexoesLivres[i] = conexoesCriadas[i];
                }

            } catch (Exception e) {                
                throw new RepositorioException(e);
            }
        }
    }
    
    public synchronized void desconectar() throws RepositorioException {
        try {
            if (conexoesCriadas != null) {
                int fechadas = 0;
                for (int i = 0; i < MecanismoPersistenciaBDR.numConexoes; i++) {
                }
                for (int i = 0; i < MecanismoPersistenciaBDR.numConexoes; i++) {
                    if (conexoesCriadas[i] != null) {
                        conexoesCriadas[i].close();
                        fechadas++;
                    }
                }

                conexoesCriadas = null;
            }
        } catch (Exception e) {
            
            throw new RepositorioException(e);
        }
    }

   
    public synchronized void iniciarTransacao() throws RepositorioException {
        try {
            while (indisponivel)  {
                wait();
            }
            Connection con = (Connection)getCommunicationChannel(true);
            con.setAutoCommit(false);            
        } catch (Exception e) {           
            throw new RepositorioException(e);
        }
    }
        
    public synchronized void confirmarTransacao() throws RepositorioException {
        try {
            Connection con = (Connection)getCommunicationChannel(true);
            con.commit();
            con.setAutoCommit(true);
            releaseCommunicationChannel(true);
        } catch (Exception e) {            
            throw new RepositorioException(e);
        } finally {
            notifyAll();
        }
    }
    

    public synchronized void cancelarTransacao() throws RepositorioException {
        try {
            Connection con = (Connection)getCommunicationChannel(true);
            con.rollback();
            con.setAutoCommit(true);
            releaseCommunicationChannel(true);
        } catch (Exception e) {           
            throw new RepositorioException(e);
        } finally {
            try {
                notifyAll();
            } catch (Exception e) {
                
            }
        }
    }    
    
    /**
     * Retorna um java.sql.Statement
     */
    public synchronized Object getCanalComunicacao() throws RepositorioException {
        try {
            return getCommunicationChannel(false).createStatement();
        } catch (Exception ex) {
            throw new RepositorioException(ex);
        }            
    }
 
    /**
     * Retorna um java.sql.PreparedStatement
     */
    public synchronized Object getCanalComunicacao(String sql) throws RepositorioException {
        try {
            return getCommunicationChannel(false).prepareStatement(sql);
        } catch (Exception ex) {
            throw new RepositorioException(ex);
        }            
    }
    
    /**
     * Libera o canal de comunicação
     */ 
    public synchronized void liberarCanalComunicacao() throws RepositorioException {
        releaseCommunicationChannel(false);
    }     
     
     
    /**
     * Retorna um java.sql.Connection
     */
    private synchronized Connection getCommunicationChannel(boolean porTransacao) 
            throws RepositorioException {
        Connection resposta = null;
        try {
            Thread currentThread = Thread.currentThread();
            int threadId = currentThread.hashCode();
            if (conexoesAlocadas.containsKey("" + threadId)) {
                resposta = (Connection) conexoesAlocadas.get("" + threadId);
            } else {
                boolean achou = false;
                do {
                    if (achou) {
                        break;
                    }
                    for (int i = 0; !achou && i < MecanismoPersistenciaBDR.numConexoes; i++) {
                        if (conexoesLivres[i] == null) {
                            continue;
                        }
                        achou = true;
                        resposta = conexoesLivres[i];
                        conexoesLivres[i] = null;
                        conexoesAlocadas.put("" + threadId, resposta);
                        if (porTransacao) {
                            conexoesAlocadas.put("T" + threadId, null);
                        }
                    }

                    if (!achou) {
                        indisponivel = true;
                        wait();
                    }
                } while (true);
            }
        } catch (Exception ex) {        	
            throw new RepositorioException(ex);
        }
        return resposta;
    }     
    
    private synchronized void releaseCommunicationChannel(boolean porTransacao)  throws RepositorioException {
        try {
            Thread currentThread = Thread.currentThread();
            int threadId = currentThread.hashCode();
            if (porTransacao || !porTransacao && !conexoesAlocadas.containsKey("T" + threadId)) {
                Object canal = conexoesAlocadas.get("" + threadId);
                boolean achou = false;
                for (int i = 0; !achou && i < MecanismoPersistenciaBDR.numConexoes; i++) {
                    if (conexoesLivres[i] != null) {
                        continue;
                    }
                    achou = true;
                    conexoesAlocadas.remove("" + threadId);
                    if (conexoesAlocadas.containsKey("T" + threadId)) {
                        conexoesAlocadas.remove("T" + threadId);
                    }
                    conexoesLivres[i] = (java.sql.Connection)canal;
                }

                indisponivel = false;
            }
        } catch (Exception ex) {
            throw new RepositorioException(ex);
        } finally {
            notifyAll();
        }
    }
    
    public void finalize() {
        try {
            this.desconectar();
        } catch(RepositorioException ex) {
            ex.printStackTrace();
        }                
    }
}
