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.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource
import prolog.ast.logic.Clause
import prolog.ast.logic.Fact
import prolog.ast.logic.Rule
import prolog.ast.terms.CompoundTerm
import prolog.ast.terms.Structure
import prolog.ast.terms.Variable
import prolog.ast.terms.Functor

class LogicGrammarTests {
    private lateinit var parser: Grammar<List<Clause>>

    @BeforeEach
    fun setup() {
        parser = LogicGrammar() as Grammar<List<Clause>>
    }

    @ParameterizedTest
    @ValueSource(strings = [
        "john.",
        "mary.",
        "jimmy.",
        "male(john).",
        "male(jimmy).",
        "female(mary).",
        "not(not(true)).",
        "not(a,not(b,c),d,not(not(a)))."
    ])
    fun `parse simple fact`(input: String) {
        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 fact")
        assertTrue(result[0] is Fact, "Expected a fact")
        assertEquals(input, "${result[0]}.", "Expected fact to be '$input'")
    }

    @ParameterizedTest
    @ValueSource(strings = [
        "john. mary.",
        "likes(john, mary). likes(mary, john).",
        "belgium. capital(belgium, brussels).",
        "plus(1, 2, 3). plus(3, 4, 7).",
    ])
    fun `parse multiple facts`(input: String) {
        val result = parser.parseToEnd(input)

        assertEquals(2, result.size, "Expected 2 facts")
        assertTrue(result[0] is Fact, "Expected a fact")
        assertTrue(result[1] is Fact, "Expected a fact")
    }

    @Test
    fun `simplest rule`() {
        val input = "a :- b."

        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        assertTrue(result[0] is Rule, "Expected a rule")
        assertEquals("a :- b", result[0].toString())
    }

    @ParameterizedTest
    @ValueSource(strings = [
        "parent(X, Y) :- father(X, Y).",
        "parent(X, Y) :- mother(X, Y)."
    ])
    fun `parse simple rule`(input: String) {
        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        assertTrue(result[0] is Rule, "Expected a rule")
    }

    @Test
    fun `parse rule with very verbose checks`() {
        val input = "parent(X, Y) :- father(X, Y)."

        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        assertTrue(result[0] is Rule, "Expected a rule")

        val rule = result[0] as Rule

        assertTrue(rule.head is Structure, "Expected head to be a structure")
        val head = rule.head as Structure
        assertEquals(Functor.of("parent/2"), head.functor, "Expected functor 'parent/2'")
        assertEquals(Variable("X"), head.arguments[0], "Expected first argument 'X'")
        assertEquals(Variable("Y"), head.arguments[1], "Expected second argument 'Y'")

        assertTrue(rule.body is Structure, "Expected body to be a structure")
        val body = rule.body as Structure
        assertEquals(Functor.of("father/2"), body.functor, "Expected functor 'father/2'")
        assertEquals(Variable("X"), body.arguments[0], "Expected first argument 'X'")
        assertEquals(Variable("Y"), body.arguments[1], "Expected second argument 'Y'")
    }

    @Test
    fun `parse rule with conjunction`() {
        val input = "father(X, Y) :- parent(X, Y), male(X)."

        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        assertInstanceOf(Rule::class.java, result[0], "Expected a rule")
        val rule = result[0] as Rule
        assertInstanceOf(CompoundTerm::class.java, rule.body, "Expected body to be a compound term")
    }

    @Test
    fun `parse rule with nested conjunction`() {
        val input = "guest(X, Y) :- invited(Y, X), has_time(X), not(sick(Y))."

        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        val rule = result[0] as Rule
        assertEquals(Functor.of("guest/2"), rule.head.functor, "Expected functor 'guest/2'")
        assertEquals(Functor.of(",/2"), (rule.body as CompoundTerm).functor, "Expected functor ',/2'")
        val l0 = (rule.body as CompoundTerm)
        val l1 = l0.arguments[0] as CompoundTerm
        assertEquals(Functor.of("invited/2"), l1.functor, "Expected functor 'invited/2'")
        val l2 = l0.arguments[1] as CompoundTerm
        assertEquals(Functor.of(",/2"), l2.functor, "Expected functor ',/2'")
    }

    @Test
    fun `parse check_identical(X, Y)`() {
        var input = "check_identical(X, Y) :- X == Y."

        assertDoesNotThrow {
            val result = parser.parseToEnd(input)
        }

        input = "check_identical(X, Y) :- X = Y, !, write('X == Y succeeded'), nl."

        assertDoesNotThrow {
            val result = parser.parseToEnd(input)
        }
    }

    @Test
    fun `parse constraints`() {
        val input = ":- a."

        val result = parser.parseToEnd(input)

        assertEquals(1, result.size, "Expected 1 rule")
        assertTrue(result[0] is Rule, "Expected a rule")
        val rule = result[0] as Rule
        assertEquals(Functor.of("/0"), rule.head.functor, "Expected a constraint")
    }
}