/*
 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.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import calhoun.gebo.api.GroupingSequenceManager;
import calhoun.gebo.api.TrackManager;
import calhoun.gebo.db.FeatureFinder;
import calhoun.gebo.model.Criterion;
import calhoun.gebo.model.Feature;
import calhoun.gebo.model.FeatureTrack;
import calhoun.gebo.model.Sequence;
import calhoun.gebo.model.SequenceGroup;
import calhoun.gebo.model.SequenceNumberCriteria;
import calhoun.gebo.util.C;
import calhoun.gebo.util.Informer;
import calhoun.gebo.util.Q;
import calhoun.gebo.util.S;
import calhoun.gebo.util.U;

/**
 * Search the calhoun database for features.
 * 
 * @author <a href="mailto:reinhard@genome.wi.mit.edu">Reinhard Engels </a>
 */
public class CalhounFeatureFinder extends CalhounGuts implements FeatureFinder {

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

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

    /**
     * Creates a new <code>CalhounFeatureFinder</code> instance.
     * 
     * @param connection
     *            a <code>Connection</code> value
     */
    private CalhounFeatureFinder() {
        super("CalhounFeatureFinder", "CalhounFeatureFinder");
    }

    // /////////////////////////////////////////////////////////////////////////
    // Static Factory Methods.
    // /////////////////////////////////////////////////////////////////////////

    public static CalhounFeatureFinder getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new CalhounFeatureFinder();
        }
        return INSTANCE;
    }

    // /////////////////////////////////////////////////////////////////////////
    // FeatureFinder Interface.
    // /////////////////////////////////////////////////////////////////////////

    public PreparedStatement getFindFeatureStatement(
            SequenceGroup[] sequenceGroups,
            SequenceNumberCriteria sequenceNumberCriteria,
            Criterion[] criteria, FeatureTrack track) {
        // this is very, very, very hacky.
        // TODO: instead of preparedstatement, we should return an iterator. sql
        // version can be backed by a preparedstatement. hql by its thing.

        // SELECT F.ap_label, F.ap_subtype, F.ap_length,F.ap_start, F.ap_stop, "
        // + " F.ap_strand, S.ap_group_order,S.ap_group,

        String sql = "select F.ap_feature_id " + "from "
                + ((CalhounTrack) track).getTrackType().getView() + " F, "
                + "ap_bio_sequence S " + "where "
                + "F.ap_sequence_id = S.ap_id and F.ap_subtype='"
                + track.getId() + "' ";
        String sequenceGroupSql = getSequenceGroupSql(sequenceGroups, "S");
        String criteriaSql = getCriteriaSql(criteria, "F");
        sql += criteriaSql + sequenceGroupSql + sequenceNumberCriteria;

        PreparedStatement statement = null;
        try {
            Q.startStopWatch(sql);
            statement = getConnection().prepareStatement(sql,
                    ResultSet.TYPE_SCROLL_INSENSITIVE,
                    ResultSet.CONCUR_READ_ONLY);
        } catch (SQLException e) {
            I.error("Could not get feature result set. \nsql: " + sql, e);
        } finally {
            Q.stopStopWatch(sql);
        }
        return statement;
    }

    public Feature[] getFeatures(String[] ids) {
        try {
           // I.warn("Feature id: " + ids[0]);
            return super.getFeatures(CalhounSequenceManager.getInstance(), ids);
        } catch (Exception x) {
            I.error("Error getting features for ids: " + S.join(",", ids), x);
        }
        return null;
    }

    public Feature getSimpleFeature(String id) {
        Feature[] features = this
                .getSimpleFeatures(new String[] { id });
        if (features.length > 0) {
            return features[0];
        }
        return null;
    }

    public Feature[] getSimpleFeatures(String[] ids) {
        return getSimpleFeatures(ids, null);
    }

    public Feature[] getSimpleFeatures(String[] ids, Sequence sequence) {
        List<Feature> list = C.list();
        String[] sqls = generateSimpleSqls(ids);
        for (int i = 0; i < sqls.length; i++) {
            // I.warn("SQL: " + sqls[i]);
            try {
                Q.startStopWatch(sqls[i]);
                PreparedStatement statement = getConnection().prepareStatement(
                        sqls[i]);
                // TODO: reinahrd
                ResultSet resultSet = statement.executeQuery();
                while (resultSet.next()) {
                    CalhounTrack track = CalhounTrackManager
                            .getInstance()
                            .getTrack(resultSet.getString("AP_SUBCLASS"),
                                    resultSet.getString("AP_ONTOLOGY_TERM_ID"),
                                    resultSet.getString("AP_ANALYSIS_EVENT_ID"));
                    if (sequence == null) {
                        sequence = CalhounSequenceManager.getInstance()
                                .getSequence(
                                        resultSet.getString("AP_SEQUENCE_ID"));
                    }
                    Feature feature = createSimpleFeature(sequence,
                            track, resultSet);
                    list.add(feature);
                }
            } catch (SQLException e) {
                I.error("Could not get feature result set. \nsql: " + sqls[i],
                        e);
            } finally {
                Q.stopStopWatch(sqls[i]);
            }
        }
        return list.toArray(new Feature[0]);
    }

    protected String[] generateSimpleSqls(String[] allIds) {
        List sqlList = new ArrayList();
        String[][] idGroups = U.group(allIds, 1000);
        for (int i = 0; i < idGroups.length; i++) {
            String[] ids = idGroups[i];
            sqlList.add(getApFeatureSelect() + " WHERE AP_ID IN ("
                    + S.join(",", ids) + ") order by ap_start");
        }
        return (String[]) sqlList.toArray(new String[0]);
    }

    public Feature getFeature(String id) {
        try {
            I.warn("REINOS: A ID " + id);
            return super.getFeature(CalhounSequenceManager.getInstance(), id);
        } catch (Exception x) {
            I.error("Error getting feature by id: ", x);
        }
        return null;
    }

    // NCU00018
    public Feature getFeatureByLocus(String locusDotVersion) {
        String[] parts = locusDotVersion.split("\\.");
        String locus = locusDotVersion;
        String version = null;
        if (parts.length > 0) {
            locus = parts[0];
            if (parts.length > 1) {
                version = parts[1];
            }
        }
        String sql = "select F.ap_id from ap_euk_gene E, ap_feature F where F.ap_parent_feature_id = E.AP_FEATURE_ID and E.ap_locus= ? and F.ap_subtype IN ('CALHOUN_TRANSCRIPT','AUTOCALLED_TRANSCRIPT') ";
        if (version == null) {
            sql += " order by E.ap_version desc";
        } else {
            sql += "  and E.ap_version= ? ";
        }
        String featureId = null;
        try {
            Q.startStopWatch(sql);
            PreparedStatement statement = getConnection().prepareStatement(sql);
            statement.setString(1, locus);
            if (version != null) {
                statement.setString(2, version);
            }
            ResultSet resultSet = statement.executeQuery();
            if (resultSet.next()) {
                featureId = resultSet.getString(1);
            }
            statement.close();
            resultSet.close();
            Q.stopStopWatch(sql);
        } catch (Throwable t) {
            I.error("Could not look up Feature id for locus" + locus + "."
                    + version + ". \nsql: " + sql, t);
        }
        return getFeature(featureId);
    }

    public TrackManager getTrackManager() {
        return CalhounTrackManager.getInstance();
    }

    public GroupingSequenceManager getSequenceManager() {
        return CalhounSequenceManager.getInstance();
    }

    public Feature getSyntenicFeature(Feature f1) {
        I.warn("REINOS: Get syntenic feaure for: " + f1.getId());
        String sql = "select ap_first_feature_id, ap_second_feature_id "
                + " from ap_syntenic_anchor "
                + " where ap_first_feature_id= ? or ap_second_feature_id = ? ";
        // TODO: reinos
        String id1 = "";
        String id2 = "";
        try {
            Q.startStopWatch(sql);
            PreparedStatement statement = getConnection().prepareStatement(sql);
            statement.setLong(1, Long.parseLong(f1.getId()));
            statement.setLong(2, Long.parseLong(f1.getId()));
            ResultSet resultSet = statement.executeQuery();

            while (resultSet.next()) {
                id1 = resultSet.getString(1);
                id2 = resultSet.getString(2);
            }
            statement.close();
            resultSet.close();
            Q.stopStopWatch(sql);
        } catch (Throwable t) {
            I.error("Could not look up syntinic Feature for " + f1
                    + ". \nsql: " + sql, t);
        }
        String id = "";
        if (f1.getId().equals(id1)) {
            id = id2;
        } else {
            id = id1;
        }
        return getFeature(id);
    }

    // /////////////////////////////////////////////////////////////////////////
    // Private.
    // /////////////////////////////////////////////////////////////////////////

    // /////////////////////////////////////////////////////////////////////////
    // Data.
    // /////////////////////////////////////////////////////////////////////////

    private static CalhounFeatureFinder INSTANCE;

}