/*
* Copyright (c) 2012 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.variant.variantcontext;


// the imports for unit testing.


import htsjdk.tribble.util.ParsingUtils;
import htsjdk.variant.VariantBaseTest;
import htsjdk.variant.utils.GeneralUtils;
import org.testng.Assert;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


public class GenotypesContextUnitTest extends VariantBaseTest {
    Allele Aref, C, T;
    Genotype AA, AT, TT, AC, CT, CC, MISSING;
    List<Genotype> allGenotypes;

    @BeforeSuite
    public void before() {
        C = Allele.create("C");
        Aref = Allele.create("A", true);
        T = Allele.create("T");
        AA = GenotypeBuilder.create("AA", Arrays.asList(Aref, Aref));
        AT = GenotypeBuilder.create("AT", Arrays.asList(Aref, T));
        TT = GenotypeBuilder.create("TT", Arrays.asList(T, T));
        AC = GenotypeBuilder.create("AC", Arrays.asList(Aref, C));
        CT = GenotypeBuilder.create("CT", Arrays.asList(C, T));
        CC = GenotypeBuilder.create("CC", Arrays.asList(C, C));
        MISSING = GenotypeBuilder.create("MISSING", Arrays.asList(C, C));

        allGenotypes = Arrays.asList(AA, AT, TT, AC, CT, CC);
    }

    // --------------------------------------------------------------------------------
    //
    // Provider
    //
    // --------------------------------------------------------------------------------

    private interface ContextMaker {
        public GenotypesContext make(List<Genotype> initialSamples);
    }

    private ContextMaker baseMaker = new ContextMaker() {
        @Override
        public GenotypesContext make(final List<Genotype> initialSamples) {
            return GenotypesContext.copy(initialSamples);
        }

        @Override
        public String toString() {
            return "GenotypesContext";
        }
    };

    private final class lazyMaker implements LazyGenotypesContext.LazyParser, ContextMaker {
        @Override
        public LazyGenotypesContext.LazyData parse(final Object data) {
            GenotypesContext gc = GenotypesContext.copy((List<Genotype>)data);
            gc.ensureSampleNameMap();
            gc.ensureSampleOrdering();
            return new LazyGenotypesContext.LazyData(gc.notToBeDirectlyAccessedGenotypes, gc.sampleNamesInOrder, gc.sampleNameToOffset);
        }

        @Override
        public GenotypesContext make(final List<Genotype> initialSamples) {
            return new LazyGenotypesContext(this, initialSamples, initialSamples.size());
        }

        @Override
        public String toString() {
            return "LazyGenotypesContext";
        }
    }

    private Collection<ContextMaker> allMakers = Arrays.asList(baseMaker, new lazyMaker());

    private class GenotypesContextProvider {
        String name;
        ContextMaker maker;
        final List<Genotype> initialSamples;

        private GenotypesContextProvider(ContextMaker maker, List<Genotype> initialSamples) {
            this.name = String.format("%s with %d samples", maker.toString(), initialSamples.size());
            this.maker = maker;
            this.initialSamples = initialSamples;
        }

        public GenotypesContext makeContext() {
            return maker.make(initialSamples);
        }
    }

    @DataProvider(name = "GenotypesContextProvider")
    public Object[][] MakeSampleNamesTest() {
        List<Object[]> tests = new ArrayList<Object[]>();

        for ( ContextMaker maker : allMakers ) {
            for ( int i = 0; i < allGenotypes.size(); i++ ) {
                List<Genotype> samples = allGenotypes.subList(0, i);
                // sorted
                tests.add(new Object[]{new GenotypesContextProvider(maker, samples)});
                // unsorted
                tests.add(new Object[]{new GenotypesContextProvider(maker, GeneralUtils.reverse(samples))});
            }
        }

        return tests.toArray(new Object[][]{});
    }

    private final static void testIterable(Iterable<Genotype> genotypeIterable, Set<String> expectedNames) {
        int count = 0;
        for ( final Genotype g : genotypeIterable ) {
            Assert.assertTrue(expectedNames.contains(g.getSampleName()));
            count++;
        }
        Assert.assertEquals(count, expectedNames.size(), "Iterable returned unexpected number of genotypes");
    }

    @Test(dataProvider = "GenotypesContextProvider")
    public void testInitialSamplesAreAsExpected(GenotypesContextProvider cfg) {
        testGenotypesContextContainsExpectedSamples(cfg.makeContext(), cfg.initialSamples);
    }

    private final void testGenotypesContextContainsExpectedSamples(GenotypesContext gc, List<Genotype> expectedSamples) {
        Assert.assertEquals(gc.isEmpty(), expectedSamples.isEmpty());
        Assert.assertEquals(gc.size(), expectedSamples.size());

        // get(index) is doing the right thing
        for ( int i = 0; i < expectedSamples.size(); i++ ) {
            Assert.assertEquals(gc.get(i), expectedSamples.get(i));
        }
        Assert.assertFalse(gc.containsSample(MISSING.getSampleName()));

        // we can fetch samples by name
        final Set<String> genotypeNames = VariantContextUtils.genotypeNames(expectedSamples);
        for ( final String name : genotypeNames ) {
            Assert.assertTrue(gc.containsSample(name));
        }
        Assert.assertFalse(gc.containsSample(MISSING.getSampleName()));

        // all of the iterators are working
        testIterable(gc.iterateInSampleNameOrder(), genotypeNames);
        testIterable(gc, genotypeNames);
        testIterable(gc.iterateInSampleNameOrder(genotypeNames), genotypeNames);
        if ( ! genotypeNames.isEmpty() ) {
            Set<String> first = Collections.singleton(genotypeNames.iterator().next());
            testIterable(gc.iterateInSampleNameOrder(first), first);
        }

        // misc. utils are working as expected
        assertEqualsSet(gc.getSampleNames(), genotypeNames, "gc sample names vs. expected sample names");
        Assert.assertTrue(ParsingUtils.isSorted(gc.getSampleNamesOrderedByName()));
        Assert.assertTrue(ParsingUtils.isSorted(gc.iterateInSampleNameOrder()));
        Assert.assertTrue(gc.containsSamples(genotypeNames));

        final Set<String> withMissing = new HashSet<String>(Arrays.asList(MISSING.getSampleName()));
        withMissing.addAll(genotypeNames);
        Assert.assertFalse(gc.containsSamples(withMissing));
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testImmutable(GenotypesContextProvider cfg) {
        GenotypesContext gc = cfg.makeContext();
        Assert.assertEquals(gc.isMutable(), true);
        gc.immutable();
        Assert.assertEquals(gc.isMutable(), false);
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider", expectedExceptions = Throwable.class )
    public void testImmutableCall1(GenotypesContextProvider cfg) {
        GenotypesContext gc = cfg.makeContext();
        gc.immutable();
        gc.add(MISSING);
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testClear(GenotypesContextProvider cfg) {
        GenotypesContext gc = cfg.makeContext();
        gc.clear();
        testGenotypesContextContainsExpectedSamples(gc, Collections.<Genotype>emptyList());
    }

    private static final List<Genotype> with(List<Genotype> genotypes, Genotype ... add) {
        List<Genotype> l = new ArrayList<Genotype>(genotypes);
        l.addAll(Arrays.asList(add));
        return l;
    }

    private static final List<Genotype> without(List<Genotype> genotypes, Genotype ... remove) {
        List<Genotype> l = new ArrayList<Genotype>(genotypes);
        l.removeAll(Arrays.asList(remove));
        return l;
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testAdds(GenotypesContextProvider cfg) {
        Genotype add1 = GenotypeBuilder.create("add1", Arrays.asList(Aref, Aref));
        Genotype add2 = GenotypeBuilder.create("add2", Arrays.asList(Aref, Aref));

        GenotypesContext gc = cfg.makeContext();
        gc.add(add1);
        testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1));

        gc = cfg.makeContext();
        gc.add(add1);
        gc.add(add2);
        testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2));

        gc = cfg.makeContext();
        gc.addAll(Arrays.asList(add1, add2));
        testGenotypesContextContainsExpectedSamples(gc, with(cfg.initialSamples, add1, add2));
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testRemoves(GenotypesContextProvider cfg) {
        Genotype rm1 = AA;
        Genotype rm2 = AC;

        GenotypesContext gc = cfg.makeContext();
        if (gc.size() > 1) {
            Genotype rm = gc.get(0);
            gc.remove(rm);
            testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm));
        }

        gc = cfg.makeContext();
        gc.remove(rm1);
        testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1));

        gc = cfg.makeContext();
        gc.remove(rm1);
        gc.remove(rm2);
        testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1, rm2));

        gc = cfg.makeContext();
        gc.removeAll(Arrays.asList(rm1, rm2));
        testGenotypesContextContainsExpectedSamples(gc, without(cfg.initialSamples, rm1, rm2));

        gc = cfg.makeContext();
        HashSet<Genotype> expected = new HashSet<Genotype>();
        if ( gc.contains(rm1) ) expected.add(rm1);
        if ( gc.contains(rm2) ) expected.add(rm2);
        gc.retainAll(Arrays.asList(rm1, rm2));

        // ensure that the two lists are the same
        assertEqualsSet(new HashSet<Genotype>(gc.getGenotypes()), expected, "gc genotypes vs. expected");
        // because the list order can change, we use the gc's list itself
        testGenotypesContextContainsExpectedSamples(gc, gc.getGenotypes());
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testSet(GenotypesContextProvider cfg) {
        Genotype set = GenotypeBuilder.create("replace", Arrays.asList(Aref, Aref));
        int n = cfg.makeContext().size();
        for ( int i = 0; i < n; i++ ) {
            GenotypesContext gc = cfg.makeContext();
            Genotype setted = gc.set(i, set);
            Assert.assertNotNull(setted);
            ArrayList<Genotype> l = new ArrayList<Genotype>(cfg.initialSamples);
            l.set(i, set);
            testGenotypesContextContainsExpectedSamples(gc, l);
        }
    }

    @Test(enabled = true, dataProvider = "GenotypesContextProvider")
    public void testReplace(GenotypesContextProvider cfg) {
        int n = cfg.makeContext().size();
        for ( int i = 0; i < n; i++ ) {
            GenotypesContext gc = cfg.makeContext();
            Genotype toReplace = gc.get(i);
            Genotype replacement = GenotypeBuilder.create(toReplace.getSampleName(), Arrays.asList(Aref, Aref));
            gc.replace(replacement);
            ArrayList<Genotype> l = new ArrayList<Genotype>(cfg.initialSamples);
            l.set(i, replacement);
            Assert.assertEquals(replacement, gc.get(i));
            testGenotypesContextContainsExpectedSamples(gc, l);
        }
    }

    // subset to samples tested in VariantContextUnitTest
}
