/*
 * Copyright (c) 2007-2010 by The Broad Institute, Inc. and the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * This software is licensed under the terms of the GNU Lesser General Public License (LGPL), Version 2.1 which
 * is available at http://www.opensource.org/licenses/lgpl-2.1.php.
 *
 * THE SOFTWARE IS PROVIDED "AS IS." THE BROAD AND MIT MAKE NO REPRESENTATIONS OR WARRANTIES OF
 * ANY KIND CONCERNING THE SOFTWARE, EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT
 * OR OTHER DEFECTS, WHETHER OR NOT DISCOVERABLE.  IN NO EVENT SHALL THE BROAD OR MIT, OR THEIR
 * RESPECTIVE TRUSTEES, DIRECTORS, OFFICERS, EMPLOYEES, AND AFFILIATES BE LIABLE FOR ANY DAMAGES OF
 * ANY KIND, INCLUDING, WITHOUT LIMITATION, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ECONOMIC
 * DAMAGES OR INJURY TO PROPERTY AND LOST PROFITS, REGARDLESS OF WHETHER THE BROAD OR MIT SHALL
 * BE ADVISED, SHALL HAVE OTHER REASON TO KNOW, OR IN FACT SHALL KNOW OF THE POSSIBILITY OF THE
 * FOREGOING.
 */

package org.broad.igv.track.tribble;

import net.sf.samtools.util.CloseableIterator;
import org.broad.igv.data.DataSource;
import org.broad.igv.feature.Feature;
import org.broad.igv.feature.Genome;
import org.broad.igv.feature.LocusScore;
import org.broad.igv.session.ViewContext;
import org.broad.igv.tdf.TDFDataSource;
import org.broad.igv.tdf.TDFReader;
import org.broad.igv.track.FeatureSource;
import org.broad.igv.util.ParsingUtils;
import org.broad.tribble.FeatureCodec;
import org.broad.tribble.FeatureReader;
import org.broad.tribble.readers.BasicFeatureReader;
import org.broad.tribble.util.CloseableTribbleIterator;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class TribbleFeatureSource implements FeatureSource {

    CachingFeatureReader reader;

    DataSource coverageSource;

    /**
     * Map of IGV chromosome name -> source name
     */
    Map<String, String> chrNameMap = new HashMap();
    private int binSize;
    Object header;

    public TribbleFeatureSource(String path) throws IOException {

        FeatureCodec codec = CodecFactory.getCodec(path);

        BasicFeatureReader basicReader = new BasicFeatureReader(path, codec);
        header = basicReader.getHeader();
        initBinSize(basicReader);
        reader = new CachingFeatureReader(basicReader, getBinSize());

        init();
        initCoverageSource(path + ".tdf");

    }

    private void initCoverageSource(String covPath) {
        if (ParsingUtils.pathExists(covPath))   {
            TDFReader reader = TDFReader.getReader(covPath);
            coverageSource = new TDFDataSource(reader, 0, "");
        }
    }

    private void init() {
        Genome genome = ViewContext.getInstance().getGenome();
        if (genome != null) {
            for (String seqName : reader.getSequenceNames()) {
                String igvChr = genome.getChromosomeAlias(seqName);
                if (igvChr != null && !igvChr.equals(seqName)) {
                    chrNameMap.put(igvChr, seqName);
                }
            }
        }
    }


    /**
     * Return features overlapping the query interval
     *
     * @param chr
     * @param start
     * @param end
     * @return
     * @throws IOException
     */
    public CloseableIterator<Feature> getFeatures(String chr, int start, int end) throws IOException {

        String seqName = chrNameMap.get(chr);
        if (seqName == null) seqName = chr;
        return reader.query(seqName, start, end);
    }

    /**
     * Return coverage values overlapping the query interval.   At this time Tribble sources do not provide
     * coverage values
     *
     * @param chr
     * @param start
     * @param end
     * @param zoom
     * @return
     */
    public List<LocusScore> getCoverageScores(String chr, int start, int end, int zoom) {
        return coverageSource == null ? null :
                coverageSource.getSummaryScoresForRange(chr, start, end, zoom);
    }

    public int getBinSize() {
        return binSize;
    }

    public void setBinSize(int size) {
        reader.setTileSize(size);
    }

    public Object getHeader() {
        return header;
    }


    private void initBinSize(FeatureReader reader) {
        int m = 10000;
        double density = estimateDensity(reader);
        if (density == -1) {
            binSize = 100000;
        } else {
            int tmp = (int) (m / density);
            binSize = Math.max(30000, tmp);
        }
    }

    private double estimateDensity(FeatureReader reader) {


        int number = 10000;
        int n = 0;
        org.broad.tribble.Feature firstFeature = null;
        org.broad.tribble.Feature lastFeature = null;
        CloseableTribbleIterator<org.broad.tribble.Feature> iter = null;

        try {
            iter = reader.iterator();

            while (iter.hasNext() && n < number) {
                org.broad.tribble.Feature f = iter.next();
                if (f != null) {
                    if (firstFeature == null) {
                        firstFeature = f;
                    } else {
                        lastFeature = f;
                    }
                    n++;
                }
            }
            if (n > 1) {
                return ((double) n) / (lastFeature.getStart() - firstFeature.getStart());
            } else {
                return -1;
            }
        } catch (IOException e) {
            return -1;
        }
        finally {
            if (iter != null) {
                iter.close();
            }
        }
    }


}
