/*
 * 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.
 */

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.broad.igv.sam.reader;

import net.sf.samtools.SAMFileHeader;
import net.sf.samtools.SAMFileReader;
import net.sf.samtools.SAMFileReader.ValidationStringency;
import net.sf.samtools.SAMRecord;
import net.sf.samtools.util.CloseableIterator;
import org.apache.log4j.Logger;
import org.broad.igv.Globals;
import org.broad.igv.exceptions.DataLoadException;
import org.broad.igv.sam.Alignment;
import org.broad.igv.sam.EmptyAlignmentIterator;
import org.broad.igv.sam.SamAlignment;
import org.broad.igv.util.FileUtils;
import org.broad.igv.util.SeekableStream;
import org.broad.igv.util.SeekableStreamFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;

/**
 * A wrapper for SamTextReader that supports query by interval.
 *
 * @author jrobinso
 */
public class SamQueryTextReader implements AlignmentQueryReader {

    static Logger log = Logger.getLogger(SamQueryTextReader.class);
    String samFile;
    SeekableStream stream;
    FeatureIndex featureIndex;
    SAMFileHeader header;


    public SamQueryTextReader(String samFile) throws IOException {
        this(samFile, true);
    }

    public SamQueryTextReader(String samFile, boolean requireIndex) throws IOException {
        this.samFile = samFile;
        stream = SeekableStreamFactory.getStreamFor(samFile);
        loadHeader();

        if (requireIndex) {
            featureIndex = SamUtils.getIndexFor(samFile);
            if (featureIndex == null) {
                throw new DataLoadException("Could not locate index file.", samFile);
            }
        }
    }

    public SAMFileHeader getHeader() {
        if (header == null) {
            loadHeader();
        }
        return header;
    }

    private void loadHeader() {
        SAMFileReader reader = new SAMFileReader(stream);
        header = reader.getFileHeader();
        reader.close();
    }

    public synchronized CloseableIterator<Alignment> query(final String sequence, final int start, final int end, final boolean contained) {

        if (featureIndex == null) {
            featureIndex = SamUtils.getIndexFor(samFile);
        }

        if (featureIndex == null) {
            throw new java.lang.UnsupportedOperationException("SAM files must be indexed to support query methods");
        }
        if (!featureIndex.containsChromosome(sequence)) {
            return EmptyAlignmentIterator.getInstance();
        }

        // If contained == false (include overlaps) we need to adjust the start to
        // ensure we get features that extend into this segment.  
        int startAdjustment = contained ? 0 : featureIndex.getLongestFeature(sequence);
        int startTileNumber = Math.max(0, (start - startAdjustment)) / featureIndex.getTileWidth();

        FeatureIndex.TileDef seekPos = featureIndex.getTileDef(sequence, startTileNumber);

        if (seekPos != null) {

            try {
                // Skip to the start of the chromosome (approximate)
                stream.close();
                stream = SeekableStreamFactory.getStreamFor(samFile);
                stream.seek(seekPos.getStartPosition());
                SAMFileReader reader = new SAMFileReader(stream);
                reader.setValidationStringency(ValidationStringency.SILENT);

                CloseableIterator<SAMRecord> iter = reader.iterator();
                return new SAMQueryIterator(sequence, start, end, contained, iter);

            } catch (IOException ex) {
                log.error("Error opening sam file", ex);
            }
        }
        return EmptyAlignmentIterator.getInstance();
    }

    public boolean hasIndex() {
        if (featureIndex == null) {
            getIndex();
        }
        return featureIndex != null;
    }

    public void close() throws IOException {
        if (stream != null) {
            stream.close();
            stream = null;
        }
    }


    private FeatureIndex getIndex() {
        if (featureIndex == null) {
            featureIndex = SamUtils.getIndexFor(samFile);
        }
        return featureIndex;
    }

    public Set<String> getSequenceNames() {
        FeatureIndex idx = getIndex();
        if (idx == null) {
            return null;
        } else {
            return idx.getIndexedChromosomes();
        }

    }

    public CloseableIterator<Alignment> iterator() {
        SAMFileReader reader = new SAMFileReader(stream);
        reader.setValidationStringency(ValidationStringency.SILENT);
        CloseableIterator<SAMRecord> iter = reader.iterator();
        return new SAMQueryIterator(iter);

    }

    /**
     *
     */
    class SAMQueryIterator implements CloseableIterator<Alignment> {

        String chr;
        int start;
        int end;
        boolean contained;
        SAMRecord currentRecord;
        CloseableIterator<SAMRecord> wrappedIterator;

        public SAMQueryIterator(CloseableIterator<SAMRecord> wrappedIterator) {
            this.chr = null;
            this.wrappedIterator = wrappedIterator;
            currentRecord = wrappedIterator.next();
        }

        public SAMQueryIterator(String sequence, int start, int end, boolean contained,
                                CloseableIterator<SAMRecord> wrappedIterator) {
            this.chr = sequence;
            this.start = start;
            this.end = end;
            this.contained = contained;
            this.wrappedIterator = wrappedIterator;
            advanceToFirstRecord();
        }

        private void advanceToFirstRecord() {
            while (wrappedIterator.hasNext()) {
                currentRecord = wrappedIterator.next();
                if (!currentRecord.getReferenceName().equals(chr)) {
                    break;
                } else if ((contained && currentRecord.getAlignmentStart() >= start) ||
                        (!contained && currentRecord.getAlignmentEnd() >= start)) {
                    break;
                }
            }
        }

        public void close() {
            wrappedIterator.close();
        }

        public boolean hasNext() {
            if (chr == null && currentRecord != null) {
                return true;
            }
            if (currentRecord == null || (chr != null && !chr.equals(currentRecord.getReferenceName()))) {
                return false;
            } else {
                return contained ? currentRecord.getAlignmentEnd() <= end
                        : currentRecord.getAlignmentStart() <= end;
            }
        }

        public SamAlignment next() {
            SAMRecord ret = currentRecord;
            if (wrappedIterator.hasNext()) {
                currentRecord = wrappedIterator.next();
            } else {
                currentRecord = null;
            }
            return new SamAlignment(ret);

        }

        public void remove() {
            //throw new UnsupportedOperationException("Not supported yet.");
        }
    }
}
