/*
 * Copyright 2009, 2010 Lari Natri <lari.natri@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package compid.data.db_derby;

import compid.data.*;
import compid.data.tuples.*;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Collection;
import java.util.HashSet;

/**
 *
 * @author Lari Natri
 */
public class UpdateHandlerDerby extends BaseDAODerby implements UpdateHandler {


    private final DAOFactory daoFactory;

    public UpdateHandlerDerby(DAOFactory daoFactory) {
        this.daoFactory = daoFactory;
    }

    @Override
    public void addFullDataset(FullDataset dc) throws DAOException {
        Connection conn = getNewConnection();

        if (dc.getDataSetInfos().size()==1) { // update counts column
            HashSet<Integer> groupIds = new HashSet<Integer>(512);
            for (ProteinHit ph : dc.getProteinHits()) {
                groupIds.add(ph.getGroupId());
            }
            String dataCountsStr = "Groups: "+groupIds.size()+"; Proteins: "
                    +dc.getProteins().size()+"; Peptides: "
                    +dc.getPeptides().size() + "; Queries: "+dc.getQueries().size()+"; Sub Proteins: ";
            dataCountsStr+= (dc.getProteins().size()>groupIds.size() ? "YES" : "NO");

            dc.getDataSetInfos().iterator().next().setDataCountsStr(dataCountsStr);
            groupIds.clear();
            groupIds = null;
        }


        setAutoCommit(conn, false);
        long beginTime = System.nanoTime();
        long time;
        try {
            System.out.println("DEBUG: Injecting dataset into database @ UpdateHandlerDerby.addFullDataset().");
            insertMultipleFastNoFKChecks(conn,dc.getProtDatabases());
            time = System.nanoTime();
            insertMultipleFastNoFKChecks(conn,dc.getProtSequences());
            System.out.println("DEBUG: "+dc.getProtSequences().size()+" ProtSequences inserted in "+(System.nanoTime()-time)/1000000+" ms");
            insertMultipleFastNoFKChecks(conn,dc.getDataSetInfos());
            time = System.nanoTime();
            insertMultipleFastNoFKChecks(conn,dc.getPeptides());
            System.out.println("DEBUG: "+dc.getPeptides().size()+" Peptides      inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            ProteinDAO pDAO = daoFactory.getProteinDAO();
            PreparedStatement updOldProtsStmt = prepareUpdatableStatement(conn,SQL.UPDATEPROTSEQIDBYACCESSION);
            for (Protein prot : dc.getProteins()) {
                if (!pDAO.setForeignKeysFromRefs(prot, false)) {
                    throw new DAOException();
                }
                // If this protein has a sequence which is already in the database,
                // other proteins with the same accession as this will be updated
                // to point to the sequence in this prot. Added for datasets containing
                // sequences: no crashes on unique accession + existing proteins
                // without sequences will now get them.
                try {
                    if (prot.getProtSeq().getPrimaryKey()>0 && prot.getProtSeq()
                            .getLength()>0 && prot.getAccession().length()>0)
                    {
                        this.updateProteinsSeqId(conn, updOldProtsStmt, prot.getAccession(), prot.getProtSeq().getPrimaryKey());
                    }
                } catch (SQLException ex) {
                    throw new DAOException(ex);
                }
            }
            close(updOldProtsStmt);
            insertMultipleFastNoFKChecks(conn,dc.getProteins());
            System.out.println("DEBUG: "+dc.getProteins().size()+" Proteins      inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            QueryDAO qDAO = daoFactory.getQueryDAO();
            for (Query q : dc.getQueries()) {
                if (!qDAO.setForeignKeysFromRefs(q, false)) {
                    throw new DAOException();
                }
            }
            insertMultipleFastNoFKChecks(conn,dc.getQueries());
            System.out.println("DEBUG: "+dc.getQueries().size()+" Queries       inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            ProteinHitDAO phDAO = daoFactory.getProteinHitDAO();
            for (ProteinHit ph : dc.getProteinHits()) {
                if (!phDAO.setForeignKeysFromRefs(ph, false)) {
                    throw new DAOException();
                }
            }
            insertMultipleFastNoFKChecks(conn,dc.getProteinHits());
            System.out.println("DEBUG: "+dc.getProteinHits().size()+" ProteinHits   inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            PeptideHitDAO pepHitDAO = daoFactory.getPeptideHitDAO();
            for (PeptideHit pepHit : dc.getPeptideHits()) {
                if (!pepHitDAO.setForeignKeysFromRefs(pepHit, false)) {
                    throw new DAOException();
                }
            }
            insertMultipleFastNoFKChecks(conn,dc.getPeptideHits());
            System.out.println("DEBUG: "+dc.getPeptideHits().size()+" PeptideHits   inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            ProtHitPeptHitJoinDAO ppDAO = daoFactory.getProtHitPeptHitJoinDAO();
            for (ProtHitPeptHitJoin phph : dc.getProtHitPeptHitJoins()) {
                if (!ppDAO.setForeignKeysFromRefs(phph, false)) {
                    throw new DAOException();
                }
            }
            insertMultipleFastNoFKChecks(conn,dc.getProtHitPeptHitJoins());
            System.out.println("DEBUG: "+dc.getProtHitPeptHitJoins().size()+" PHPHjoins     inserted in "+(System.nanoTime()-time)/1000000+" ms");
            time = System.nanoTime();

            commit(conn);

            System.out.println("DEBUG:  Injected full dataset in "+(System.nanoTime()-beginTime)/1000000+" ms TOTAL");

        } catch (DAOException ex) {
            try {
                rollback(conn);

            } catch (DAOException ex2) {}
            throw ex;
        } finally {
            //setAutoCommit(conn, true);
            close(conn);
        }

    }

    @Override
    public int deleteDatasets(Collection<DatasetInfo> dataSetInfos)
                                  throws DAOException
    {
        // TODO: delete orphaned: Protein, ProtSeq and Peptide!
        // Tries before and was very slow, but it might be quicker now
        // with updated indexing
        int deletionCount = 0;
        Connection conn = getNewConnection();
        PreparedStatement delDatasetsStmt = prepareStatement(conn, SQL.DELETEDATASETS);
        //PreparedStatement delPeptides = prepareStatement(conn, SQL.DELETEORPHANEDPEPTIDES);
        try {
            //setAutoCommit(conn,false);
            for (DatasetInfo dsi : dataSetInfos) {
                delDatasetsStmt.setInt(1, findPrimaryKey(conn, dsi));
                delDatasetsStmt.executeUpdate(); // cascades
                commit(conn);
                ++deletionCount;
            }
            commit(conn);
// Peptide deletion commented out: very slow.. On the other hand it might be
// good to keep them for insertion speed, because they are shared.
//            delPeptides.executeUpdate(); // delete orphaned Peptides
//            commit(conn);
        } catch (Exception ex) {
            throw new DAOException(ex);
        } finally {
            setAutoCommit(conn,true);
            close(delDatasetsStmt);
            close(conn);
        }

        return deletionCount;
    }

    @Override
    public int updateSequenceRefs(Collection<Protein> prots)
            throws DAOException
    {
        Connection conn = getNewConnection();
        PreparedStatement stmt
                = prepareUpdatableStatement(conn, SQL.UPDATEPROTSEQIDBYPROTID);
        int updateCount = 0;

        ProteinDAO protDAO = daoFactory.getProteinDAO();

        try {
            setAutoCommit(conn, false);

            // first insert the sequences
            HashSet<ProtSeq> seqs = new HashSet<ProtSeq>(prots.size(),1);
            for (Protein prot : prots) {
                if (prot.getProtSeq()!=null) {
                    seqs.add(prot.getProtSeq());
                }
            }
            insertMultipleFastNoFKChecks(conn,seqs); // adds only if not exists
            seqs = null;

            commit(conn); // this commit must be here, it seems

            // then update sequence FK on proteins
            for (Protein prot : prots) {
                int protPK = prot.getPrimaryKey();
                // find PK if not set
                if (protPK<1) {
                    protPK = findPrimaryKey(conn,prot);
                    prot.setPrimaryKey(protPK);
                }
                if (protPK>0) { // update only if protein already in database
                    protDAO.setForeignKeysFromRefs(prot,false);
                    stmt.setInt(1, prot.getProtSeq().getPrimaryKey());
                    stmt.setInt(2, protPK);
                    updateCount+=stmt.executeUpdate(); // update
                }
            }

            commit(conn);
            setAutoCommit(conn, true);
        } catch (SQLException ex) {
            rollback(conn);
            throw new DAOException(ex);
        } finally {
            close(stmt);
            close(conn);
        }
        return updateCount;
    }

    @Override
    public boolean updateProteinsSeqId(String accession, int id)
            throws DAOException
    {
        boolean updated;
        Connection conn = getNewConnection();
        PreparedStatement stmt = prepareUpdatableStatement(conn,SQL.UPDATEPROTSEQIDBYACCESSION);
        try {
            stmt.setInt(1, id);
            stmt.setInt(2, id);
            stmt.setString(3, accession);
            updated = this.updateProteinsSeqId(conn, stmt, accession, id);
        } catch (SQLException ex) {
            throw new DAOException(ex);
        } finally {
            close(stmt);
            close(conn);
        }
        return (updated);
    }
    
    private boolean updateProteinsSeqId(Connection conn, PreparedStatement updprotseqidbyaccStmt, String accession, int id) throws SQLException
    {
        updprotseqidbyaccStmt.setInt(1, id);
        updprotseqidbyaccStmt.setInt(2, id);
        updprotseqidbyaccStmt.setString(3, accession);
        return (updprotseqidbyaccStmt.executeUpdate()>0);
    }

}
