package htsjdk.samtools.util;

import htsjdk.HtsjdkTest;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class LocatableUnitTest extends HtsjdkTest{

    private static Locatable getLocatable(final String contig, final int start, final int end) {
        return new Locatable() {
            @Override
            public String getContig() {
                return contig;
            }

            @Override
            public int getStart() {
                return start;
            }

            @Override
            public int getEnd() {
                return end;
            }

            @Override
            public String toString() {
                return String.format("%s:%s-%s", contig, start, end);
            }
        };
    }

    @DataProvider(name = "IntervalSizeData")
    public Object[][] getIntervalSizeData() {
        // Intervals + expected sizes
        return new Object[][]{
                { getLocatable("1", 1, 1), 1 },
                { getLocatable("1", 1, 2), 2 },
                { getLocatable("1", 1, 10), 10 },
                { getLocatable("1", 2, 10), 9 },
                { getLocatable("1", 1,0), 0}
        };
    }

    @Test(dataProvider = "IntervalSizeData")
    public void testGetSize(final Locatable interval, final int expectedSize ) {
        Assert.assertEquals(interval.getLengthOnReference(), expectedSize, "size() incorrect for interval " + interval);
    }

    @DataProvider(name = "IntervalOverlapData")
    public static Object[][] getIntervalOverlapData() {
        final Locatable standardInterval = getLocatable("1", 10, 20);
        final Locatable oneBaseInterval = getLocatable("1", 10, 10);

        return new Object[][] {
                { standardInterval, getLocatable("2", 10, 20), false },
                { standardInterval, getLocatable("1", 1, 5), false },
                { standardInterval, getLocatable("1", 1, 9), false },
                { standardInterval, getLocatable("1", 1, 10), true },
                { standardInterval, getLocatable("1", 1, 15), true },
                { standardInterval, getLocatable("1", 10, 10), true },
                { standardInterval, getLocatable("1", 10, 15), true },
                { standardInterval, getLocatable("1", 10, 20), true },
                { standardInterval, getLocatable("1", 15, 20), true },
                { standardInterval, getLocatable("1", 15, 25), true },
                { standardInterval, getLocatable("1", 20, 20), true },
                { standardInterval, getLocatable("1", 20, 25), true },
                { standardInterval, getLocatable("1", 21, 25), false },
                { standardInterval, getLocatable("1", 25, 30), false },
                { oneBaseInterval, getLocatable("2", 10, 10), false },
                { oneBaseInterval, getLocatable("1", 1, 5), false },
                { oneBaseInterval, getLocatable("1", 1, 9), false },
                { oneBaseInterval, getLocatable("1", 1, 10), true },
                { oneBaseInterval, getLocatable("1", 10, 10), true },
                { oneBaseInterval, getLocatable("1", 10, 15), true },
                { oneBaseInterval, getLocatable("1", 11, 15), false },
                { oneBaseInterval, getLocatable("1", 15, 20), false },
                { standardInterval, null, false },
                { standardInterval, standardInterval, true },
        };
    }

    @Test(dataProvider = "IntervalOverlapData")
    public void testOverlap(final Locatable firstInterval, final Locatable secondInterval, final boolean expectedOverlapResult ) {
        Assert.assertEquals(firstInterval.overlaps(secondInterval), expectedOverlapResult,
                "overlap() returned incorrect result for intervals " + firstInterval + " and " + secondInterval);
    }

    @DataProvider(name = "overlapsWithMargin")
    public Object[][] overlapsWithMargin(){
        final Locatable standardInterval = getLocatable("1", 10, 20);
        final Locatable middleInterval = getLocatable("1", 100, 200);
        final Locatable zeroLengthInterval = getLocatable("1", 1, 0);

        return new Object[][] {
                { standardInterval, getLocatable("2", 10, 20), 100, false },
                { standardInterval, getLocatable("1", 1, 15), 0, true },
                { standardInterval, getLocatable("1", 30, 50), 9, false },
                { standardInterval, getLocatable("1", 30, 50), 10, true },
                { middleInterval, getLocatable("1", 50, 99), 0, false },
                { middleInterval, getLocatable("1", 50, 90), 9, false },
                { middleInterval, getLocatable("1", 50, 90), 10, true },
                { middleInterval, getLocatable("1", 150, 149), 0, true },
                { middleInterval, getLocatable("1", 100, 99), 0, true },
                { middleInterval, getLocatable("1", 99, 98), 0, false },
                { standardInterval, getLocatable(null, 10, 20), 100, false }
        };
    }

    @Test(dataProvider = "overlapsWithMargin")
    public void testOverlapWithMargin(final Locatable firstInterval, final Locatable secondInterval, int margin, final boolean expectedOverlapResult ) {
        Assert.assertEquals(firstInterval.withinDistanceOf(secondInterval, margin), expectedOverlapResult,
                "overlap() returned incorrect result for intervals " + firstInterval + " and " + secondInterval);
    }

    @DataProvider(name = "IntervalContainsData")
    public Object[][] getIntervalContainsData() {
        final Locatable containingInterval = getLocatable("1", 10, 20);
        final Locatable zeroLengthIntervalBetween9And10 = getLocatable("1", 10, 9);
        return new Object[][] {
                { containingInterval, getLocatable("2", 10, 20), false },
                { containingInterval, getLocatable("1", 1, 5), false },
                { containingInterval, getLocatable("1", 1, 10), false },
                { containingInterval, getLocatable("1", 5, 15), false },
                { containingInterval, getLocatable("1", 9, 10), false },
                { containingInterval, getLocatable("1", 9, 20), false },
                { containingInterval, getLocatable("1", 10, 10), true },
                { containingInterval, getLocatable("1", 10, 15), true },
                { containingInterval, getLocatable("1", 10, 20), true },
                { containingInterval, getLocatable("1", 10, 21), false },
                { containingInterval, getLocatable("1", 15, 25), false },
                { containingInterval, getLocatable("1", 20, 20), true },
                { containingInterval, getLocatable("1", 20, 21), false },
                { containingInterval, getLocatable("1", 20, 25), false },
                { containingInterval, getLocatable("1", 21, 25), false },
                { containingInterval, getLocatable("1", 25, 30), false },
                { containingInterval, null, false },
                { containingInterval, containingInterval, true },
                { containingInterval, getLocatable(null, 10, 20), false},
                { getLocatable(null, 10, 20), getLocatable(null, 10, 20), false},

                //0 length intervals
                { containingInterval, zeroLengthIntervalBetween9And10, true},
                { containingInterval, getLocatable("1", 15, 14), true},
                { containingInterval, getLocatable("1", 21,20), true},
                { containingInterval, getLocatable("1", 25, 24), false},
                {zeroLengthIntervalBetween9And10, getLocatable("1", 9, 8), false},
                {zeroLengthIntervalBetween9And10, getLocatable("1", 11, 10), false},

                //0 length interval is considered to contain itself
                {zeroLengthIntervalBetween9And10, zeroLengthIntervalBetween9And10, true}
        };
    }

    @Test(dataProvider = "IntervalContainsData")
    public void testContains(final Locatable firstInterval, final Locatable secondInterval, final boolean expectedContainsResult ) {
        Assert.assertEquals(firstInterval.contains(secondInterval), expectedContainsResult,
                "contains() returned incorrect result for intervals " + firstInterval + " and " + secondInterval);
    }
}
