package org.broad.tribble.source.query;

import org.apache.log4j.Logger;
import org.broad.tribble.Tribble;
import org.broad.tribble.index.Block;
import org.broad.tribble.index.Index;
import org.broad.tribble.index.IndexFactory;
import org.broad.tribble.readers.AsciiLineReader;
import org.broad.tribble.readers.LineReader;
import org.broad.tribble.source.BasicFeatureSource;
import org.broad.tribble.util.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * Created by IntelliJ IDEA.
 * User: jrobinso
 * Date: May 17, 2010
 * Time: 3:26:34 PM
 * To change this template use File | Settings | File Templates.
 */
public class AsciiQuerySource implements QuerySource {

    private static Logger log = Logger.getLogger(BasicFeatureSource.class);

    protected SeekableStream seekableStream;
    protected Index index;
    long markPosition;

    public AsciiQuerySource(String featureFile, String indexFile) throws IOException {
        seekableStream = SeekableStreamFactory.getStreamFor(featureFile);
        index = IndexFactory.loadIndex(indexFile);
    }

    public AsciiQuerySource(String featureFile) throws IOException {
        String indexFile = Tribble.indexFile(featureFile);
        seekableStream = SeekableStreamFactory.getStreamFor(featureFile);
        index = IndexFactory.loadIndex(indexFile);
    }

    public AsciiQuerySource(String featureFile, Index indexInstance) throws IOException {
        seekableStream = SeekableStreamFactory.getStreamFor(featureFile);
        this.index = indexInstance;
    }


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

    public LineReader iterate() throws IOException {
        seekableStream.seek(0);
        return new AsciiLineReader(seekableStream);
    }

    /**
     * Mark the current stream position
     */
    public void mark() {
        try {
            markPosition = seekableStream.position();
        } catch (IOException e) {
            log.error("Error setting mark", e);
        }
    }

    /**
     * AsciiQuerySource does not support mark/reset.  Returns false always.
     * @return false.
     */
    public boolean markSupported() {
        return true;
    }

    /**
     * AsciiQuerySource does not support mark/reset.  Throws UnsupportedOperationException.
     */
    public void reset()  {
        try {
            seekableStream.seek(markPosition);
        } catch (IOException e) {
            log.error("Error resetting position", e);
        }
    }


    public LineReader query(String chr, int start, int end) {
        return new IndexedReader(chr, start, end);
    }

    public List<String> getSequenceNames() {
        return new ArrayList<String>(index.getSequenceNames());
    }



    public class IndexedReader implements LineReader {

        String chr;
        int start;
        int end;

        AsciiLineReader reader;
        List<Block> blocks;


        // Return a reader to loop over the whole file

        public IndexedReader() {
            reader = new AsciiLineReader(seekableStream);
        }

        // Return a reader to loop over to stream over a query inerval

        public IndexedReader(String chr, int start, int end) {
            this.chr = chr;
            this.start = start;
            this.end = end;
            init();
        }

        public String readLine() throws IOException {
            return reader == null ? null : reader.readLine();
        }

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


        /**
         * Initialize the reader
         * <p/>
         * todo -- the block collection is really not used, this implementation will stream over entire extent
         * todo -- covered by range of blocks.
         */
        private void init() {

            if (index == null) {
                throw new UnsupportedOperationException("Files must be indexed to support query methods");
            }

            blocks = index.getBlocks(chr, start, end);
            //log.info(String.format("Query %s %d-%d resulting in %d blocks (first start is %d) for index %s",
            //        chr, start, end, blocks != null ? blocks.size() : -1, blocks != null ? blocks.get(0).getStartPosition() : -1, index));

            if (blocks == null || blocks.size() == 0) {
                // No features for this query
                reader = null;

            } else {
                Block firstBlock = blocks.get(0);
                try {
                    seekableStream.seek(firstBlock.getStartPosition());
                    reader = new AsciiLineReader(seekableStream);
                } catch (IOException ex) {
                    log.error("Error seeking to position: " + firstBlock.getStartPosition(), ex);
                    // TODO -- throw application exception?
                }
            }

        }
    }

}
