package calhoun.gebo.internal.db;

import java.util.List;
import java.util.Random;

import calhoun.bean.AlignmentSegment;
import calhoun.bean.AlignmentSpan;
import calhoun.gebo.model.Feature;
import calhoun.gebo.model.FeatureTrack;
import calhoun.gebo.model.Sequence;
import calhoun.gebo.model.SequenceGroup;
import calhoun.gebo.model.Strand;
import calhoun.gebo.util.C;
import calhoun.gebo.util.Informer;
import calhoun.gebo.util.Q;
import calhoun.gebo.util.S;
import calhoun.seq.Alignment;

public class MultiAlignmentSpan extends calhoun.gebo.internal.db.AlignmentSpan {

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

    private static Random RANDOM = new Random();

    private MultiAlignmentSpan(Sequence sequence, String label, String id,
            int start, int stop, FeatureTrack track, Strand strand,
            AlignmentSpan spanA, AlignmentSpan spanB) {
        super(sequence, label, id, start, stop, track, strand, false, false, 0);
        m_spanA = spanA;
        m_spanB = spanB;
        // m_alignment = new
        // calhoun.bean.Alignment(m_spanA.getRawSequence(),m_spanB.getRaw());
        // updateCache(start, stop);
    }

    public static MultiAlignmentSpan create(Sequence sequence,
            CalhounTrack track, AlignmentSpan spanA, AlignmentSpan spanB) {
        MultiAlignmentSpan f = new MultiAlignmentSpan(sequence, spanB
                .getSequence().getDescription(), spanB.getId(), spanA
                .getStart(), spanA.getStop(), track, Strand.fromBean(spanA
                .getStrand()), spanA, spanB);
        return f;
    }

    public String getAlignmentString(int a, int z) {
        a -= 2;
        z -= 2;
        // updateCache(a, z);
        // a -= m_cachedA;
        // z -= m_cachedA;
        // return m_cachedString.substring(a-1, z-1);
        return m_alignmentString.substring(a, z);
        // return S.x("?", z - a + 1);
    }

    // segments = s.find("from AlignmentSegment seg where seg.sequenceGroup.id =
    // 'DENGUE'")

    private List<String> makeAlignmentStrings(List<AlignmentSegment> segments,
            int start, int stop) {
        // the first segment is the one start and stop are with respect to
        AlignmentSegment refSeg = segments.get(0);
        // this is a bit of a hack. I should probably fix up the Alignment code
        // so it
        // allows the query seq to be null. It fails an exception if either seq
        // is null,
        // but we really don't care what the value is.
        String fakeRawRef = S.x("A", refSeg.getSpan().getQueryLength());
        // map start and stop to the fake reference (global coordinate frame)
        int startOnRef = refSeg.mapSequenceToQuery(start);
        int stopOnRef = refSeg.mapSequenceToQuery(stop);
        List<String> alStrings = C.list();
        for (AlignmentSegment seg : segments) {
            // for each segment we're interested in, construct an alignment
            // object
            // with it's
            // gap string and looking at the interval as mapped to _this_
            // segment.
            // int startOnSeg = seg.mapQueryToSequence(startOnRef);
            // int stopOnSeg = seg.mapQueryToSequence(stopOnRef);
            Alignment al = new Alignment(seg.getGap(), seg
                    .retrieveRawSequence(), fakeRawRef);
            // an Alignment holds a pairwise alignment between a sequence called
            // "reference"
            // and one called "target". "target" is the fakeRef, and hence the
            // value
            // is meaningless. However,
            // reference is our original sequence with gaps inserted.
            alStrings.add(al.getReference());
            // return the array of alignments
        }
        return alStrings;
    }

    // als = makeAlignmentStrings(segments, 400, 500)

    private void appendAlignmentString(StringBuilder b, int a, int z) {
        // TODO wait till phil's snippet
        List<AlignmentSegment> segments = C.list();
        // I.warn("This many segments: " + m_spanA.getSegments().size());
        // I.warn("This many segments: " + m_spanB.getSegments().size());
        if (m_spanA.getSegments().size() > 1
                || m_spanB.getSegments().size() > 1) {
            I.error("More than one segment per span!");
        }
        segments.addAll(m_spanA.getSegments());
        segments.addAll(m_spanB.getSegments());
        List<String> strings = makeAlignmentStrings(segments, a, z);
        String stringA = strings.get(0);
        String stringB = strings.get(1);
        // hack this is going to break with multiple segments.
        appendAlignmentString(b, stringA, stringB, this);
    }

    protected int getSequencePosition(Feature segment, int i) {
        // no idea. hack. works for now.
        return segment.getSequencePosition(i);
    }

    protected int getGapStartFudge() {
        return 0;
    }

    public void updateCache() {
        // updateCache(getStart(), getStop());
        updateCache(1, getSequence().getLength());
    }

    private void updateCache(int a, int z) {
        if (m_alignmentString == null || a < m_cachedA || z > m_cachedZ) {
            // I.warn("updating cache: required a..z: " + a + ".." + z);
            a -= MIN_CACHE_SIZE / 2;
            if (a < 1) {
                a = 1;
            }
            m_cachedA = a;
            m_cachedZ = z;
            if (m_cachedZ < m_cachedA + MIN_CACHE_SIZE) {
                m_cachedZ = m_cachedA + MIN_CACHE_SIZE
                        + RANDOM.nextInt(MIN_CACHE_SIZE);
            }
            if (m_cachedZ > getStop()) {
                m_cachedZ = getStop();
            }
            a = m_cachedA;
            z = m_cachedZ;
            I.warn("updating cache: allocated a..z: " + a + ".." + z);
            StringBuilder b = new StringBuilder();
            appendAlignmentString(b, a, z);
            // I.warn("updating cache: string: " + b.toString());
            m_alignmentString = b.toString();
        }
    }

    public static List<CalhounTrack> explodeMultipleAlignmentTrack(
            CalhounTrack ct, Sequence sequence) {
        List<CalhounTrack> explodedTracks = C.list();
        SequenceGroup sg = sequence.getPrimarySequenceGroup();
        Sequence[] sequences = sg.getSequences();
        List<AlignmentSpan> alignmentSpans = C.list();
        // get the alignmentSpan A
        // get each alignmentSpan B join A and B to create exploded track.
        calhoun.util.DbSession session = CalhounConnectionManager.getInstance()
                .getDbSession();
        session.clear();
        calhoun.bean.AlignmentSpan spanA = null;
        String hql = "from AlignmentSpan f where f.sequenceGroup.id = ? and f.analysisRun.id = " + ct.getAnalysisEventId();
        Q.startStopWatch(hql);
        List rows = session.find(hql, sg.getId());
        for (Object row : rows) {
            AlignmentSpan a = (AlignmentSpan) row;
            if (a.getSequence().getId().equals(sequence.getId())) {
                spanA = a;
            }
            alignmentSpans.add(a);
        }
        if ( spanA == null ) {
            I.warn("No matching alignmentspan for sequence id " + sequence.getId());
            return explodedTracks;
        }
        for (AlignmentSpan spanB : alignmentSpans) {
            CalhounTrack mact = CalhounTrack.intern(ct.getTrackType(), ct
                    .getSubclass(), ct.getOntologyTerm(), ct
                    .getAnalysisEventId()
                    + "_m" + spanB.getId(), "M Alspn (N): "
                    + spanB.getSequence().getDescription(), 0, "");
            mact.setDefaultVisible(false);
            mact.loadPrefs();
            mact.setTemplateTrack(ct);
            mact.setExplodedMultiAlignmentSpanTrack(true);
            for (Sequence s : sequences) {
                List<Feature> features = C.list();
                features.add(MultiAlignmentSpan.create(s, mact, spanA, spanB));
                mact.putFeatures(features, s);
            }
            explodedTracks.add(mact);
           // System.out.println("REINOS: adding exploded Track: " + mact);
        }
        Q.stopStopWatch(hql);
        return explodedTracks;
    }

    private static int MIN_CACHE_SIZE = 50000;
    private int m_cachedA;
    private int m_cachedZ;
    private calhoun.bean.AlignmentSpan m_spanA;
    private calhoun.bean.AlignmentSpan m_spanB;
    private calhoun.seq.Alignment m_alignment;
}
