package parser.grammars

import com.github.h0tk3y.betterParse.combinators.*
import com.github.h0tk3y.betterParse.grammar.parser
import com.github.h0tk3y.betterParse.parser.Parser
import com.github.h0tk3y.betterParse.utils.Tuple2
import prolog.ast.arithmetic.Float
import prolog.ast.arithmetic.Integer
import prolog.ast.lists.List as PList
import prolog.ast.terms.*
import prolog.builtins.Dynamic
import prolog.ast.lists.List.Empty
import prolog.ast.lists.List.Cons

/**
 * Precedence is based on the following table:
 *
 * | Precedence | Type | Operators                                                                                     |
 * |------------|------|-----------------------------------------------------------------------------------------------|
 * | 1200       | xfx  | --\>, :-, =\>, ==\>                                                                           |
 * | 1200       | fx   | :-, ?-                                                                                        |
 * | 1150       | fx   | dynamic                                                                                       |
 * | 1105       | xfy  | |                                                                                             |
 * | 1100       | xfy  | ;                                                                                             |
 * | 1050       | xfy  | ->, *->                                                                                       |
 * | 1000       | xfy  | ,                                                                                             |
 * | 990        | xfx  | :=                                                                                            |
 * | 900        | fy   | \+                                                                                            |
 * | 700        | xfx  | <, =, =.., =:=, =<, ==, =\=, >, >=, \=, \==, as, is, >:<, :<                                  |
 * | 600        | xfy  | :                                                                                             |
 * | 500        | yfx  | +, -, /\, \/, xor                                                                             |
 * | 500        | fx   | ?                                                                                             |
 * | 400        | yfx  | *, /, //, div, rdiv, <<, >>, mod, rem                                                         |
 * | 200        | xfx  | **                                                                                            |
 * | 200        | xfy  | ^                                                                                             |
 * | 200        | fy   | +, -, \                                                                                       |
 * | 100        | yfx  | .                                                                                             |
 * | 1          | fx   | $                                                                                             |
 *
 * It is very easy to extend this grammar to support more operators. Just add them at the appropriate rule or create a
 * new rule and chain it to the existing ones.
 *
 * @see [SWI-Prolog Predicate op/3](https://www.swi-prolog.org/pldoc/man?predicate=op/3)
 */
open class TermsGrammar : Tokens() {
    // Basic named terms
    protected val variable: Parser<Variable> by (variableToken or anonymousVariableToken) use { Variable(text) }
    protected val simpleAtom: Parser<Atom> by (nameToken or exclamation) use { Atom(text) }
    protected val quotedAtom: Parser<Atom> by (dummy
            or ticked
            or doubleTicked
            or backTicked
            ) use { Atom(text.substring(1, text.length - 1)) }
    protected val atom: Parser<Atom> by (quotedAtom or simpleAtom)
    protected val compound: Parser<Structure> by (atom * -leftParenthesis * separated(
        parser(::termNoConjunction),
        comma,
        acceptZero = true
    ) * -rightParenthesis) use {
        Structure(t1, t2.terms)
    }

    // Basic arithmetic
    protected val int: Parser<Integer> by integerToken use { Integer(text.toInt()) }
    protected val float: Parser<Float> by floatToken use { Float(text.toFloat()) }

    protected val functor: Parser<String> by (nameToken * divide * int) use { "${t1.text}${t2.text}$t3" }

    // Base terms (atoms, compounds, variables, numbers)
    protected val baseTerm: Parser<Term> by (dummy
            or (-leftParenthesis * parser(::term) * -rightParenthesis)
            or parser(::list)
            or compound
            or atom
            or variable
            or float
            or int
            )

    protected val op200: Parser<CompoundTerm> by ((plus or minus) * parser(::term200)) use {
        CompoundTerm(Atom(t1.text), listOf(t2))
    }
    protected val term200: Parser<Term> by (op200 or baseTerm)

    protected val op400: Parser<String> by (multiply or divide) use { text }
    protected val term400: Parser<Term> by (term200 * zeroOrMore(op400 * term200)) use {
        t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
    }

    protected val op500: Parser<String> by (plus or minus) use { text }
    protected val term500: Parser<Term> by (term400 * zeroOrMore(op500 * term400)) use {
        t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
    }

    protected val op700: Parser<String> by (univOp or equivalent or notEquivalent or equals or notEquals or isOp) use { text }
    protected val term700: Parser<Term> by (term500 * optional(op700 * term500)) use {
        if (t2 == null) t1 else CompoundTerm(Atom(t2!!.t1), listOf(t1, t2!!.t2))
    }

    protected val not: Parser<Term> by (notOp * parser(::term900)) use {
        CompoundTerm(Atom(t1.text), listOf(t2))
    }
    protected val term900: Parser<Term> by (not or term700)

    protected val op1000: Parser<String> by (comma) use { text }
    protected val term1000: Parser<Term> by (term900 * zeroOrMore(op1000 * term900)) use {
        constructRightAssociative(t1, t2)
    }

    protected val op1100: Parser<String> by (semicolon) use { text }
    protected val term1100: Parser<Term> by (term1000 * zeroOrMore(op1100 * term1000)) use {
        constructRightAssociative(t1, t2)
    }

    protected val dynamic: Parser<Term> by (dynamicOp * functor) use {
        CompoundTerm( Atom(t1.text), listOf(Atom(t2)) )
    }
    protected val term1150: Parser<Term> by (dynamic or term1100) use { this }

    protected val op1200: Parser<String> by (neck) use { text }
    protected val term1200: Parser<Term> by (term1150 * zeroOrMore(op1200 * term1100)) use {
        t2.fold(t1) { acc, (op, term) -> CompoundTerm(Atom(op), listOf(acc, term)) }
    }

    // Lists
    protected val list: Parser<PList> by (-leftBracket * separated(
        parser(::termNoConjunction),
        comma,
        acceptZero = true
    ) * -rightBracket) use {
        var list: PList = Empty
        // Construct the list in reverse order
        for (term in this.terms.reversed()) {
            list = Cons(term, list)
        }
        list
    }

    // Term - highest level expression
    protected val term: Parser<Term> by term1200
    protected val termNoConjunction: Parser<Term> by term700

    // Parts for clauses
    protected val head: Parser<Head> by (compound or atom)
    protected val body: Parser<Body> by term use { this as Body }

    override val rootParser: Parser<Any> by term

    fun constructRightAssociative(left: Term, pairs: List<Tuple2<String, Term>>): Term {
        if (pairs.isEmpty()) return left

        val (name, next) = pairs.first()
        val remainingPairs = pairs.drop(1)
        return CompoundTerm(Atom(name), listOf(left, constructRightAssociative(next, remainingPairs)))
    }
}
