package parser.grammars

import com.github.h0tk3y.betterParse.grammar.Grammar
import com.github.h0tk3y.betterParse.grammar.parseToEnd
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.terms.Atom
import prolog.ast.terms.Structure
import prolog.ast.terms.Term
import prolog.ast.terms.Variable
import prolog.builtins.Is
import prolog.logic.equivalent
import prolog.ast.lists.List.Empty
import prolog.ast.lists.List.Cons

class TermsGrammarTests {
    private lateinit var parser: Grammar<Term>

    @BeforeEach
    fun setup() {
        parser = TermsGrammar() as Grammar<Term>
    }

    @ParameterizedTest
    @ValueSource(strings = ["a", "foo", "foo1", "fooBar", "foo_bar"])
    fun `parse atom`(name: String) {
        val result = parser.parseToEnd(name)

        assertEquals(Atom(name), result, "Expected atom '$name'")
    }

    @ParameterizedTest
    @ValueSource(
        strings = [
            "'tick'",
            "\"doubleTick\"",
            "`backTick`",
            "'i have spaces'",
            "`i have a 'quote' inside`",
            "'I have Cases'",
            "'I, h@v3 many (!!!) special characters?! {}'"
        ]
    )
    fun `Parse a quoted atom`(input: String) {
        val result = parser.parseToEnd(input)

        val expected = input.substring(1, input.length - 1)

        assertEquals(Atom(expected), result, "Expected atom")
    }

    @ParameterizedTest
    @ValueSource(strings = ["X", "X1", "X_1"])
    fun `parse variable`(name: String) {
        val result = parser.parseToEnd(name)

        assertEquals(Variable(name), result)
    }

    @Test
    fun `parse anonymous variable`() {
        val input = "_"

        val result = parser.parseToEnd(input)

        assertEquals(Variable("_"), result, "Expected anonymous variable")
    }

    @Test
    fun `empty compound term`() {
        val input = "f()"

        val result = parser.parseToEnd(input)

        assertEquals(Structure(Atom("f"), emptyList()), result, "Expected atom 'f'")
    }

    @Test
    fun `parse compound term f(a)`() {
        val input = "f(a)"

        val result = parser.parseToEnd(input)

        assertEquals(Structure(Atom("f"), listOf(Atom("a"))), result, "Expected atom 'f(a)'")
    }

    @Test
    fun `parse compound term f(a, b)`() {
        val input = "f(a, b)"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(Atom("f"), listOf(Atom("a"), Atom("b"))),
            result,
            "Expected atom 'f(a, b)'"
        )
    }

    @Test
    fun `parse compound term with variable f(a, X)`() {
        val input = "f(a, X)"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(Atom("f"), listOf(Atom("a"), Variable("X"))),
            result,
            "Expected atom 'f(a, X)'"
        )
    }

    @Test
    fun `parse compound term with var and int`() {
        val input = "check_identical(A, 13)"
        val result = parser.parseToEnd(input)
        assertEquals(
            Structure(Atom("check_identical"), listOf(Variable("A"), Integer(13))),
            result,
            "Expected atom 'check_identical(A, 13)'"
        )
    }

    @Test
    fun `parse nested compound term f(a, g(b))`() {
        val input = "f(a, g(b))"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Atom("b"))))),
            result,
            "Expected atom 'f(a, g(b))'"
        )
    }

    @Test
    fun `parse compound term with variable f(a, g(X))`() {
        val input = "f(a, g(X))"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(Atom("f"), listOf(Atom("a"), Structure(Atom("g"), listOf(Variable("X"))))),
            result,
            "Expected atom 'f(a, g(X))'"
        )
    }

    @Test
    fun `parse nested compound term with variables`() {
        val input = "hit(character(Name, Class, Level, HP), character(Name, Class, Level, T))"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(
                Atom("hit"),
                listOf(
                    Structure(Atom("character"), listOf(Variable("Name"), Variable("Class"), Variable("Level"), Variable("HP"))),
                    Structure(Atom("character"), listOf(Variable("Name"), Variable("Class"), Variable("Level"), Variable("T")))
                )
            ),
            result,
            "Expected atom 'hit(character(Name, Class, Level, HP), character(Name, Class, Level, T))'"
        )
    }

    @Test
    fun `parse compound term with anonymous variables`() {
        val input = "f(a, _, g(X))"

        val result = parser.parseToEnd(input)

        assertEquals(
            Structure(Atom("f"), listOf(Atom("a"), Variable("_"), Structure(Atom("g"), listOf(Variable("X"))))),
            result,
            "Expected atom 'f(a, _, g(X))'"
        )
    }

    @ParameterizedTest
    @ValueSource(ints = [0, 1, 5, 12, 345, 123456789])
    fun `parse positive integer`(number: Int) {
        val input = number.toString()

        val result = parser.parseToEnd(input)

        assertEquals(Integer(number), result, "Expected integer '$number'")
    }

    @ParameterizedTest
    @ValueSource(ints = [-987654321, -543, -21, -1])
    fun `parse negative integer`(number: Int) {
        val input = number.toString()

        val result = parser.parseToEnd(input)

        assertEquals(Structure(Atom("-"), listOf(Integer(0 - number))), result, "Expected integer '$number'")
    }

    @Test
    fun `parse float`() {
        val input = "42.0"

        val result = parser.parseToEnd(input)

        assertEquals(Float(42.0f), result, "Expected float '42.0'")
    }

    @Test
    fun `parse negative float`() {
        val input = "-42.0"

        val result = parser.parseToEnd(input)

        assertEquals(Structure(Atom("-"), listOf(Float(42.0f))), result, "Expected float '-42.0'")
    }

    @ParameterizedTest
    @ValueSource(strings = ["got_an_a(Student)", "grade(Student, Grade)"])
    fun `parse unification`(input: String) {
        assertDoesNotThrow { parser.parseToEnd(input) }
    }

    @Nested
    class `Operators and precedence` {
        private lateinit var parser: Grammar<Term>

        @BeforeEach
        fun setup() {
            parser = TermsGrammar() as Grammar<Term>
        }

        @Test
        fun `can parse equivalent`() {
            val input = "X == Y"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(Atom("=="), listOf(Variable("X"), Variable("Y"))),
                result,
                "Expected equivalent operator"
            )
        }

        @Test
        fun `can parse cut`() {
            val input = "!"
            val result = parser.parseToEnd(input)
            assertEquals(Atom("!"), result, "Expected cut operator")
        }

        @Test
        fun `can parse 'is'`() {
            val input = "T is 1"
            val result = parser.parseToEnd(input)
            assertEquals(
                Structure(Atom("is"), listOf(Variable("T"), Integer(1))),
                result
            )
        }

        @Test
        fun `can parse 'is' with addition`() {
            val input = "T is 1 + 2"
            val result = parser.parseToEnd(input)
            assertEquals(
                Structure(Atom("is"), listOf(Variable("T"), Structure(Atom("+"), listOf(Integer(1), Integer(2))))),
                result
            )
        }

        @ParameterizedTest
        @ValueSource(strings = ["+", "-", "*", "/"])
        fun `can parse with spaces`(operator: String) {
            val input = "1 $operator 2"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(Atom(operator), listOf(Integer(1), Integer(2))),
                result,
                "Expected operator '$operator'"
            )
        }

        @ParameterizedTest
        @ValueSource(strings = ["+", "-", "*", "/"])
        fun `can parse without spaces`(operator: String) {
            val input = "1${operator}2"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(Atom(operator), listOf(Integer(1), Integer(2))),
                result,
                "Expected operator '$operator' without spaces"
            )
        }

        @Test
        fun `parse addition and multiplication`() {
            val input = "1 + 2 * 3"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(Atom("+"), listOf(Integer(1), Structure(Atom("*"), listOf(Integer(2), Integer(3))))),
                result,
                "Expected addition and multiplication"
            )
        }

        @Test
        fun `parse multiplication and addition`() {
            val input = "1 * 2 + 3"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(Atom("+"), listOf(Structure(Atom("*"), listOf(Integer(1), Integer(2))), Integer(3))),
                result,
                "Expected multiplication and addition"
            )
        }

        @Test
        fun `complex expression`() {
            val input = "1 + 2 * 3 - 4 / 5"

            val result = parser.parseToEnd(input)

            assertEquals(
                Structure(
                    Atom("-"),
                    listOf(
                        Structure(Atom("+"), listOf(Integer(1), Structure(Atom("*"), listOf(Integer(2), Integer(3))))),
                        Structure(Atom("/"), listOf(Integer(4), Integer(5)))
                    )
                ),
                result,
                "Expected complex expression"
            )
        }
    }

    @Nested
    class Lists {
        private lateinit var parser: Grammar<Term>

        @BeforeEach
        fun setup() {
            parser = TermsGrammar() as Grammar<Term>
        }

        @Test
        fun `parse empty list`() {
            val input = "[]"

            val result = parser.parseToEnd(input)

            assertEquals(Empty, result, "Expected empty list")
        }

        @Test
        fun `parse non-empty list`() {
            val input = "[a, b, c]"

            val result = parser.parseToEnd(input)

            assertEquals(
                Cons(Atom("a"), Cons(Atom("b"), Cons(Atom("c"), Empty))),
                result,
                "Expected non-empty list"
            )
        }

        @Test
        fun `parse nested lists`() {
            val input = "[a, [b, c], d]"

            val result = parser.parseToEnd(input)

            assertEquals(
                Cons(Atom("a"), Cons(Cons(Atom("b"), Cons(Atom("c"), Empty)), Cons(Atom("d"), Empty))),
                result,
                "Expected nested lists"
            )
        }
    }
}