/*
 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.awt.Color;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import calhoun.gebo.api.AbstractCombinedDataManager;
import calhoun.gebo.api.FiledSequenceManager;
import calhoun.gebo.model.CompoundFeature;
import calhoun.gebo.model.Feature;
import calhoun.gebo.model.FeatureTrack;
import calhoun.gebo.model.Segment;
import calhoun.gebo.model.Sequence;
import calhoun.gebo.model.Strand;
import calhoun.gebo.util.Informer;
import calhoun.gebo.util.S;

/**
 * <code>SeqPathDataManager</code> is an object to interpret seqpath
 * information into language that calhoun understands. Presumbally, it will do
 * following tasks:
 * <p> . read seqpath from resources . get a sequence and sequence related
 * characteristics(Id, length, strand, etc. etc) . put the sequence information
 * into a calhoun Sequence . further process the Sequence into a calhoun
 * SequenceModel . process each feature(SNP, SSR, etc. etc) in the sequence into
 * Calhoun Feature . maintain following mappings in this object: sequence id -->
 * Sequence sequence id --> SequenceModel feature id --> Feature
 * </p>
 * 
 */
public class SeqPathDataManager extends AbstractCombinedDataManager implements
        FiledSequenceManager {

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

    /**
     * constructs a new instance of SeqPathDataManager
     * 
     */
    public SeqPathDataManager() {
        super("SeqPathDM", "SeqPath");
        String id = "sp01";
        m_track = new FeatureTrack(this, id, "SeqPath Track", Color.GRAY, false,
                false, true, 1, id);
    }

    // /////////////////////////////////////////////////////////////////////////
    // SequenceManager Interface.
    // /////////////////////////////////////////////////////////////////////////

    public String getRaw(Segment segment) {
        return S.randomString("ATGC", segment.getLength());
    }

    // //////////////////////////////////////////////////////////////////////////
    // TrackManager Interface.
    // //////////////////////////////////////////////////////////////////////////

    public Feature[] getFeatures(Segment segment, FeatureTrack track) {
        return (Feature[]) m_featureList.toArray(new Feature[0]);
    }

    public FeatureTrack[] getTracks() {
        return new FeatureTrack[] { m_track };
    }

    public FeatureTrack[] getTracks(Sequence sequence) {
        return getTracks();
    }

    // /////////////////////////////////////////////////////////////////////////
    // SequenceFileLoader Interface.
    // /////////////////////////////////////////////////////////////////////////

    public String[] getExtensions() {
        return new String[] { "xml" };
    }

    public String[] getMimeTypes() {
        return new String[] { "seqPath", "text/seqPath" };
    }

    public String getDescription() {
        return "XML Files";
    }

    public Sequence[] loadSequenceFile(File file) {
        Sequence sequence = null;
        try {
            DocumentBuilderFactory factory = DocumentBuilderFactory
                    .newInstance();
            DocumentBuilder builder = factory.newDocumentBuilder();
            Document document = builder.parse(file);
            NodeList regionNodeList = document
                    .getElementsByTagName(SeqPathDomReference.SEQPATH_COVERAGE_REGION);
            if (regionNodeList == null) {
                return null;
            }
            for (int i = 0; i < regionNodeList.getLength(); i++) {
                Element seqElement = (Element) regionNodeList.item(i);
                sequence = new Sequence(this, getETV(seqElement,
                        SPDR.REGION_NAME), seqElement
                        .getAttribute(SPDR.SEQPATH_REGION_ID), 0, this);
                // sequenceList.add(sequence);
                m_sequenceList.add(sequence);
                generateFeatures(sequence, seqElement);
            }
        } catch (Exception e) {
            I.error("Error reading seqpath xml file", file, e);
        }
        return new Sequence[] { sequence };
    }

    // /////////////////////////////////////////////////////////////////////////
    // Private Methods.
    // /////////////////////////////////////////////////////////////////////////

    private void generateFeatures(Sequence sequence, Element seqElement) {
        NodeList featureNodeList = seqElement
                .getElementsByTagName(SPDR.SEQPATH_COVERAGE);
        if (featureNodeList != null) {
            for (int i = 0; i < featureNodeList.getLength(); i++) {
                generateFeature(sequence, (Element) featureNodeList.item(i));
            }
        }
    }

    private void generateFeature(Sequence sequence, Element featureElement) {
        if (featureElement == null) {
            return;
        }
        String coverageId = featureElement
                .getAttribute(SPDR.SEQPATH_COVERAGE_ID);
        String contigStart = getETV(featureElement, SPDR.CONTIG_START);
        String contigStop = getETV(featureElement, SPDR.CONTIG_STOP);
        String relativePos = getETV(featureElement,
                SPDR.RELATIVE_CONTIG_POSITION);
        String cloneId = getETV(featureElement, SPDR.CLONE_ID);
        String gi = getETV(featureElement, SPDR.GENBANK_ACCESSION);
        String isDuplicate = getETV(featureElement, SPDR.IS_DUPLICATE);
        if ("Y".equals(isDuplicate.toString())) {
            return;
        }
        if (contigStart == null && contigStop == null) {
            return;
        }
        int start = 0;
        int stop = 0;
        if (contigStart != null) {
            start = Integer.parseInt(contigStart);
        }
        if (contigStop != null) {
            stop = Integer.parseInt(contigStop);
        }
        if (stop <= start) {
            stop = start + GAP_SIZE;
        }
        if (stop > sequence.getLength()) {
            sequence.setUnOffsetLength(stop);
        }
        Feature feature = new CompoundFeature(sequence, coverageId, coverageId, start,
                stop, m_track, Strand.PLUS);
        // feature.setProperty("ssr", ssrPos);
        feature.setProperty("component_subtype", coverageId + "sub_type");
        if (gi != null) {
            feature.setProperty("Genbank_Accession", gi);
        }
        m_featureList.add(feature);
    }

    // ETV = Element Text Value. It's private, don't complain.
    private String getETV(Element e, String t) {
        if (e == null) {
            throw new IllegalArgumentException("Null element");
        }
        if (t == null) {
            throw new IllegalArgumentException("Null string");
        }
        NodeList nlist = e.getElementsByTagName(t);
        if (nlist == null) {
            return null;
        }
        Node item = nlist.item(0);
        if (item == null) {
            return null;
        }
        Node text = item.getFirstChild();
        if (text == null) {
            return null;
        }
        return text.getNodeValue();
    }

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

    private List m_sequenceList = new ArrayList();
    private List m_featureList = new ArrayList();
    // private List m_sequenceGroupList = new ArrayList();
    private Map m_sequenceGroupMap = new HashMap();
    private FeatureTrack m_track;
    private static int GAP_SIZE = 1000;
}

class SPDR extends SeqPathDomReference {
    // saves typing.
}
