/*
 The Broad Institute
 SOFTWARE COPYRIGHT NOTICE AGREEMENT
 This software and its documentation are copyright (2006) by the
 Broad Institute/Massachusetts Institute of Technology. All rights are
 reserved.

 This software is supplied without any warranty or guaranteed support
 whatsoever. Neither the Broad Institute nor MIT can be responsible for its
 use, misuse, or functionality.
 */

package calhoun.gebo.internal.db;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import calhoun.gebo.db.DataAccessException;
import calhoun.gebo.db.EditManager;
import calhoun.gebo.model.AnnotatedGene;
import calhoun.gebo.model.Feature;
import calhoun.gebo.model.FeatureTrack;
import calhoun.gebo.model.Identifiable;
import calhoun.gebo.model.Orf;
import calhoun.gebo.model.RnaDnaFeature;
import calhoun.gebo.model.Sequence;
import calhoun.gebo.model.Strand;
import calhoun.gebo.model.SubFeature;
import calhoun.gebo.util.Informer;
import calhoun.gebo.util.S;
import calhoun.gebo.util.U;
import calhoun.service.ArgoTranscriptService;

/**
 * @author <a href="mailto:reinhard@genome.wi.mit.edu">Reinhard Engels </a>
 */
public abstract class CalhounAnnotatedTranscript extends
        CalhounAnnotatedFeature {

    private static Informer I = new Informer(CalhounAnnotatedTranscript.class);

    // /////////////////////////////////////////////////////////////////////////
    // Constructors.
    // /////////////////////////////////////////////////////////////////////////

    public CalhounAnnotatedTranscript(Sequence sequence, FeatureTrack track,
            ResultSet resultSet, int editFlag) throws SQLException {
        super(sequence, track, resultSet, true, true, editFlag);
        getTranscriptType();
        setOrfVisible(true);
    }

    public CalhounAnnotatedTranscript(Sequence sequence, String label,
            String id, int start, int stop, FeatureTrack track, Strand strand,
            int editFlag) {
        super(sequence, label, id, start, stop, track, strand, true, true,
                editFlag);
        if (stop > sequence.getLength() || start < 1) {
            I.error("Transcript coordinates are out of sequence bounds!");
        }
        setOrfVisible(true);
    }

    public String toString() {
        return super.toString() + " ("
                + getPropertyValue(AP.TRANSCRIPT_ACCESSION_NUMBER) + ")";
    }

    public String getAccession() {
        return getPropertyValue(AP.TRANSCRIPT_ACCESSION_NUMBER);
    }

    public AnnotatedGene getGene() {
        if (m_gene == null && existsInDB()) {
            String geneFeatureId = getPropertyValue(AP.PARENT_FEATURE_ID);
            if (!S.isEmpty(geneFeatureId))
                try {
                    setGene(getTM().getGene(geneFeatureId));
                } catch (Exception e) {
                    I.error("Unable to find gene: " + geneFeatureId);
                }
        }
        return m_gene;
    }

    public void setGene(AnnotatedGene gene) {
        if (gene == AnnotatedGene.NEW) {
            m_gene = null;
            setProperty(AP.PARENT_FEATURE_ID, "");
            setProperty(AP.GENE_ACCESSION_NUMBER, "");
        } else if (gene == AnnotatedGene.NULL) {
            I
                    .error("Ignoring attempt to to assigned AnnotatedGene.NULL to a transcript!");
        } else {
            m_gene = gene;
            setProperty(AP.PARENT_FEATURE_ID, gene.getId());
            setProperty(AP.GENE_ACCESSION_NUMBER, gene.getAccession());
        }
    }

    // Stuff for EditCacheManager

    public void restore() {
        super.restore();
        restoreGene();
    }

    private void restoreGene() {
        String geneFeatureId = getPropertyValue(AP.PARENT_FEATURE_ID);
        if (geneFeatureId != null && !geneFeatureId.equals("")) {
            setGene(getTM().getGene(geneFeatureId));
        }
    }

    public Feature[] getSupplementalEvidence() {
        if (m_supplementalEvidence == null && existsInDB()) {
            setSupplementalEvidence(getTM().getSupplementalEvidence(this));
        }
        return m_supplementalEvidence;
    }

    public String insert() throws DataAccessException {
        try {
            setProperty(AP.TRANSCRIPT_ACCESSION_NUMBER, null);
            setProperty(AP.GENE_ACCESSION_NUMBER, null);
            setProperty(AP.CREATED_BY, CalhounConnectionManager.getInstance()
                    .getUsername());
            String id = insertOrUpdate(ArgoTranscriptService.INSERT_MODE, true);
            ((CalhounTrackManager) getTrack().getTrackManager())
                    .clearTracksForSequenceIfNoTranscriptsYet(getSequence());
            return id;
        } catch (Throwable t) {
            t.printStackTrace();
            throw new DataAccessException(t);
        }
    }

    public String update() throws DataAccessException {
        try {

            setProperty(AP.MODIFIED_BY, CalhounConnectionManager.getInstance()
                    .getUsername());
            return insertOrUpdate(ArgoTranscriptService.UPDATE_MODE, true);
        } catch (Throwable t) {
            I.trace(t);
            throw new DataAccessException(t);
        }
    }

    public void review() throws DataAccessException {
        try {
            ArgoTranscriptService service = new ArgoTranscriptService(
                    getConnection());
            service.reviewAnnotatedTranscript(getId(), CalhounConnectionManager
                    .getInstance().getUsername(),
                    getPropertyValue(AP.REVIEW_COMMENT));
        } catch (Throwable t) {
            throw new DataAccessException(
                    "Error reviewing transcript.\nError: " + t);
        }
    }

    public void delete() throws DataAccessException {
        checkRetirementCode();
        try {
            ArgoTranscriptService service = new ArgoTranscriptService(
                    getConnection());
            service.retireAnnotatedTranscript(
                    getPropertyValue(AP.PARENT_FEATURE_ID), getId(),
                    getSequence().getId(), getStrand().toString(),
                    CalhounConnectionManager.getInstance().getUsername(),
                    getPropertyValue(AP.RETIREMENT_COMMENT), Integer
                            .parseInt(getPropertyValue(AP.RETIREMENT_CODE)));
        } catch (Throwable t) {
            throw new DataAccessException(t);
        }
    }

    public void performCalhounValidatation() throws Throwable {
        insertOrUpdate(getEditFlag(), false);
    }

    public Orf getOrf() {
        if (m_orf == null) {
            m_orf = super.getOrf();
            // what is the logic?
            // if double partial
            if (m_orf != null && m_orf.getRnaLength() % 3 != 0) {
                // I.warn("gaga!");
                if (m_orf.isDoublePartial()) {
                    // I.warn("double partial!");
                    // TODO: reinhard: this is rare, but handle it.
                    // find a frame without contained stop codons.
                    // problem: if more than one, no way to choose.
                } else if (m_orf.getFivePrime() == getFivePrime()) {
                    // I.warn("fiver");
                    // reset five prime codon to first in three prime codon
                    // frame.
                    int frame = getStopCodon().getFrame();
                    m_orf.setFivePrime(m_orf.getDnaPosition(frame));
                } else if (m_orf.getThreePrime() == getThreePrime()) {
                    // I.warn("threebee");
                    int frame = getStopCodon().getFrame();
                    int threePrime = getThreePrime();
                    while (RnaDnaFeature.getFrame(threePrime) != frame) {
                        threePrime--;
                    }
                    m_orf.setThreePrime(threePrime);
                    // reset three prime codon to first in five prime codon
                    // frame.
                } else {
                    I.warn("what the...");

                }
            }
            return m_orf;
            // return null;
        } else {
            return super.getOrf();
        }
    }

    private String insertOrUpdate(int mode, boolean commit) throws Throwable {

        // validateEvidence();

        
        //I.warn("insertOrUpdate: " + dump());
        
        ArgoTranscriptService service = new ArgoTranscriptService(
                getConnection());
        // I.warn("Start Codon: " + getStartCodon());
        // I.warn("getFivePrime: " + getStartCodon().getFivePrime());

        int startCodon = getStartCodon().getFivePrime();
        int stopCodon = getStopCodon().getFivePrime();

        // If transcript is partial, remove "open" codon.

        Orf orf = getOrf();
        // I.error(orf.getPartialityReport());
        int frame = orf.getFrame();
        if (orf.isPartial()) {
            if (getOrf().isThreePrimePartial()) {
                // I.error("Partial (stopSide)");
                stopCodon = 0;
            }

            if (getOrf().isFivePrimePartial()) {
                // I.warn("Partial (startSide)");
                startCodon = 0;
            }

        } else {
            // I.warn("Orf is not partial");
        }
        // Codon a = getStartCodon();
        // Codon z = getStopCodon();
        // I.warn("Setting start codon 5': " + a.getFivePrime() + " RNA: "
        // + a.getRnaRaw() + " DNA: " + a.getRaw());
        // I.warn("Setting stop codon 5': " + z.getFivePrime() + " RNA: "
        // + z.getRnaRaw() + " DNA: " + z.getRaw());
        String n = ",\n";
        I
                .warn("\nString[] ids ="
                        + "service.insertOrUpdateAnnotatedTranscript(\n"
                        + mode
                        + n
                        + U.qq(getSequence().getId())
                        + n
                        + getStart()
                        + n
                        + getStop()
                        + n
                        + U.qq(getStrand().toString())
                        + n
                        + startCodon
                        + n
                        + stopCodon
                        + n
                        + Integer.parseInt(getTranscriptType().getId())
                        + n
                        + U.qq(getPropertyValue(AP.FEATURE_NAME))
                        + n
                        + U
                                .qq(getPropertyValue(AP.PRIMARY_EVIDENCE_FEATURE_ID))
                        + n
                        + U.qq(getPropertyValue(AP.TRANSCRIPT_COMMENTS))
                        + n
                        + U.qq(getPropertyValue(AP.CREATED_BY))
                        + n
                        + U.qq(getPropertyValue(AP.MODIFIED_BY))
                        + n
                        + U
                                .stringyArray(joinIdsForStoredProcedure(getCurationFlags()))
                        + n
                        + U.stringyArray(getExonList(this))
                        + n
                        + U
                                .stringyArray(joinIdsForStoredProcedure(getSupplementalEvidence()))
                        + n + getGeneSymbol() + n
                        + U.qq(getGene() == null ? "" : getGene().getId()) + n
                        + U.qq(getPropertyValue(AP.FEATURE_ID)) + ");\n");
        String[] ids = null;
        if (commit) {
            ids = service.insertOrUpdateAnnotatedTranscript(mode, getSequence()
                    .getId(), getStart(), getStop(), getStrand().toString(),
                    startCodon, stopCodon, Integer.parseInt(getTranscriptType()
                            .getId()), getPropertyValue(AP.FEATURE_NAME),
                    getPropertyValue(AP.PRIMARY_EVIDENCE_FEATURE_ID),
                    getPropertyValue(AP.TRANSCRIPT_COMMENTS),
                    getPropertyValue(AP.CREATED_BY),
                    getPropertyValue(AP.MODIFIED_BY),
                    joinIdsForStoredProcedure(getCurationFlags()),
                    getExonList(this),
                    joinIdsForStoredProcedure(getSupplementalEvidence()),
                    getGeneSymbol(), (getGene() == null ? "" : getGene()
                            .getId()), getPropertyValue(AP.FEATURE_ID));
            long parentFeatureId = Long.parseLong(ids[0]);
            long featureId = Long.parseLong(ids[1]);
            return featureId + "";
        } else {
            if (mode == EditManager.DELETE_FLAG) {
                mode = EditManager.UPDATE_FLAG;
            }
            service.validateTranscriptCreation(mode, getSequence().getId(),
                    getStart(), getStop(), getStrand().toString(), startCodon,
                    stopCodon, Integer.parseInt(getTranscriptType().getId()),
                    getPropertyValue(AP.FEATURE_NAME),
                    getPropertyValue(AP.PRIMARY_EVIDENCE_FEATURE_ID),
                    getPropertyValue(AP.TRANSCRIPT_COMMENTS),
                    getPropertyValue(AP.CREATED_BY),
                    getPropertyValue(AP.MODIFIED_BY),
                    joinIdsForStoredProcedure(getCurationFlags()),
                    getExonList(this),
                    joinIdsForStoredProcedure(getSupplementalEvidence()),
                    getGeneSymbol(), (getGene() == null ? "" : getGene()
                            .getId()), getPropertyValue(AP.FEATURE_ID));
            return "";
        }

    }

    public String getGeneSymbol() {
        return getPropertyValue(AP.GENE_SYMBOL);
    }

    private String[] joinIdsForStoredProcedure(Identifiable[] items) {
        List l = new ArrayList();
        for (int i = 0; i < items.length; i++) {
            l.add(items[i].getId());
        }
        return (String[]) l.toArray(new String[l.size()]);
    }

    public static String getExonString(Feature feature) {
        // public only for testing.
        String[] sa = getExonList(feature);
        StringBuffer b = new StringBuffer();

        for (int i = 0; i < sa.length; i++) {
            if (i > 0)
                b.append("#");
            b.append(sa[i]);
        }

        return b.toString();
    }

    private static String[] getExonList(Feature feature) {
        // public only for testing.
        SubFeature[] subFeatures = feature.getOrderedSubFeatures();
        List l = new ArrayList();
        for (int i = 0; i < subFeatures.length; i++) {
            String type = null;
            if (subFeatures.length == 1) {
                type = "Only";
            } else if (i == 0) {
                type = "Init";
            } else if (i + 1 == subFeatures.length) {
                type = "Term";
            } else {
                type = "Intr";
            }
            l.add("" + subFeatures[i].getStart() + ","
                    + subFeatures[i].getStop() + "," + type);
        }
        return (String[]) l.toArray(new String[l.size()]);
    }

    // hacks.

    public boolean hasGeneId() {
        return true;
    }

    public boolean hasTranscriptId() {
        return true;
    }

    public String getGeneId() {
        return getPropertyValue(AP.GENE_ACCESSION_NUMBER);
    }

    public String getTranscriptId() {
        return getPropertyValue(AP.TRANSCRIPT_ACCESSION_NUMBER);
    }

    private AnnotatedGene m_gene;
    // private static CalhounTrack PENDING_ANNOTATED_TRANSCRIPT_TRACK;
}