/*
 * The MIT License
 *
 * Copyright (c) 2010 The Broad Institute
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package htsjdk.samtools.util;

import htsjdk.samtools.SAMFileHeader;

import java.util.BitSet;
import java.util.List;

/**
 * Serve up loci of interest based on an interval list.
 *
 * @author alecw at broadinstitute dot oh are gee
 */
public class IntervalListReferenceSequenceMask implements ReferenceSequenceMask {

    private final SAMFileHeader header;
    // if memory usage becomes a problem... this could be changed to a SparseBitSet
    // http://java.sun.com/developer/onlineTraining/collections/magercises/BitSet/index.html
    private final BitSet currentBitSet = new BitSet();
    private int currentSequenceIndex = -1;
    private final PeekableIterator<Interval> intervalIterator;
    private final int lastSequenceIndex;
    private final int lastPosition;

    public IntervalListReferenceSequenceMask(final IntervalList intervalList) {
        this.header = intervalList.getHeader();
        final List<Interval> uniqueIntervals = intervalList.uniqued().getIntervals();
        if (uniqueIntervals.isEmpty()) {
            lastSequenceIndex = -1;
            lastPosition = 0;
        } else {
            final Interval lastInterval = uniqueIntervals.get(uniqueIntervals.size() - 1);
            lastSequenceIndex = header.getSequenceIndex((lastInterval.getContig()));
            lastPosition = lastInterval.getEnd();
        }
        intervalIterator = new PeekableIterator<>(uniqueIntervals.iterator());
    }

    /**
     * It is required that sequenceIndex is >= any previous sequenceIndex passed to this class.
     *
     * @return true if the mask is set for the given sequence and position
     */
    @Override
    public boolean get(final int sequenceIndex, final int position) {
        ensureSequenceLoaded(sequenceIndex);
        return currentBitSet.get(position);
    }

    /**
     * It is required that sequenceIndex is >= any previous sequenceIndex passed to this class.
     *
     * @return the next pos on the given sequence >= position that is set, or -1 if there are no more set positions
     */
    @Override
    public int nextPosition(final int sequenceIndex, final int position) {
        ensureSequenceLoaded(sequenceIndex);
        // nextSetBit returns the first set bit on or after the starting index, therefore position+1
        return currentBitSet.nextSetBit(position + 1);
    }

    private void ensureSequenceLoaded(final int sequenceIndex) {
        if (sequenceIndex < this.currentSequenceIndex) {
            throw new IllegalArgumentException("Cannot look at an earlier sequence.  Current: " +
                    this.currentSequenceIndex + "; requested: " + sequenceIndex);
        }
        if (sequenceIndex > currentSequenceIndex) {
            currentBitSet.clear();
            while (intervalIterator.hasNext()) {
                final Interval interval = intervalIterator.peek();
                final int nextSequenceIndex = header.getSequenceIndex(interval.getContig());
                if (nextSequenceIndex < sequenceIndex) {
                    intervalIterator.next();
                } else if (nextSequenceIndex == sequenceIndex) {
                    currentBitSet.set(interval.getStart(), interval.getEnd() + 1);
                    intervalIterator.next();
                } else {
                    break;
                }
            }
            currentSequenceIndex = sequenceIndex;
        }
    }

    /**
     * @return Largest sequence index for which there are set bits.
     */
    @Override
    public int getMaxSequenceIndex() {
        return lastSequenceIndex;
    }

    /**
     * @return the largest position on the last sequence index
     */
    @Override
    public int getMaxPosition() {
        return lastPosition;
    }

    public SAMFileHeader getHeader() {
        return header;
    }
}
