package picard.analysis.artifacts;

import htsjdk.samtools.metrics.MetricsFile;
import htsjdk.samtools.util.IOUtil;
import org.testng.Assert;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;
import picard.cmdline.CommandLineProgramTest;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class CollectSequencingArtifactMetricsTest extends CommandLineProgramTest {

    private static final File TEST_DIR = new File("testdata/picard/analysis/artifacts/CollectSequencingArtifactMetrics");
    private static final File REFERENCE = new File(TEST_DIR, "test.fasta");
    private static final File TEST_SAM = new File(TEST_DIR, "test.sam");
    private static final File TEST_IUPAC = new File(TEST_DIR, "testWithIUPACBases.sam");
    private static final File DB_SNP = new File(TEST_DIR, "test.dbsnp.vcf");
    private static final File INTERVALS = new File(TEST_DIR, "test.interval_list");
    private static final File TEST_CASES = new File(TEST_DIR, "ExpectedMetricsOutput");

    private File globalTempOutputDir;

    @Override
    public String getCommandLineProgramName() {
        return CollectSequencingArtifactMetrics.class.getSimpleName();
    }

    @BeforeTest
    public void setUp() throws IOException {
        globalTempOutputDir = IOUtil.createTempDir("artifactMetrics.tmp").toFile();
    }

    @AfterTest
    public void tearDown() throws IOException {
        IOUtil.deleteDirectoryTree(globalTempOutputDir);
    }

    /**
     * Run the CLP using standard arguments and maybe some additional ones, then compare to the expected results.
     *
     * @param testCase name of test case (should match one of the file sets in {@code TEST_CASES}
     * @param extraArgs extra arguments of the form {@code "KEY1=VALUE1", "KEY2=VALUE2"}, etc. These can override standard args.
     * @return the base path of the output metrics
     */
    private void runAnalysis(final String testCase, final String ... extraArgs) throws IOException {
        final File actual = new File(globalTempOutputDir, testCase);
        final File expected = new File(TEST_CASES, testCase);

        final Map<String, String> args = new HashMap<String, String>();
        args.put("INPUT", TEST_SAM.getAbsolutePath());
        args.put("OUTPUT", actual.getAbsolutePath());
        args.put("REFERENCE_SEQUENCE", REFERENCE.getAbsolutePath());
        args.put("MINIMUM_INSERT_SIZE", "30"); // test data has this insert size
        args.put("MAXIMUM_INSERT_SIZE", "30"); // test data has this insert size
        args.put("CONTEXT_SIZE", "0"); // ignore context by default, to cut down on file size

        for (final String extraArg : extraArgs) {
            final String[] kv = extraArg.split("=");
            args.put(kv[0], kv[1]);
        }

        runPicardCommandLine(args);
        assertAllFilesEqual(expected, actual);
    }

    private void assertAllFilesEqual(final File expectedBase, final File actualBase) {
        Assert.assertTrue(areMetricsEqual(expectedBase, actualBase, SequencingArtifactMetrics.PRE_ADAPTER_SUMMARY_EXT),"Pre-Adapter summary files differ.");
        Assert.assertTrue(areMetricsEqual(expectedBase, actualBase, SequencingArtifactMetrics.PRE_ADAPTER_DETAILS_EXT),"Pre-Adapter details files differ.");
        Assert.assertTrue(areMetricsEqual(expectedBase, actualBase, SequencingArtifactMetrics.BAIT_BIAS_SUMMARY_EXT), "Bait-Bias summary files differ.");
        Assert.assertTrue(areMetricsEqual(expectedBase, actualBase, SequencingArtifactMetrics.BAIT_BIAS_DETAILS_EXT), "Bait-bias details files differ.");
        Assert.assertTrue(areMetricsEqual(expectedBase, actualBase, SequencingArtifactMetrics.ERROR_SUMMARY_EXT), "Error-summary files differ.");
    }

    private boolean areMetricsEqual(final File expectedBase, final File actualBase, final String extension) {
        return MetricsFile.areMetricsEqual(new File(expectedBase + extension), new File(actualBase + extension));
    }

    @Test
    public void testContext() throws IOException {
        runAnalysis("with_context", "CONTEXT_SIZE=1");
    }

    @Test
    public void testLargerContext() throws IOException {
        runAnalysis("with_larger_context", "CONTEXT_SIZE=2");
    }

    @Test
    public void testDbSnp() throws IOException {
        runAnalysis("with_dbsnp", "DB_SNP=" + DB_SNP);
    }

    @Test
    public void testIntervalList() throws IOException {
        runAnalysis("with_intervals", "INTERVALS=" + INTERVALS);
    }

    @Test
    public void testNoBqCutoff() throws IOException {
        runAnalysis("no_bq_cutoff", "MINIMUM_QUALITY_SCORE=0");
    }

    @Test
    public void testNoMqCutoff() throws IOException {
        runAnalysis("no_mq_cutoff", "MINIMUM_MAPPING_QUALITY=0");
    }

    @Test
    public void testUnmappedMate() throws IOException {
        runAnalysis("unmapped_mate", "MINIMUM_INSERT_SIZE=0", "MAXIMUM_INSERT_SIZE=0");
    }

    @Test
    private void runIUPACCase(){
        final File actual = new File(globalTempOutputDir, "IUPAC");

        final Map<String, String> args = new HashMap<>();
        args.put("INPUT", TEST_IUPAC.getAbsolutePath());
        args.put("OUTPUT", actual.getAbsolutePath());
        args.put("REFERENCE_SEQUENCE", REFERENCE.getAbsolutePath());
        args.put("MINIMUM_INSERT_SIZE", "30"); // test data has this insert size
        args.put("MAXIMUM_INSERT_SIZE", "30"); // test data has this insert size
        args.put("CONTEXT_SIZE", "0"); // ignore context by default, to cut down on file size

        Assert.assertEquals(runPicardCommandLine(args),0);
    }
}
