package prolog.builtins

import io.Logger
import io.Terminal
import parser.ReplParser
import prolog.Answers
import prolog.ast.Database.Program
import prolog.Substitutions
import prolog.ast.logic.Satisfiable
import prolog.ast.terms.Atom
import prolog.ast.terms.Operator
import prolog.ast.terms.Term
import prolog.logic.applySubstitution
import prolog.logic.unifyLazy

/**
 * Write [Term] to the current output, using brackets and operators where appropriate.
 */
class Write(private val term: Term) : Operator("write", rightOperand = term), Satisfiable {
    constructor(message: String) : this(Atom(message))

    override fun satisfy(subs: Substitutions): Answers {
        val t = applySubstitution(term, subs)

        Terminal().say(t.toString())

        Program.storeNewLine = true

        return sequenceOf(Result.success(emptyMap()))
    }

    override fun applySubstitution(subs: Substitutions): Write = Write(applySubstitution(term, subs))

    override fun toString(): String = "write($term)"
}

/**
 * Write a newline character to the current output stream.
 */
object Nl : Atom("nl"), Satisfiable {
    override fun satisfy(subs: Substitutions): Answers {
        Terminal().say("\n")
        Program.storeNewLine = false
        return sequenceOf(Result.success(emptyMap()))
    }

    override fun applySubstitution(subs: Substitutions): Nl = this
}

class WriteLn(private val term: Term) : Conjunction(Write(term), Nl) {
    constructor(message: String) : this(Atom(message))
    override fun applySubstitution(subs: Substitutions): WriteLn = WriteLn(applySubstitution(term, subs))
    override fun toString(): String = "writeln($term)"
}

/**
 * Read the next Prolog term from the current input stream and unify it with [Term].
 *
 * On reaching end-of-file, [Term] is unified with the [Atom] `end_of_file`.
 */
class Read(private val term: Term) : Operator(Atom("read"), null, term), Satisfiable {
    private val io = Terminal()
    private val parser = ReplParser()

    private fun readTerm(): Term {
        val input = io.readLine()
        Logger.debug("Read input: $input")

        return when (input) {
            "end_of_file" -> Atom("end_of_file")
            else -> {
                val out = parser.parse(input).query
                Logger.debug("Parsed input: $out")
                out as? Term ?: throw IllegalArgumentException("Expected a term, but got: $out")
            }
        }
    }

    override fun satisfy(subs: Substitutions): Answers = sequence {
        val t1 = applySubstitution(term, subs)

        val t2 = readTerm()
        Logger.debug("Read term: $t2")

        yieldAll(unifyLazy(t1, t2, subs))
    }

    override fun applySubstitution(subs: Substitutions): Read = Read(applySubstitution(term, subs))
}
