package repl

import interpreter.Preprocessor
import io.Logger
import io.Terminal
import parser.ReplParser
import prolog.Answer
import prolog.Answers
import prolog.flags.AppliedCut

class Repl {
    private val io = Terminal()
    private val parser = ReplParser()
    private val preprocessor = Preprocessor()

    init {
        welcome()
        while (true) {
            try {
                printAnswers(query())
            } catch (e: Exception) {
                Logger.error("Error parsing REPL: ${e.message}")
                Logger.debug(e.stackTraceToString())
            }
        }
    }

    private fun welcome() {
        io.checkNewLine()
        io.say("Prolog REPL. Type '^D' to quit.\n")
    }

    private fun query(): Answers {
        val queryString = io.prompt("?-", { "|  " }, { it.endsWith(".") })
        val simpleQuery = parser.parse(queryString)
        val query = preprocessor.preprocess(simpleQuery)
        return query.satisfy(emptyMap())
    }

    private fun printAnswers(answers: Answers) {
        val iterator = answers.iterator()

        if (!iterator.hasNext()) {
            io.say("false.\n")
            return
        }

        io.say(prettyPrint(iterator.next()))

        while (true) {
            when (io.prompt("")) {
                ";" -> {
                    try {
                        io.say(prettyPrint(iterator.next()))
                    } catch (_: NoSuchElementException) {
                        break
                    }
                }

                "" -> {
                    io.checkNewLine()
                    return
                }
                "a" -> {
                    io.checkNewLine()
                    return
                }
                "." -> {
                    io.checkNewLine()
                    return
                }
                "h" -> {
                    help()
                    io.say("Action?")
                }

                else -> {
                    io.say("Unknown action: (h for help)\n")
                    io.say("Action?")
                }
            }
        }

        io.say("\n")
    }

    private fun help(): String {
        io.say("Commands:\n")
        io.say("  ;   find next solution\n")
        io.say("  a   abort\n")
        io.say("  .   end query\n")
        io.say("  h   help\n")
        return ""
    }

    private fun prettyPrint(result: Answer): String {
        result.fold(
            onSuccess = {
                val subs = result.getOrNull()!!
                if (subs.isEmpty()) {
                    io.checkNewLine()
                    return "true."
                }
                return subs.entries.joinToString(",\n") { "${it.key} = ${it.value}" }
            },
            onFailure = { failure ->
                if (failure is AppliedCut) {
                    if (failure.subs != null) {
                        return prettyPrint(Result.success(failure.subs))
                    }
                    return "false."
                }

                return "ERROR: ${failure.message}"
            }
        )
    }
}
