/*
 * 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.SamInputResource;
import htsjdk.samtools.SamReader;
import htsjdk.samtools.SamReaderFactory;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.io.ByteArrayInputStream;

/**
 * @author alecw@broadinstitute.org
 */
public class SamLocusIteratorTest {
    private SamReader createSamFileReader(final String samExample) {
        final ByteArrayInputStream inputStream = new ByteArrayInputStream(samExample.getBytes());
        return SamReaderFactory.makeDefault().open(SamInputResource.of(inputStream));
    }

    private SamLocusIterator createSamLocusIterator(final SamReader samReader) {
        final SamLocusIterator ret = new SamLocusIterator(samReader);
        ret.setEmitUncoveredLoci(false);
        return ret;
    }

    @Test
    public void testBasicIterator() {

        final String sqHeader = "@HD\tSO:coordinate\tVN:1.0\n@SQ\tSN:chrM\tAS:HG18\tLN:100000\n";
        final String seq1 = "ACCTACGTTCAATATTACAGGCGAACATACTTACTA";
        final String qual1 = "++++++++++++++++++++++++++++++++++++"; // phred 10
        final String s1 = "3851612\t16\tchrM\t165\t255\t36M\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String exampleSam = sqHeader + s1 + s1;

        final SamReader samReader = createSamFileReader(exampleSam);
        final SamLocusIterator sli = createSamLocusIterator(samReader);


        // make sure we accumulated depth of 2 for each position
        int pos = 165;
        for (final SamLocusIterator.LocusInfo li : sli) {
            Assert.assertEquals(pos++, li.getPosition());
            Assert.assertEquals(2, li.getRecordAndPositions().size());
        }

    }

    @Test
    public void testEmitUncoveredLoci() {

        final String sqHeader = "@HD\tSO:coordinate\tVN:1.0\n@SQ\tSN:chrM\tAS:HG18\tLN:100000\n";
        final String seq1 = "ACCTACGTTCAATATTACAGGCGAACATACTTACTA";
        final String qual1 = "++++++++++++++++++++++++++++++++++++"; // phred 10
        final String s1 = "3851612\t16\tchrM\t165\t255\t36M\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String exampleSam = sqHeader + s1 + s1;

        final SamReader samReader = createSamFileReader(exampleSam);
        final SamLocusIterator sli = new SamLocusIterator(samReader);

        // make sure we accumulated depth of 2 for each position
        int pos = 1;
        final int coveredStart = 165;
        final int coveredEnd = CoordMath.getEnd(coveredStart, seq1.length());
        for (final SamLocusIterator.LocusInfo li : sli) {
            Assert.assertEquals(li.getPosition(), pos++);
            final int expectedReads;
            if (li.getPosition() >= coveredStart && li.getPosition() <= coveredEnd) {
                expectedReads = 2;
            } else {
                expectedReads = 0;
            }
            Assert.assertEquals(li.getRecordAndPositions().size(), expectedReads);
        }
        Assert.assertEquals(pos, 100001);

    }

    @Test
    public void testQualityFilter() {

        final String sqHeader = "@HD\tSO:coordinate\tVN:1.0\n@SQ\tSN:chrM\tAS:HG18\tLN:100000\n";
        final String seq1 = "ACCTACGTTCAATATTACAGGCGAACATACTTACTA";
        final String qual1 = "++++++++++++++++++++++++++++++++++++"; // phred 10
        final String qual2 = "+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*"; // phred 10,9...
        final String s1 = "3851612\t16\tchrM\t165\t255\t36M\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String s2 = "3851612\t16\tchrM\t165\t255\t36M\t*\t0\t0\t" + seq1 + "\t" + qual2 + "\n";
        final String exampleSam = sqHeader + s1 + s2;

        final SamReader samReader = createSamFileReader(exampleSam);
        final SamLocusIterator sli = createSamLocusIterator(samReader);
        sli.setQualityScoreCutoff(10);


        // make sure we accumulated depth 2 for even positions, 1 for odd positions
        int pos = 165;
        for (final SamLocusIterator.LocusInfo li : sli) {
            Assert.assertEquals((pos % 2 == 0) ? 1 : 2, li.getRecordAndPositions().size());
            Assert.assertEquals(pos++, li.getPosition());
        }

    }

    /**
     * Try all CIGAR operands (except H and P) and confirm that loci produced by SamLocusIterator are as expected.
     */
    @Test
    public void testSimpleGappedAlignment() {
        final String sqHeader = "@HD\tSO:coordinate\tVN:1.0\n@SQ\tSN:chrM\tAS:HG18\tLN:100000\n";
        final String seq1 = "ACCTACGTTCAATATTACAGGCGAACATACTTACTA";
        final String qual1 = "++++++++++++++++++++++++++++++++++++"; // phred 10
        final String s1 = "3851612\t16\tchrM\t165\t255\t3S3M3N3M3D3M3I18M3S\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String exampleSam = sqHeader + s1 + s1;

        final SamReader samReader = createSamFileReader(exampleSam);
        final SamLocusIterator sli = createSamLocusIterator(samReader);


        // make sure we accumulated depth of 2 for each position
        final int[] expectedReferencePositions = new int[]{
                // 3S
                165, 166, 167, // 3M
                // 3N
                171, 172, 173, // 3M
                // 3D
                177, 178, 179, // 3M
                // 3I
                180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197}; // 18M

        final int[] expectedReadOffsets = new int[]{
                // 3S
                3, 4, 5, // 3M
                // 3N
                6, 7, 8, // 3M
                // 3D
                9, 10, 11, // 3M
                // 3I
                15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 // 3M
        };
        int i = 0;
        for (final SamLocusIterator.LocusInfo li : sli) {
            Assert.assertEquals(li.getRecordAndPositions().size(), 2);
            Assert.assertEquals(li.getPosition(), expectedReferencePositions[i]);
            Assert.assertEquals(li.getRecordAndPositions().get(0).getOffset(), expectedReadOffsets[i]);
            Assert.assertEquals(li.getRecordAndPositions().get(1).getOffset(), expectedReadOffsets[i]);
            ++i;
        }
    }

    /**
     * Test two reads that overlap because one has a deletion in the middle of it.
     */
    @Test
    public void testOverlappingGappedAlignments() {
        final String sqHeader = "@HD\tSO:coordinate\tVN:1.0\n@SQ\tSN:chrM\tAS:HG18\tLN:100000\n";
        final String seq1 = "ACCTACGTTCAATATTACAGGCGAACATACTTACTA";
        final String qual1 = "++++++++++++++++++++++++++++++++++++"; // phred 10
        // Were it not for the gap, these two reads would not overlap
        final String s1 = "3851612\t16\tchrM\t165\t255\t18M10D18M\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String s2 = "3851613\t16\tchrM\t206\t255\t36M\t*\t0\t0\t" + seq1 + "\t" + qual1 + "\n";
        final String exampleSam = sqHeader + s1 + s2;

        final SamReader samReader = createSamFileReader(exampleSam);
        final SamLocusIterator sli = createSamLocusIterator(samReader);
        // 5 base overlap btw the two reads
        final int numBasesCovered = 36 + 36 - 5;
        final int[] expectedReferencePositions = new int[numBasesCovered];
        final int[] expectedDepths = new int[numBasesCovered];
        final int[][] expectedReadOffsets = new int[numBasesCovered][];

        int i;
        // First 18 bases are from the first read
        for (i = 0; i < 18; ++i) {
            expectedReferencePositions[i] = 165 + i;
            expectedDepths[i] = 1;
            expectedReadOffsets[i] = new int[]{i};
        }
        // Gap of 10, then 13 bases from the first read
        for (; i < 36 - 5; ++i) {
            expectedReferencePositions[i] = 165 + 10 + i;
            expectedDepths[i] = 1;
            expectedReadOffsets[i] = new int[]{i};
        }
        // Last 5 bases of first read overlap first 5 bases of second read
        for (; i < 36; ++i) {
            expectedReferencePositions[i] = 165 + 10 + i;
            expectedDepths[i] = 2;
            expectedReadOffsets[i] = new int[]{i, i - 31};

        }
        // Last 31 bases of 2nd read
        for (; i < 36 + 36 - 5; ++i) {
            expectedReferencePositions[i] = 165 + 10 + i;
            expectedDepths[i] = 1;
            expectedReadOffsets[i] = new int[]{i - 31};
        }

        i = 0;
        for (final SamLocusIterator.LocusInfo li : sli) {
            Assert.assertEquals(li.getRecordAndPositions().size(), expectedDepths[i]);
            Assert.assertEquals(li.getPosition(), expectedReferencePositions[i]);
            Assert.assertEquals(li.getRecordAndPositions().size(), expectedReadOffsets[i].length);
            for (int j = 0; j < expectedReadOffsets[i].length; ++j) {
                Assert.assertEquals(li.getRecordAndPositions().get(j).getOffset(), expectedReadOffsets[i][j]);
            }
            ++i;
        }
    }
}
