/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2007-2015 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 org.broad.igv.tools;

import org.broad.igv.AbstractHeadlessTest;
import org.broad.igv.prefs.IGVPreferences;
import org.broad.igv.feature.genome.Genome;
import org.broad.igv.prefs.PreferencesManager;
import org.broad.igv.tools.parsers.DataConsumer;
import org.broad.igv.track.TrackType;
import org.broad.igv.util.TestUtils;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;

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

import static junit.framework.Assert.assertEquals;


public class CoverageCounterTest extends AbstractHeadlessTest {

    static IGVPreferences preferenceManager;

    @BeforeClass
    public static void setUpClass() throws Exception {
        AbstractHeadlessTest.setUpClass();
        preferenceManager = PreferencesManager.getPreferences();
    }

    /**
     * Test the "mapping quality" flag.  Also indirectly tests the query parameters.
     */
    @Test @Ignore("Requires largedata bundle")
    public void testMappingQualityFlag() throws IOException {
        String bamURL = TestUtils.LARGE_DATA_DIR + "HG00171.hg18.bam";
        String queryString = "chr1:152522155-152522155";
        int minMapQuality = 40;
        File wigFile = new File(TestUtils.TMP_OUTPUT_DIR + "testMapQual.wig");
        int windowSize = 1;

        TestDataConsumer dc = new TestDataConsumer();

        CoverageCounter cc = new CoverageCounter(bamURL, dc, windowSize, 0, wigFile, genome, queryString, minMapQuality, 0);

        cc.parse();

        String totalCount = dc.attributes.get("totalCount");

        assertEquals("7", totalCount);
    }

    @Ignore    // The test file no longer exists
    @Test
    public void testPairFlag() throws Exception{
        String bamURL = "http://data.broadinstitute.org/igvdata/1KG/pilot2Bams/NA12878.SLX.bam";
        String queryString = "2:1000-1100";
        File wigFile = new File(TestUtils.TMP_OUTPUT_DIR + "testPair.wig");
        int windowSize = 1;

        TestDataConsumer dc = new TestDataConsumer();

        CoverageCounter cc = new CoverageCounter(bamURL, dc, windowSize, 0, wigFile, genome, queryString, 0, CoverageCounter.PAIRED_COVERAGE);

        cc.parse();

        //Have manually checked these regions and verified that there is 1 pair
        //in the 1000-1100 region, and 4 at location 851
        //with
        for(TestData td: dc.testDatas){
            assertEquals(1.0f, td.data[0]);
        }


        queryString = "2:851";
        dc = new TestDataConsumer();

        cc = new CoverageCounter(bamURL, dc, windowSize, 0, wigFile, genome, queryString, 0, CoverageCounter.PAIRED_COVERAGE);

        cc.parse();

        for(TestData td: dc.testDatas){
            assertEquals(4.0f, td.data[0]);
        }

    }

    /*
    Test whether we count the number of features correctly.
     */
    @Test
    public void testCountStrand() throws Exception {
        String ifile = TestUtils.DATA_DIR + "bed/Unigene.sample.sorted.bed";


        int expTot = 71;
        int windowSize = 25;

        File wigFile = null;

        int[] countFlags = new int[]{0, CoverageCounter.STRANDS_BY_READ, CoverageCounter.STRANDS_BY_FIRST_IN_PAIR,
                CoverageCounter.BASES,
                CoverageCounter.INCLUDE_DUPS, CoverageCounter.BASES + CoverageCounter.STRANDS_BY_READ,
                CoverageCounter.BASES + CoverageCounter.STRANDS_BY_FIRST_IN_PAIR};
        //We specifically do not test this, because it's unreliable. Some alignments default to assuming they
        //are the first in pair, but let secondinpair be none
        //countFlags = new int[]{CoverageCounter.STRANDS_BY_SECOND_IN_PAIR};
        int[] expectedTotal = new int[]{expTot, expTot, expTot, expTot, expTot, expTot, expTot, expTot};

        for (int ii = 0; ii < countFlags.length; ii++) {
            TestDataConsumer dc = new TestDataConsumer();
            CoverageCounter cc = new CoverageCounter(ifile, dc, windowSize, 0, wigFile, genome, null, 0, countFlags[ii]);
            cc.parse();

            String totalCount = dc.attributes.get("totalCount");
            int totCount = Integer.valueOf(totalCount);
            assertEquals(expectedTotal[ii], totCount);
        }
    }


    @Test
    public void testCountBases() throws Exception {
        String ifile = TestUtils.DATA_DIR + "sam/NA12878.muc1.test.sam";
        int expected_cols = 14;

        File wigFile = new File(TestUtils.TMP_OUTPUT_DIR, "testCountBases.wig");
        wigFile.deleteOnExit();
        int windowSize = 1;

        TestDataConsumer dc = new TestDataConsumer();
        int strandOptions = CoverageCounter.STRANDS_BY_READ + CoverageCounter.BASES;
        CoverageCounter cc = new CoverageCounter(ifile, dc, windowSize, 0, wigFile, genome, null, 0, strandOptions);
        cc.parse();


        int check_startpos = 153426135 - 1;
        Map<Byte, Integer> posCounts = new HashMap<Byte, Integer>();
        Map<Byte, Integer> negCounts = new HashMap<Byte, Integer>();
        byte[] keys = new byte[]{'A', 'C', 'G', 'T', 'N', CoverageCounter.DEL, CoverageCounter.INS};
        int[] posvals = new int[]{9, 0, 0, 2, 0, 0, 0};
        int[] negvals = new int[]{16, 0, 0, 0, 0, 0, 0};
        for (int ii = 0; ii < keys.length; ii++) {
            posCounts.put(keys[ii], posvals[ii]);
            negCounts.put(keys[ii], negvals[ii]);
        }

        for (TestData tdata : dc.testDatas) {

            float[] numbers = tdata.data;
            assertEquals(expected_cols, numbers.length);

            if (tdata.start == check_startpos) {
                for (int ii = 0; ii < posvals.length; ii++) {
                    assertEquals(posvals[ii], numbers[ii], 1e-2);
                    assertEquals(negvals[ii], numbers[ii + keys.length], 1e-2);
                }
            }
        }

    }

    /**
     * Test different strand options, just count output columns
     * and make sure we get the right number
     *
     * @throws Exception
     */

    public void testColumnCounts() throws Exception {
        String ifile = TestUtils.DATA_DIR + "sam/NA12878.muc1.test.sam";

        File wigFile = null;
        Genome genome = this.genome;
        int[] windowSizes = new int[]{1, 50, 100, 500};
        //All possible combinations of STRAND_XXX flags
        int[] strandops = new int[2];
        strandops[0] = 0;
        strandops[1] = CoverageCounter.STRANDS_BY_READ;

//        String[] otherflags = new String[]{CoverageCounter.FIRST_IN_PAIR, CoverageCounter.BASES,
//                CoverageCounter.FIRST_IN_PAIR + CoverageCounter.BASES};

//        for (String so : strandops) {
//            for (String of : otherflags) {
//                int expectedcols = so.contains(CoverageCounter.STRAND_SEPARATE) ? 2 : 1;
//                if (of.contains(CoverageCounter.BASES)) {
//                    expectedcols *= 5;
//                }
//
//                String strandOptions = so + of;
//                for (int windowSize : windowSizes) {
//                    TestDataConsumer dc = new TestDataConsumer();
//                    CoverageCounter cc = new CoverageCounter(ifile, dc, windowSize, 0, wigFile, genome, "sc=" + strandOptions);
//                    cc.parse();
//
//                    assertEquals(expectedcols, dc.testDatas.get(0).data.length);
//                }
//            }
//        }

    }

    @Test
    public void testIncludeDuplicatesFlag() throws IOException {
        String bamURL = "http://data.broadinstitute.org/igvdata/BodyMap/hg18/Merged/HBM.adipose.bam.sorted.bam";
        int options = CoverageCounter.INCLUDE_DUPS;
        String queryString = "chr1:153425249-153425249";
        int windowSize = 1;
        File wigFile = null;
        Genome genome = null;

        TestDataConsumer dc = new TestDataConsumer();

        CoverageCounter cc = new CoverageCounter(bamURL, dc, windowSize, 0, wigFile, genome, queryString, 0, options);

        cc.parse();

        String totalCount = dc.attributes.get("totalCount");

        assertEquals("22", totalCount);

    }


    static class TestDataConsumer implements DataConsumer {

        Map<String, String> attributes = new HashMap<String, String>();
        public ArrayList<TestData> testDatas = new ArrayList<TestData>();

        public void setType(String type) {

        }

        /**
         * Just write all the data a containment object
         *
         * @param chr
         * @param start
         * @param end
         * @param data
         * @param name
         */
        public void addData(String chr, int start, int end, float[] data, String name) {
            TestData newData = new TestData(chr, start, end, data.clone(), name);
            this.testDatas.add(newData);
        }

        public void parsingComplete() {

        }

        public void setTrackParameters(TrackType trackType, String trackLine, String[] trackNames) {

        }

        public void setTrackParameters(TrackType trackType, String trackLine, String[] trackNames, boolean b) {
            //To change body of implemented methods use File | Settings | File Templates.
        }

        public void setSortTolerance(int tolerance) {

        }

        public void setAttribute(String key, String value) {
            attributes.put(key, value);
        }
    }

    private static class TestData {
        public String chr;
        public int start;
        public int end;
        public float[] data;
        public String name;

        public TestData(String chr, int start, int end, float[] data, String name) {
            this.chr = chr;
            this.start = start;
            this.end = end;
            this.data = data;
            this.name = name;
        }
    }
}
