/*
 * Copyright (c) 2009-2010 by The Broad Institute, Inc.
 * 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.tribble.index.linear;

import org.apache.log4j.Logger;
import org.broad.tribble.index.AbstractIndex;
import org.broad.tribble.index.Block;
import org.broad.tribble.index.Index;
import org.broad.tribble.index.IndexFactory;
import org.broad.tribble.util.LittleEndianInputStream;
import org.broad.tribble.util.LittleEndianOutputStream;

import java.io.*;
import java.util.*;

/**
 * Index defined by dividing the genome by chromosome, then each chromosome into bins of fixed width (in
 * genomic coordinates).   Features are allocated to bins by start position.  The longest feature in each
 * recorded and used to adjust the start position of a query to include all bins that might have a feature
 * that overlaps the query interval.  This works well for feature sets of approximately homogeneous length,
 * or whose longest feature is on the order of the bin width or less.
 * <p/>
 * magicNumber      integer
 * type             integer
 * version          integer
 * filename         null terminated character array
 * filesize         long
 * lastModified     long
 * md5              String
 * flags            integer
 * <p/>
 * ------  LINEAR INDEX
 * nChromosomes     integer
 */

public class LinearIndex extends AbstractIndex implements Index {

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

    public static final int CURRENT_VERSION = 2;

    /**
     * Width of each block in base pairs
     */
    private int defaultBinWidth;

    /**
     * Default constructor -- used by factory methods.  Do not remove.
     */
    public LinearIndex() {

    }

    public LinearIndex(int binWidth, String featureFile) {
        super(featureFile);
        this.version = CURRENT_VERSION;
        this.defaultBinWidth = binWidth;
    }

    @Override
    protected int getType() {
        return IndexFactory.LINEAR;
    }

    public Set<String> getIndexedChromosomes() {
        return chrIndeces.keySet();
    }

    /**
     * Record a bin position and size.
     */
    public void add(String chr, long idx, int size, int longestFeature) {

        ChrIndex chrIndex = (ChrIndex) chrIndeces.get(chr);
        if (chrIndex == null) {
            chrIndex = new ChrIndex(chr, defaultBinWidth);
            chrIndeces.put(chr, chrIndex);
        }
        chrIndex.updateLongestFeature(longestFeature);
        chrIndex.addBlock(new Block(idx, size));


    }


    public Set<String> getSequenceNames() {
        return (chrIndeces == null ? new HashSet() : chrIndeces.keySet());
    }

    @Override
    public Class getIndexClass() {
        return ChrIndex.class;
    }


    /**
     * check the current version against the version we read in
     *
     * @return true if we're up to date, false otherwise
     */
    public boolean isCurrentVersion() {
        return version == CURRENT_VERSION;
    }


    public static class ChrIndex implements org.broad.tribble.index.ChrIndex {
        private String name;
        private int binWidth;
        private int longestFeature;
        private int largestBlockSize;
        private int totalBlockSize;
        private List<Block> blocks;

        /**
         * Default constructor needed for factory methods -- DO NOT REMOVE
         */
        public ChrIndex() {

        }
        

        ChrIndex(LittleEndianInputStream dis) throws IOException {
            read(dis);
        }

        ChrIndex(String name, int binWidth) {
            this.name = name;
            this.binWidth = binWidth;
            this.blocks = new ArrayList(100);
            this.longestFeature = 0;
            this.largestBlockSize = 0;
            this.totalBlockSize = 0;
        }


        public String getName() {
            return name;
        }

        void addBlock(Block block) {
            blocks.add(block);
            totalBlockSize += block.getSize();
            largestBlockSize = Math.max(largestBlockSize, block.getSize());
        }

        public List<Block> getBlocks(int start, int end) {
            if (blocks == null || blocks.isEmpty()) {
                // TODO -- throw exception ?
                return null;
            }
            // Adjust position for the longest feature in this chromosome.  This insures we get features that start
            // before the bin but extend into it
            int adjustedPosition = Math.max(start - longestFeature, 0);

            int startBinNumber = Math.min(adjustedPosition / binWidth, blocks.size() - 1);
            // are we off the end of the bin list

            int endBinNumber = Math.min(end / binWidth, blocks.size() - 1);

            return blocks.subList(startBinNumber, endBinNumber + 1);
        }


        public void updateLongestFeature(int featureLength) {
            longestFeature = Math.max(longestFeature, featureLength);
        }

        public void write(LittleEndianOutputStream dos) throws IOException {

            // Chr name, binSize,  # bins,  longest feature
            dos.writeString(name);
            dos.writeInt(binWidth);
            dos.writeInt(blocks.size());
            dos.writeInt(longestFeature);
            dos.writeInt(largestBlockSize);
            dos.writeInt(totalBlockSize);

            long pos = 0;
            int size = 0;
            for (Block block : blocks) {
                pos = block.getStartPosition();
                size = block.getSize();
                dos.writeLong(pos);
            }
            // End of last block for this chromosome
            dos.writeLong(pos + size);
        }

        public void read(LittleEndianInputStream dis) throws IOException {
            name = dis.readString();
            binWidth = dis.readInt();
            int nBins = dis.readInt();
            longestFeature = dis.readInt();
            largestBlockSize = dis.readInt();
            totalBlockSize = dis.readInt();

            blocks = new ArrayList(nBins);
            long pos = dis.readLong();
            for (int binNumber = 0; binNumber < nBins; binNumber++) {
                long nextPos = dis.readLong();
                int size = (int) (nextPos - pos);
                blocks.add(new Block(pos, size));
                pos = nextPos;
            }

        }

    }
}

