package ru.dbotthepony.kstarbound.lua

import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonNull
import org.apache.logging.log4j.LogManager
import org.classdump.luna.ByteString
import org.classdump.luna.exec.CallPausedException
import org.classdump.luna.runtime.LuaFunction
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kstarbound.util.ActionPacer
import ru.dbotthepony.kstarbound.util.sbIntern
import ru.dbotthepony.kstarbound.world.World

class LuaMessageHandlerComponent(val lua: LuaEnvironment, val nameProvider: () -> String) {
	private val handlers = HashMap<String, LuaFunction<*, *, *, *, *>>()

	init {
		val table = lua.newTable()
		lua.globals["message"] = table

		table["setHandler"] = luaFunction { message: ByteString, handler: LuaFunction<*, *, *, *, *>? ->
			if (handler == null) {
				handlers.remove(message.decode())
			} else {
				handlers[message.decode().sbIntern()] = handler
			}
		}
	}

	private val logPacer = ActionPacer(1, 5)

	fun handle(message: String, isLocal: Boolean, arguments: JsonArray): JsonElement {
		val handler = handlers[message] ?: throw World.MessageCallException("No registered handler for $message")

		try {
			val unpack = arguments.map { lua.from(it) }.toTypedArray()
			val result = lua.executor.call(lua, handler, isLocal, *unpack)

			if (result.isEmpty()) {
				return JsonNull.INSTANCE
			} else {
				return toJsonFromLua(result[0])
			}
		} catch (err: CallPausedException) {
			if (logPacer.consumeAndReturnDeadline() <= 0L)
				LOGGER.error("${nameProvider.invoke()}: '$message' handler attempted to yield across C boundary", err)

			throw World.MessageCallException("$message handler attempted to yield across C boundary")
		} catch (err: Throwable) {
			if (logPacer.consumeAndReturnDeadline() <= 0L)
				LOGGER.error("${nameProvider.invoke()}: Exception while handling message '$message'", err)

			throw err
		}
	}

	companion object {
		private val LOGGER = LogManager.getLogger()
	}
}
