import sun.invoke.empty.Empty
import java.util.*

class ForthEvaluator {
    val stringStack: Stack<String> = Stack<String>()
    val intStack: Stack<Int> = Stack<Int>()

    val overridenOperators = mutableMapOf<String, String>()

    private val validOps = listOf<String>("+", "-", "*", "/", "dup", "drop", "swap", "over")

    fun evaluateProgram(inpList: List<String>): List<Int> {
        for (i in inpList) {
            val rawTokenized: List<String> = i.split(' ')
            if (":" == rawTokenized.first()) {
                overridenOperators[rawTokenized[1]] = rawTokenized.subList(2, rawTokenized.size - 1).joinToString(separator = " ")
                try {
                    if (overridenOperators[rawTokenized[1]]!!.toInt() is Int){
                        throw IllegalArgumentException("Cannot redefine numbers")
                    }
                } catch (e: NumberFormatException){}
            } else {
                val tokenized = doReplacements(rawTokenized).split(' ')
                tokenized.all { validateToken(it) }
                for (j in tokenized) {
                    when (j.toLowerCase()) {
                        "+" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(x + y)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Addition requires that the stack contain at least 2 values")
                            }
                        }
                        "-" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(y - x)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Subtraction requires that the stack contain at least 2 values")
                            }
                        }
                        "*" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(y * x)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Multiplication requires that the stack contain at least 2 values")
                            }
                        }
                        "/" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(y / x)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Division requires that the stack contain at least 2 values")
                            } catch (e: ArithmeticException) {
                                throw IllegalArgumentException("Division by 0 is not allowed")
                            }
                        }
                        "dup" -> {
                            try {
                                val x = intStack.pop()
                                intStack.push(x)
                                intStack.push(x)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Duplicating requires that the stack contain at least 1 value")
                            }
                        }
                        "drop" -> {
                            try {
                                intStack.pop()
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Dropping requires that the stack contain at least 1 value")
                            }
                        }
                        "swap" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(x)
                                intStack.push(y)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Swapping requires that the stack contain at least 2 values")
                            }
                        }
                        "over" -> {
                            try {
                                val x = intStack.pop()
                                val y = intStack.pop()
                                intStack.push(y)
                                intStack.push(x)
                                intStack.push(y)
                            } catch (e: EmptyStackException) {
                                throw IllegalArgumentException("Overing requires that the stack contain at least 2 values")
                            }
                        }
                        else -> {
                            intStack.push(j.toInt())
                        }
                    }
                }
            }
        }
        return intStack.toList()

    }

    private fun validateToken(token: String): Boolean {
        if (token.toLowerCase() !in validOps) {
            try {
                token.toInt()
            } catch (e: NumberFormatException) {
                if (token !in validOps) {
                    throw IllegalArgumentException("No definition available for operator \"$token\"")
                }
            }
        }
        return true
    }

    private fun doReplacements(inpList: List<String>): String {
        val returnList = mutableListOf<String>()
        for (i in inpList) {
            if (i in overridenOperators.keys) {
                returnList.add(overridenOperators[i]!!)
            } else {
                returnList.add(i)
            }
        }
        return returnList.joinToString(separator = " ")
    }
}