package de.renew.unify;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Vector;
import java.util.stream.Stream;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;


/**
 * JUnit test case for checking the {@link TupleIndex} and
 * related classes.
 * <p>
 * Created: Fri Nov  9  2001
 *
 * @author Michael Duvigneau
 */
public class TupleIndexTest {
    private static final Tuple TUPLE_1 =
        new Tuple(new Object[] { "a", new Object(), Boolean.TRUE }, null);
    private static final Tuple TUPLE_2 =
        new Tuple(new Object[] { new Tuple(new Object[] { "kuno" }, null) }, null);

    private static final Tuple A_1 = TUPLE_1;
    private static final Tuple A_2 = TUPLE_2;
    private static final Tuple A_3 = new Tuple(0);
    private static final List A_4 = new List(0);
    private static final List A_5 =
        new List(null, new List(TUPLE_2, new List(new List(0), new List(0), null), null), null);
    private static final List A_6 =
        new List(TUPLE_1, new List(TUPLE_2, new List(new List(0), new List(0), null), null), null);
    private static final Integer A_7 = 1;
    private static final Integer A_8 = 2;

    private static final Tuple B_1 = new Tuple(new Object[] { "a", "a", "a" }, null);
    private static final Tuple B_2 = new Tuple(new Object[] { "a", "a", null }, null);
    private static final Tuple B_3 = new Tuple(new Object[] { "a", "a", "c" }, null);
    private static final Tuple B_4 = new Tuple(new Object[] { "c", "a", "c" }, null);

    /**
     * _indexA contains:
     * <ul>
     * <li> a1 — {@code t1} </li>
     * <li> a2 — {@code t2} </li>
     * <li> a3 — {@code []} </li>
     * <li> a4 — {@code {}} </li>
     * <li> a5 — {@code {null, t2, {}}} </li>
     * <li> a6 — {@code {t1, t2, {}}} </li>
     * <li> a7 — {@code 1} </li>
     * <li> a8 — {@code 2} </li>
     * </ul>
     * {@code where t1 = ["a", new Object(), true] and t2 = [["kuno"]]}
     */
    private static final TupleIndex INDEX_A;
    /** _indexB contains:
     * <ul>
     * <li> b1 — {@code ["a", "a", "a"]} </li>
     * <li> b2 — {@code ["a", "a", null]} </li>
     * <li> b3 — {@code ["a", "a", "c"]} </li>
     * <li> b4 — {@code ["c", "a", "a"]} </li>
     * </ul>
     */
    private static final TupleIndex INDEX_B;

    static {
        // create indexA
        INDEX_A = new TupleIndex();
        INDEX_A.insert(A_1);
        INDEX_A.insert(A_2);
        INDEX_A.insert(A_3);
        INDEX_A.insert(A_4);
        INDEX_A.insert(A_5);
        INDEX_A.insert(A_6);
        INDEX_A.insert(A_7);
        INDEX_A.insert(A_8);

        // create indexB
        INDEX_B = new TupleIndex();
        INDEX_B.insert(B_1);
        INDEX_B.insert(B_2);
        INDEX_B.insert(B_3);
        INDEX_B.insert(B_4);
    }

    /**
     * Test method for {@link TupleIndex#insert} and {@link TupleIndex#remove}
     * Tests whether three different tuples that are equal but not the same
     * can be removed and reinserted into a {@code TupleIndex} after being
     * modified.
     */
    @Test
    public void testInsertAndRemoveModifyingEqualTuples() {
        //given
        TupleIndex index = new TupleIndex();
        Vector<String> v1 = new Vector<>();
        v1.add("eat");
        Vector<String> v2 = new Vector<>();
        v2.add("eat");
        Vector<String> v3 = new Vector<>();
        v3.add("eat");
        Tuple t1 = new Tuple(new Object[] { "a", v1 }, null);
        Tuple t2 = new Tuple(new Object[] { "b", v2 }, null);
        Tuple t3 = new Tuple(new Object[] { "c", v3 }, null);
        // Just to make it clear:
        // The three Vectors are not identical, but they are equal
        // because they have the same contents.
        assertEquals(v1, v2);
        assertEquals(v1, v3);
        assertEquals(v2, v3);
        assertNotSame(v1, v2);
        assertNotSame(v1, v3);
        assertNotSame(v2, v3);

        //when
        index.insert(t1);
        index.insert(t2);
        index.insert(t3);

        // What we want to simulate now:
        // A transition removes one token (vector) from the place
        // and modifies it.
        // This should be allowed and possible, in contrast to the
        // modification of the vector while it is in the place.
        index.remove(t1);
        v1.add("more");
        index.insert(t1);

        //then
        assertNotEquals(v1, v2);
        assertNotEquals(v1, v3);

        //when
        // Now comes the interesting part: Are the two other vectors
        // still accessible?
        index.remove(t2);
        index.remove(t3);
    }

    /**
     * Test method for {@link TupleIndex#getPossibleMatches}. Checks whether
     * the result from the method for the given index matches the given expected
     * results.
     *
     * @param index the index to search
     * @param pattern the pattern to look for in the given index
     * @param expected the expected result of the search
     */
    @ParameterizedTest
    @MethodSource("provideTupleIndexes")
    public void testGetPossibleMatches(TupleIndex index, Object pattern, Object[] expected) {
        //given
        java.util.List<Object> expectedList = Arrays.asList(expected);

        //when
        java.util.List<Object> actualResults = new ArrayList<>(index.getPossibleMatches(pattern));

        //then
        assertEquals(
            expectedList.size(), actualResults.size(),
            () -> "Unexpected number of results.\nPattern: " + pattern + "\nExpected: "
                + expectedList + "\nActual: " + actualResults);

        for (Object expectedItem : expectedList) {
            assertTrue(
                actualResults.contains(expectedItem), () -> "Expected item missing: " + expectedItem
                    + "\nActual results: " + actualResults);
        }

        for (Object actualItem : actualResults) {
            assertTrue(
                expectedList.contains(actualItem), () -> "Unexpected item found: " + actualItem
                    + "\nExpected results: " + expectedList);
        }
    }

    private static Stream<Arguments> provideTupleIndexes() {
        return Stream.of(
            Arguments.of(INDEX_A, A_1, new Object[] { A_1 }),
            Arguments.of(
                INDEX_A, new Unknown(), new Object[] { A_1, A_2, A_3, A_4, A_5, A_6, A_7, A_8 }),
            Arguments.of(INDEX_A, Boolean.FALSE, new Object[] { }),
            Arguments.of(INDEX_A, 2, new Object[] { A_8 }),
            Arguments.of(INDEX_A, new Tuple(0), new Object[] { A_3 }),
            Arguments.of(INDEX_A, new Tuple(3), new Object[] { A_1 }),
            Arguments.of(INDEX_A, new List(0), new Object[] { A_4 }),
            Arguments.of(INDEX_A, new List(2), new Object[] { A_5, A_6 }),
            Arguments.of(INDEX_A, new List(null, null, null), new Object[] { }),
            Arguments.of(INDEX_A, new List(A_1, new Unknown(), null), new Object[] { A_6 }),
            Arguments.of(
                INDEX_B, new Tuple(new Object[] { "a", new Unknown(), "c" }, null),
                new Object[] { B_3, B_4 }),
            Arguments
                .of(INDEX_B, new Tuple(new Object[] { "a", "a", "c" }, null), new Object[] { B_3 }),
            Arguments
                .of(INDEX_B, new Tuple(new Object[] { "c", "a", "a" }, null), new Object[] { }));
    }
}