package picard.analysis;

import htsjdk.samtools.SAMFileHeader;
import htsjdk.samtools.SAMSequenceRecord;
import htsjdk.samtools.util.Histogram;
import htsjdk.samtools.util.Interval;
import htsjdk.samtools.util.IntervalList;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import picard.PicardException;

/**
 * Tests for WgsMetrics.
 */
public class WgsMetricsTest {

    private Histogram<Integer> emptyDepthHistogram() { return new Histogram<>(); }
    private Histogram<Integer> singleDepthHistogram(final int depth, final int count) {
        final Histogram<Integer> histogram = new Histogram<>();
        histogram.increment(depth, count);
        return histogram;
    }
    private Histogram<Integer> twoSiteDepthHistogram(final int depth1, final int count1, final int depth2, final int count2) {
        final Histogram<Integer> histogram = new Histogram<>();
        if (0 < depth1) histogram.increment(depth1, count1);
        if (0 < depth2) histogram.increment(depth2, count2);
        return histogram;
    }

    private IntervalList buildIntervalList(final int start, final int end) {
        final SAMFileHeader header = new SAMFileHeader();
        header.addSequence(new SAMSequenceRecord("CONTIG", 100000000));
        final IntervalList intervals = new IntervalList(header);
        if (0 < start) intervals.add(new Interval("CONTIG", start, end));
        return intervals;
    }

    private WgsMetrics emptyMetrics() {
        return new WgsMetrics(
                buildIntervalList(-1, -1),
                emptyDepthHistogram(), emptyDepthHistogram(),
                0, 0, 0, 0, 0, 0, 0, 0, 1000000,
                null, -1
        );
    }

    private WgsMetrics singleDepthMetrics(final int depth, final int countScale, final int start) {
        final int count = 100000 * countScale;
        final int totalExcluded = (5 + 10 + 20 + 30 + 40 + 50 + 60) * countScale;
        return new WgsMetrics(
                buildIntervalList(start, start),
                singleDepthHistogram(depth, count),
                singleDepthHistogram(depth, count),5d * countScale / count,
                10d * countScale / count, 20d * countScale / count, 30d * countScale / count,
                40d * countScale / count, 50d * countScale / count, 60d * countScale / count,
                totalExcluded / (double) (count + totalExcluded),
                1000000,
                null, -1
        );
    }

    private WgsMetrics twoSiteDepthMetrics(final int depth1, final int countScale1,
                                                             final int depth2, final int countScale2,
                                                             final int start) {
        final int count1 = 100000 * countScale1;
        final int count2 = 100000 * countScale2;
        final int count  = count1 + count2;
        final int countScale = countScale1 + countScale2;
        final int totalExcluded = (5 + 10 + 20 + 30 + 40 + 50 + 60) * countScale;
        return new WgsMetrics(
                buildIntervalList(start, start+1),
                twoSiteDepthHistogram(depth1, count1, depth2, count2),
                twoSiteDepthHistogram(depth1, count1, depth2, count2),5d * countScale / count,
                10d * countScale / count, 20d * countScale / count, 30d * countScale / count,
                40d * countScale / count, 50d * countScale / count, 60d * countScale / count,
                totalExcluded / (double) (count + totalExcluded),
                100000,
                null, -1
        );
    }

    @Test(dataProvider = "testWgsMetricsMergeDataProvider")
    public void testWgsMetricsMerge(final WgsMetrics left,
                                    final WgsMetrics right,
                                    final WgsMetrics expected) {
        left.merge(right);
        left.calculateDerivedFields();
        Assert.assertTrue(left.equals(expected));
    }

    @DataProvider(name = "testWgsMetricsMergeDataProvider")
    public Object[][] testWgsMetricsMergeDataProvider() {
        return new Object[][] {
                {emptyMetrics(), emptyMetrics(), emptyMetrics()},
                {emptyMetrics(), singleDepthMetrics(1, 1, 1), singleDepthMetrics(1, 1, 1)},
                {singleDepthMetrics(1, 1, 1), emptyMetrics(), singleDepthMetrics(1, 1, 1)},
                {singleDepthMetrics(1, 1, 1), singleDepthMetrics(1, 1, 2), twoSiteDepthMetrics(1, 2, 0, 0, 1)},
                {singleDepthMetrics(1, 1, 1), singleDepthMetrics(1, 2, 2), twoSiteDepthMetrics(1, 3, 0, 0, 1)},
                {singleDepthMetrics(1, 1, 1), singleDepthMetrics(1, 1, 2), twoSiteDepthMetrics(1, 2, 0, 0, 1)},
                {singleDepthMetrics(1, 1, 1), singleDepthMetrics(2, 1, 2), twoSiteDepthMetrics(1, 1, 2, 1, 1)},
                {twoSiteDepthMetrics(1, 1, 2, 1, 1), twoSiteDepthMetrics(1, 1, 2, 1, 3), twoSiteDepthMetrics(1, 2, 2, 2, 1)}
        };
    }

    @Test(expectedExceptions = {PicardException.class})
    public void testMergeOverlappingIntervals() {
        singleDepthMetrics(1, 1, 1).merge(singleDepthMetrics(1, 1, 1));
    }
}
