/*
 * Copyright (C) 2009-2011 Institute for Computational Biomedicine,
 *                    Weill Medical College of Cornell University
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.campagnelab.goby.alignments.processors;

import com.google.protobuf.ByteString;
import edu.cornell.med.icb.identifier.IndexedIdentifier;
import it.unimi.dsi.fastutil.ints.IntAVLTreeSet;
import it.unimi.dsi.fastutil.ints.IntArraySet;
import it.unimi.dsi.fastutil.ints.IntSortedSet;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.objects.ObjectListIterator;
import it.unimi.dsi.lang.MutableString;
import org.campagnelab.goby.alignments.Alignments;
import org.campagnelab.goby.alignments.ConcatSortedAlignmentReader;
import org.campagnelab.goby.reads.RandomAccessSequenceInterface;
import org.campagnelab.goby.util.KnownIndelSet;
import org.campagnelab.goby.util.WarningCounter;
import org.campagnelab.goby.util.dynoptions.DynamicOptionClient;
import org.campagnelab.goby.util.dynoptions.RegisterThis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;

/**
 * Support to realign reads on the fly in the proximity of indels. This implementation starts randomly filtering out alignments
 * from the source if more than 500,000 entries make it into the sliding realignment window. The more alignments are added
 * past the threshold the more difficult it become to add new ones. This strategy helps consuming all memory in the realignment
 * step working with alignments that have artefactual peaks of very high coverage.
 *
 * @author Fabien Campagne
 *         Date: Apr 30, 2011
 *         Time: 11:58:07 AM
 *         <p>
 */
public class RealignmentProcessor implements AlignmentProcessorInterface {

    public static final DynamicOptionClient doc() {
        return doc;
    }

    @RegisterThis
    public static final DynamicOptionClient doc = new DynamicOptionClient(RealignmentProcessor.class,
            "known-indel-set:path to set of known indels generated by VCFToKnownIndelsMode"
    );

    private IndexedIdentifier targetIdentifiers;
    int windowLength = 0;

    int currentTargetIndex = -1;
    private int numTargets;
    private KnownIndelSet knownIndels;

    private int processedCount;
    private int numEntriesRealigned;
    private GenomeAlignmentTargetMapper targetMapper;

    @Override
    public int getModifiedCount() {
        return numEntriesRealigned;
    }

    @Override
    public int getProcessedCount() {
        return processedCount;
    }

    /**
     * The targetIndex that was active and for which we may still have entries stored in the corresponding pool:
     */
    private int previousActiveTargetIndex = -1;
    /**
     * The FIFO queue that holds target indices that have entries pooled in tinfo:
     */
    private IntSortedSet activeTargetIndices = new IntAVLTreeSet();
    private WarningCounter genomeNull = new WarningCounter(2);


    private ObjectArrayList<InfoForTarget> targetInfo = new ObjectArrayList<InfoForTarget>();

    final private SkipToIterator iterator;
    private RandomAccessSequenceInterface genome;






    private void initalizeKnownIndels(){
        String knownIndelsPath = doc.getString("known-indel-set");
        if (knownIndelsPath != null && knownIndelsPath.length()>0){
            try {
                knownIndels = new KnownIndelSet(knownIndelsPath);
            } catch (IOException e) {
                e.printStackTrace();
                throw new RuntimeException("Error loading known indel set: " + knownIndelsPath);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
                throw new RuntimeException("Error findind KnownIndelSetCreator class.");
            }
        }
    }

    public RealignmentProcessor(final ConcatSortedAlignmentReader sortedReaders) {
        targetIdentifiers = sortedReaders.getTargetIdentifiers();
        iterator = new SkipToSortedReader(sortedReaders);
        numTargets = sortedReaders.getNumberOfTargets();
        targetInfo = new ObjectArrayList<InfoForTarget>(numTargets);
        initalizeKnownIndels();
    }

    public RealignmentProcessor(final ObjectListIterator<Alignments.AlignmentEntry> entryIterator) {
        iterator = new SkipToListIterator(entryIterator);
        initalizeKnownIndels();
    }

    int enqueuedCount = 0;

    public Alignments.AlignmentEntry nextRealignedEntry(final int targetIndex, final int position) throws IOException {

        boolean mustLoadPool;
        if (activeTargetIndices.isEmpty()) {
            // nothing seen yet, load the pool
            mustLoadPool = true;
        } else {
            //determine if the pool has enough entry within windowSize:
            InfoForTarget backTargetInfo = targetInfo.get(activeTargetIndices.firstInt());
            InfoForTarget frontTargetInfo = targetInfo.get(activeTargetIndices.lastInt());
            // windowLength is zero at the beginning
            int wl = windowLength == 0 ? 1000 : windowLength;
            mustLoadPool = backTargetInfo.entriesInWindow.isEmpty() ||
                    currentTargetIndex == -1 ||
                    (activeTargetIndices.firstInt() == currentTargetIndex &&
                            backTargetInfo.maxEntryPosition < backTargetInfo.windowStartPosition + wl) ||
                    (activeTargetIndices.lastInt() > currentTargetIndex &&
                            frontTargetInfo.entriesInWindow.isEmpty());
        }

        if (mustLoadPool) {
            // fill the window pool only if we don't have enough entries to return already.
            int windowStartPosition = Integer.MAX_VALUE;
            int minTargetIndex = Integer.MAX_VALUE;
            // push entries to the pool until we have a pool windowLength wide:
            Alignments.AlignmentEntry entry;
            do {
                entry = iterator.skipTo(targetIndex, position);

                if (entry != null) {
//if (entry.getPosition()==11056072) {
//    System.out.println("STOP");
//}
                    final int entryTargetIndex = entry.getTargetIndex();
                    minTargetIndex = Math.min(minTargetIndex, entry.getTargetIndex());
                    currentTargetIndex = minTargetIndex;
                    // push new targetIndices to activeTargetIndices:
                    if (activeTargetIndices.isEmpty() || activeTargetIndices.lastInt() != entryTargetIndex) {
                        //     LOG.info("Adding targetIndex: " + entryTargetIndex + " at back of activeTargetIndices ");
                        activeTargetIndices.add(entryTargetIndex);
                    }
                    final InfoForTarget frontInfo = reallocateTargetInfo(entryTargetIndex);
                    // System.out.printf("windowStartPosition=%,d %n",windowStartPosition);

                    pushEntryToPool(frontInfo, position, entry);

                    windowStartPosition = frontInfo.windowStartPosition;
                } else if (activeTargetIndices.isEmpty()) {
                    // entry == null and none stored in windows
                    // we could not find anything to return at all. Return null here.
                    this.targetInfo.clear();
                    return null;

                }
            }
            while (entry != null &&
                    entry.getTargetIndex() == activeTargetIndices.firstInt() &&
                    entry.getPosition() < windowStartPosition + windowLength);
        }

        // check if we still have entries in the previously active target:
        int backTargetIndex = activeTargetIndices.firstInt();
        while (targetInfo.get(backTargetIndex).entriesInWindow.isEmpty()) {
            activeTargetIndices.rem(backTargetIndex);

            targetInfo.get(backTargetIndex).clear();
            if (activeTargetIndices.isEmpty()) {
                // no more targets, we are done.
                this.targetInfo.clear();
                return null;
            }
            backTargetIndex = activeTargetIndices.firstInt();
        }

        final InfoForTarget backInfo = targetInfo.get(backTargetIndex); // the pool info we will use to dequeue the entry at the back of the window
        //  System.out.printf("back is holding %d entries %n", backInfo.entriesInWindow.size());

        if (backInfo.entriesInWindow.isEmpty() && activeTargetIndices.isEmpty()) {
            this.targetInfo.clear();
            return null;
        }
// now find the entry at the left of the realignment window on the active target:
        Alignments.AlignmentEntry returnedEntry = backInfo.remove();

        if (backInfo.positionsWithSpanningIndel.size() > 0) {
            returnedEntry = realign(returnedEntry, backInfo);
        }
        if (returnedEntry.getTargetIndex() > currentTargetIndex) {
            for (int i = 0; i < returnedEntry.getTargetIndex(); i++) {
                pruneTargetInfo(i);
            }
        }
        // advance the windowStartPosition
        int previousWindowStart = backInfo.windowStartPosition;
        int windowStartPosition = Math.max(backInfo.windowStartPosition, returnedEntry.getPosition());
        // remove indel locations that are now outside the new window position
        if (previousWindowStart != windowStartPosition) {
            int lastPosition = windowStartPosition - 1;

            backInfo.removeIndels(previousWindowStart, lastPosition);
        }
        backInfo.windowStartPosition = windowStartPosition;
        ++processedCount;

        return returnedEntry;


    }

    private void pruneTargetInfo(int targetIndex) {
        this.targetInfo.get(targetIndex).clear();
    }


    private InfoForTarget reallocateTargetInfo(int targetIndex) {
        int intermediateTargetIndex = targetInfo.size() - 1;
        while (intermediateTargetIndex <= targetIndex) {
            if (knownIndels != null){
                try {
                    intermediateTargetIndex++;
                    targetInfo.add(new InfoForTarget(intermediateTargetIndex,knownIndels.getAllIndelsInChrom(targetMapper.getAlignmentId(currentTargetIndex))));
                }
                catch (NullPointerException e){
                    System.out.println("There is something wrong with the indel set. Perhaps it is aligned to a different genome than the reads?");
                    e.printStackTrace();
                    System.exit(1);
                }
            } else {
                targetInfo.add(new InfoForTarget(++intermediateTargetIndex));
            }
            numTargets = targetInfo.size();
        }
        return targetInfo.get(targetIndex);

    }

    private final boolean[] directions = new boolean[]{true, false};

    private Alignments.AlignmentEntry realign(final Alignments.AlignmentEntry entry, InfoForTarget tinfo) {
        int currentBestScore = 0;
        ObservedIndel bestScoreIndel = null;
        boolean bestScoreDirection = false;
        for (ObservedIndel indel : tinfo.potentialIndels) {
            if (entryOverlapsIndel(indel, entry)) {

                for (boolean direction : directions) {

                    final int realignedScore = score(entry, indel, direction, currentBestScore, genome);
                    if (realignedScore > currentBestScore) {
                        currentBestScore = realignedScore;
                        bestScoreIndel = indel;
                        bestScoreDirection = direction;
                    }
                }
            }
        }
        if (currentBestScore <= 0) {
            return entry;
        } else {
            // actually modify entry to realign through the indel:
            ++numEntriesRealigned;
            return realign(entry, bestScoreIndel, bestScoreDirection, currentBestScore);
        }

    }

    /**
     * Return true if the alignment overlaps the indel.
     *
     * @param indel
     * @param entry
     * @return
     */
    private boolean entryOverlapsIndel(final ObservedIndel indel, final Alignments.AlignmentEntry entry) {
        final int entryStart = entry.getPosition();
        final int entryEnd = entryStart + entry.getTargetAlignedLength();
        final int indelStart = indel.getStart();
        final int indelEnd = indel.getEnd();
        return entryStart <= indelStart && indelEnd <= entryEnd ||
                entryStart < indelEnd && entryEnd > indelStart ||
                entryEnd > indelStart && entryStart < indelEnd;
    }

    private Alignments.AlignmentEntry realign(Alignments.AlignmentEntry entry,
                                              ObservedIndel indel,
                                              boolean shiftForward, int scoreDelta) {
        // use entry as prototype:
        Alignments.AlignmentEntry.Builder builder = Alignments.AlignmentEntry.newBuilder(entry);
        // update the score to reflect the realignment:
        builder.setScore(entry.getScore() + scoreDelta);
        final int indelLength = indel.positionSpan();
        // reference insertions: target aligned length increases by the length of gaps in the reads, targetAlignedLength is incremented with the length of the indel.
        // read insertions: target aligned length already correct.
        int zeroWhenReadInsertion = 1;
        if (indel.isReadInsertion()) zeroWhenReadInsertion = 0;

        builder.setTargetAlignedLength(builder.getTargetAlignedLength() + indelLength * zeroWhenReadInsertion);
        int entryPosition = entry.getPosition();
        final int originalEntryPosition = entryPosition;
        if (!shiftForward && indel.isReferenceInsertion()) {
            // shifting to the left a read insertion, must shift alignment start position to the right
            entryPosition = entry.getPosition() - indelLength;
            builder.setPosition(entryPosition);

            //  when entry position changes, we need to update the pool to reflect the new entry sort order
            // this is accomplished by wrapping this processor with a LocalSortProcessor instance.
        }
        final int indelOffsetInAlignment = indel.getStart() - entryPosition;

        final int varCount = entry.getSequenceVariationsCount();
        final int targetIndex = entry.getTargetIndex();
        int score = 0;
        final int direction = shiftForward ? 1 : -1;
        if (genome == null) {
            genomeNull.warn(LOG, "Genome must not be null outside of Junit tests.");
            return entry;
        }
        //      if (indel.isReadInsertion())  zeroWhenReadInsertion=0;
        /*
         *Reference positions for which the alignment does not agree with the reference, 0-based:
         */
        IntArraySet variantPositions = new IntArraySet();
        // determine if rewrittenVariations become compatible with reference when indel is introduced in this alignment:
        // increase the score by 1 for every base that becomes compatible.
        ObjectArrayList<Alignments.SequenceVariation> rewrittenVariations = new ObjectArrayList<Alignments.SequenceVariation>();
        for (int i = 0; i < varCount; i++) {
            Alignments.SequenceVariation var = entry.getSequenceVariations(i);
            // check if var becomes compatible with reference when var's varPosition is shifted by the length of the indel in the specified shiftForward
            // newGenomicPosition is zero-based
            final int newGenomicPosition = var.getPosition() + (direction * indelLength * zeroWhenReadInsertion) + originalEntryPosition - 1;
            for (int j = 0; j < var.getTo().length(); ++j) {

                final char toBase = var.getTo().charAt(j);
                final int index = newGenomicPosition + j;
                if (index < 0 || index > genome.getLength(targetMapper.toGenome(targetIndex))) {
                    score += -10;
                } else {
                    final boolean compatible = genome.get(targetMapper.toGenome(targetIndex),
                            newGenomicPosition + j) == toBase;
                    if (!compatible) {
                        // we keep only sequence variations that continue to be incompatible with the reference after inserting the indel:

                        rewrittenVariations.add(var);
                    }

                    variantPositions.add(var.getPosition() + entryPosition + j - 1);
                }
            }

        }
        if (indel.isReadInsertion()) zeroWhenReadInsertion = 0;
        // Determine which previously unchanged bases become incompatible with the reference when the the indel is introduced
        // startAlignment and endAlignment are zero-based
        int startAlignment = shiftForward ? entryPosition + indelOffsetInAlignment : entryPosition;
        int endAlignment = shiftForward ? entry.getTargetAlignedLength() + entryPosition : indelOffsetInAlignment + entryPosition + (direction * indelLength * zeroWhenReadInsertion);
        //  String pre = getGenomeSegment(genome, targetIndex, startAlignment, endAlignment);
        //  String post = getGenomeSegment(genome, targetIndex, startAlignment + (direction * indelLength), endAlignment + (direction * indelLength));
        //   System.out.printf(" pre and post alignments: %n%s\n%s%n", pre, post);
        // pos is zero-based:
        for (int pos = startAlignment; pos < endAlignment; pos++) {
            // both variantPositions and pos are zero-based:
            if (!variantPositions.contains(pos)) {
                // this base matched the reference sequence:
                final int realignedPos = pos + (direction * indelLength * zeroWhenReadInsertion);
                // if the realigned varPosition lies outside of the reference penalize heavily with -10, otherwise
                // count -1 for every new mismatch introduced by the indel:
                if (realignedPos >= 0) {

                    int genomeTargetIndex = targetMapper.toGenome(targetIndex);
                    final char fromBase = genome.get(genomeTargetIndex, realignedPos);
                    final char toBase = genome.get(genomeTargetIndex, pos);
                    final boolean compatible = fromBase == toBase;

                    if (!compatible) {
                        Alignments.SequenceVariation.Builder varBuilder = Alignments.SequenceVariation.newBuilder();
                        // varPosition is one-based while realignedPos and entryPos are zero-based:
                        final int varPosition = direction * (realignedPos - entryPosition) + 1;
                        varBuilder.setPosition(varPosition);
                        varBuilder.setFrom(Character.toString(fromBase));
                        varBuilder.setTo(Character.toString(toBase));
                        varBuilder.setToQuality(byteArray((byte) Byte.MAX_VALUE));
                        int readIndex = entry.getMatchingReverseStrand() ?
                                entry.getQueryLength() - indelOffsetInAlignment + (shiftForward ? 1 : indelLength) :
                                varPosition;

                        varBuilder.setReadIndex(readIndex);
                        rewrittenVariations.add(varBuilder.build());

                    }
                } else {
                    int genomeTargetIndex = targetMapper.toGenome(targetIndex);
                    LOG.warn(
                            String.format("Realigned position cannot be negative." +
                                            " Ignoring this entry: %s%n" +
                                            "Error encountered at targetIndex=%d targetId=%s pos=%d %n",
                                    entry.toString(), targetIndex, genome.getReferenceName(genomeTargetIndex), pos));
                    // returning source entry without any changes:
                    return entry;
                }
            }
        }
        // finally, add the indel into the revised alignment:
        Alignments.SequenceVariation.Builder varBuilder = Alignments.SequenceVariation.newBuilder();

        //  fix varPosition for negative strand, var positions are one-based:
        final int varPosition = shiftForward ? indelOffsetInAlignment + 1 : indel.getStart() - entryPosition + 1;
        varBuilder.setPosition(varPosition);
        varBuilder.setFrom(indel.from);
        varBuilder.setTo(indel.to);
        // we set readIndex to the left most varPosition before the read gap, by convention, see  http://tinyurl.com/goby-sequence-variations (read deletion)
        int readIndex = entry.getMatchingReverseStrand() ?
                entry.getQueryLength() - indelOffsetInAlignment + (shiftForward ? 1 : indelLength) :
                varPosition;

        varBuilder.setReadIndex(readIndex);
        rewrittenVariations.add(varBuilder.build());
        builder = builder.clearSequenceVariations();
        for (Alignments.SequenceVariation var : rewrittenVariations) {
            builder = builder.addSequenceVariations(var);
        }
        final Alignments.AlignmentEntry alignmentEntry = builder.build();
        //    System.out.printf("realigned queryIndex=%d%n", alignmentEntry.getQueryIndex());
        return alignmentEntry;
    }

    private ByteString byteArray(byte... a) {
        return ByteString.copyFrom(a);
    }


    /**
     * Score the realignment of an entry with respect to a potential indel.
     *
     * @param entry            The entry to score as if it was realigned with respect to the indel
     * @param indel            The indel under consideration.
     * @param shiftForward     Whether the indel should be introduced by shifting bases forward
     * @param currentBestScore The current maximum score over a set of indels under consideration.  @return The score obtained when realigning the entry with respect to the provided indel.
     * @param genome           The genome to use to lookup reference bases
     * @return The score that would be observed if the indel was inserted into the alignment represented by entry.
     */

    public final int score(final Alignments.AlignmentEntry entry, final ObservedIndel indel, final boolean shiftForward, final int currentBestScore,
                           final RandomAccessSequenceInterface genome) {
        int entryPosition = entry.getPosition();
        int indelOffsetInAlignment = indel.getStart() - entryPosition;
        int indelLength = indel.positionSpan();
        int varCount = entry.getSequenceVariationsCount();
        int targetIndex = entry.getTargetIndex();
        int score = 0;
        int direction = shiftForward ? 1 : -1;
        int genomeTargetIndex = targetMapper.toGenome(targetIndex);
        if (genomeTargetIndex == -1) {
            throw new RuntimeException(String.format("alignment target index  %d (chromosome %s) has no equivalent in the genome.", targetIndex, targetMapper.getAlignmentId(targetIndex)));
        }
        if (genome == null) {
            genomeNull.warn(LOG, "Genome must not be null outside of JUnit tests.");
            return Integer.MIN_VALUE;
        }
        int zeroWhenReadInsertion = 1;
        final boolean readInsertion = indel.isReadInsertion();
        if (readInsertion) zeroWhenReadInsertion = 0;
        final int targetLength = genome.getLength(genomeTargetIndex);
        /*
         *Reference positions for which the alignment does not agree with the reference, 0-based:
         */


        IntArraySet variantPositions = new IntArraySet();
        // determine if variations become compatible with reference when indel is introduced in this alignment:
        // increase the score by 1 for every base that becomes compatible.
        for (int i = 0; i < varCount; i++) {
            Alignments.SequenceVariation var = entry.getSequenceVariations(i);
            // check if var becomes compatible with reference when var's position is shifted by the length of the indel in the specified shiftForward
            // newGenomicPosition is zero-based 
            // final int originalGenomicPosition = var.getPosition() + entryPosition - 1;
            final int newGenomicPosition = var.getPosition() + (direction * indelLength*zeroWhenReadInsertion) + entryPosition - 1;
            for (int j = 0; j < var.getTo().length(); ++j) {

                if (var.getFrom().charAt(j) == '-') {
                    // var is a read insertion, it is not a mismatch.
                    score-=1;
                    continue;
                }
                final char toBase = var.getTo().charAt(j);
                final int index = newGenomicPosition + j;
                if (index < 0 || index > genome.getLength(genomeTargetIndex)) {
                    score += -20;
                } else {
                    final boolean compatible = genome.get(genomeTargetIndex, newGenomicPosition + j) == toBase;

                    score += compatible ? 1 : -1;
//                    if (entry.getQueryIndex() == 6612162) {
//                        MutableString ms = new MutableString();
//                        GenomeDebugHelper.showLocation(genome, genomeTargetIndex, newGenomicPosition + j);
//                        System.out.println("range: " + ms.toString());
//                        System.out.println("------------------------- score= " + score);
//                    }
                    // store which reference positions are different from the reference:

                    variantPositions.add(var.getPosition() + entryPosition + j - 1);
                }
            }

        }
        if (score <= currentBestScore) {
            // the score can only get worse from now on and it is already not a worthfull candidate. Do not work on it
            // anymore.
            return score;
        }

        if (readInsertion) zeroWhenReadInsertion = 0;
        // Determine which previously unchanged bases become incompatible with the reference when the the indel is introduced
        // Consider the span of reference between the indel insertion point and the end of the reference alignment going in the direction of extension.
        // startAlignment and endAlignment are zero-based
        int startAlignment = shiftForward ? entryPosition + indelOffsetInAlignment : entryPosition;
        int endAlignment = shiftForward ? entry.getTargetAlignedLength() + entryPosition : indelOffsetInAlignment + entryPosition + (direction * indelLength * zeroWhenReadInsertion);
/*
        String pre = getGenomeSegment(genome, genomeTargetIndex, startAlignment, endAlignment);
        String post = getGenomeSegment(genome, genomeTargetIndex, startAlignment + indelLength, endAlignment + indelLength);
        System.out.printf(" pre and post alignments: %n%s\n%s%n", pre, post);
*/
        // pos is zero-based:
        endAlignment = Math.min(endAlignment, genome.getLength(genomeTargetIndex) - 1);

        for (int currentPositionOnReference = startAlignment; currentPositionOnReference < endAlignment; currentPositionOnReference++) {
            // both variantPositions and currentPositionOnReference are zero-based:
            if (!variantPositions.contains(currentPositionOnReference)) {
                // this base matched the reference sequence:
                final int realignedPosOnReference = currentPositionOnReference + (direction * indelLength );
                // if the realigned position lies outside of the reference penalize heavily with -10, otherwise
                // count -1 for every new mismatch introduced by the indel:

                if (realignedPosOnReference < 0 || realignedPosOnReference >= targetLength) {
                    score += -10;
                } else {
                    final char refBase = genome.get(genomeTargetIndex, currentPositionOnReference);
                    final char newRefBase = genome.get(genomeTargetIndex, realignedPosOnReference);

                    score += (refBase == newRefBase) ? 0 : -1;
                }
                if (score <= currentBestScore) {
                    // the score can only get worse from now on and it is already not a worthfull candidate. Do not work on it
                    // anymore.
                    return score;
                }
            }
             //System.out.printf("indelOffsetInAlignment: %d shiftForward: %b score: %d%n", indelOffsetInAlignment, shiftForward, score);
        }

   //     System.out.printf("indelOffsetInAlignment: %d shiftForward: %b score: %d%n", indelOffsetInAlignment, shiftForward, score);
        return score;
    }

    private String getGenomeSegment(RandomAccessSequenceInterface genome, int targetIndex, int startAlignment, int endAlignment) {
        MutableString sequence = new MutableString();
        for (int pos = startAlignment; pos < endAlignment; pos++) {
            sequence.append(genome.get(targetMapper.toAlignment(targetIndex), pos));
        }
        return sequence.toString();
    }


    public void pushEntryToPool(InfoForTarget tinfo, int position, Alignments.AlignmentEntry entry) {
        // the window start is only decreased in the pushing step, never increased.
        final int entryPosition = entry.getPosition();
        tinfo.windowStartPosition = Math.min(tinfo.windowStartPosition, entryPosition);
        tinfo.maxEntryPosition = Math.max(tinfo.maxEntryPosition, entryPosition);
        // set window length to twice the longest read length.
        windowLength = Math.max(windowLength, entry.getQueryLength() * 2);

        // detect if the entry contains an indel. Update the window indel state accordingly
        for (int i = 0; i < entry.getSequenceVariationsCount(); ++i) {
            final Alignments.SequenceVariation var = entry.getSequenceVariations(i);
            if (isIndel(var)) {
                // start and last position are zero-based:          A--CAC start=1 end=3
                final int startPosition = var.getPosition() + entryPosition - 1;
                final int lastPosition = var.getPosition() + entryPosition +
                        Math.max(var.getFrom().length(), var.getTo().length()) - 1;

                tinfo.addIndel(startPosition, lastPosition, var.getFrom(), var.getTo());

            }
        }

        enqueuedCount += tinfo.add(entry) ? 1 : 0;

    }

    private boolean isIndel(Alignments.SequenceVariation var) {
        return (var.getFrom().indexOf('-') >= 0 ||
                var.getTo().indexOf('-') >= 0);
    }


    public void setGenome(RandomAccessSequenceInterface genome) {
        this.genome = genome;
        targetMapper = new DummyGenomeAlignmentTargetMapper(genome);
    }

    public void setGenome(RandomAccessSequenceInterface genome, IndexedIdentifier targetIdentifiers) {
        this.genome = genome;
        assert targetIdentifiers != null : "Target identifiers must be initialized";
        targetMapper = new GenomeAlignmentTargetMapper(targetIdentifiers, genome);
    }

    private static final Logger LOG = LoggerFactory.getLogger(RealignmentProcessor.class);
}
