package prolog.builtins

import com.sun.source.tree.EmptyStatementTree
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import prolog.ast.lists.List.Cons
import prolog.ast.lists.List.Empty
import prolog.ast.terms.Atom
import prolog.ast.terms.Term
import prolog.ast.terms.Variable

class ListOperatorsTests {
    @Test
    fun `size empty list is 0`() {
        assertEquals(0, Empty.size.value, "Expected size of empty list to be 0")
    }

    @Test
    fun `size of singleton is 1`() {
        val list = Cons(Atom("a"), Empty)

        assertEquals(1, list.size.value, "Expected size of singleton list to be 1")
    }

    @Test
    fun `size of list with five elements is 5`() {
        val list = Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Cons(Atom("d"), Cons(Atom("e"), Empty)))))

        assertEquals(5, list.size.value, "Expected size of list with five elements to be 5")
    }

    @Test
    fun `member(a, list of a)`() {
        val atom = Atom("a")
        val list = Cons(atom, Empty)

        val member = Member(atom, list)

        val result = member.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")
    }

    @Test
    fun `member should only check shallow`() {
        val list = Cons(Atom("a"), Cons(Cons(Atom("b"), Empty), Empty))

        var result = Member(Atom("a"), list).satisfy(emptyMap()).toList()
        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")

        result = Member(Atom("b"), list).satisfy(emptyMap()).toList()
        assertEquals(0, result.size, "Expected no solution")
    }

    @Test
    fun `member with variable in list`() {
        val atom = Atom("a")
        val variable = Variable("X")
        val list = Cons(variable, Empty)

        val member = Member(atom, list)

        val result = member.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertEquals(atom, result[0].getOrNull()!![variable], "Expected variable to be unified with atom")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `appended empty lists is an empty list`() {
        val append = Append(Empty, Empty, Empty)

        val result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `appending two empty lists gives an empty list`() {
        val list1 = Empty
        val list2 = Empty
        val list12 = Variable("Result")

        val append = Append(list1, list2, list12)

        val result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        val subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(Empty, subs[list12], "Expected result to be empty list")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `appending an empty list to another list gives that list`() {
        val nonempty = Cons(Atom("a"), Empty)

        var append = Append(nonempty, Empty, Empty)
        var result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")

        append = Append(Empty, nonempty, Empty)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")

        val variable = Variable("List1AndList2")

        append = Append(Empty, nonempty, variable)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        var subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(nonempty, subs[variable], "Expected result to be nonempty list")

        append = Append(nonempty, Empty, variable)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `appending two lists gives combined list`() {
        val list1 = Cons(Atom("a"), Cons(Atom("b"), Empty))
        val list2 = Cons(Atom("c"), Cons(Atom("d"), Empty))
        val list3 = Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Cons(Atom("d"), Empty))))
        val list4 = Cons(Atom("c"), Cons(Atom("d"), Cons(Atom("a"), Cons(Atom("b"), Empty))))

        var append = Append(list1, list2, list3)
        var result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        assertTrue(result[0].getOrNull()!!.isEmpty(), "Expected empty substitution map")

        val variable = Variable("List1AndList2")

        append = Append(list1, list2, variable)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        var subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(list3, subs[variable], "Expected result to be combined list")

        append = Append(list2, list1, variable)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(list4, subs[variable], "Expected result to be combined list")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `you can find the appended list`() {
        val list1 = Cons(Atom("a"), Cons(Atom("b"), Empty))
        val list2: Term = Variable("List2")
        val list3 = Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Cons(Atom("d"), Empty))))

        var append = Append(list1, list2, list1)
        var result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        var subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(Empty, subs[list2], "Expected result to be empty list")

        append = Append(list1, list2, list3)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(Cons(Atom("c"), Cons(Atom("d"), Empty)), subs[list2], "Expected result to be list with c and d")
    }

    @Disabled("Not implemented yet")
    @Test
    fun `you can find the prepended list`() {
        val list1 = Variable("List1")
        val list2 = Cons(Atom("c"), Cons(Atom("d"), Empty))
        val list3 = Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Cons(Atom("d"), Empty))))

        var append = Append(list1, list2, list2)
        var result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        var subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(Empty, subs[list1], "Expected result to be empty list")

        append = Append(list1, list2, list3)
        result = append.satisfy(emptyMap()).toList()

        assertEquals(1, result.size, "Expected one solution")
        assertTrue(result[0].isSuccess, "Expected success")
        subs = result[0].getOrNull()!!
        assertEquals(1, subs.size, "Expected one substitution")
        assertEquals(Cons(Atom("a"), Cons(Atom("b"), Empty)), subs[list1], "Expected result to be list with a and b")
    }
}