package ru.dbotthepony.kstarbound.lua

import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.Resumable
import org.classdump.luna.runtime.UnresolvedControlThrowable
import ru.dbotthepony.kommons.util.KOptional
import ru.dbotthepony.kommons.vector.Vector2i
import java.util.function.Supplier

class StateMachine : Resumable {
	private val states = ArrayDeque<(ExecutionContext) -> Unit>()

	fun add(state: (ExecutionContext) -> Unit): StateMachine {
		states.add(state)
		return this
	}

	fun mergeInto(other: StateMachine): StateMachine {
		states.addAll(other.states)
		return other
	}

	fun mergeFrom(other: StateMachine): StateMachine {
		other.mergeInto(this)
		return this
	}

	override fun resume(context: ExecutionContext, suspendedState: Any) {
		run(context, this, this)
	}

	fun run(context: ExecutionContext): Boolean = run(context, this, this)

	fun run(context: ExecutionContext, resumable: Resumable, suspendState: Any): Boolean {
		if (states.isEmpty())
			return false

		while (states.isNotEmpty()) {
			val next = states.removeFirst()

			try {
				next.invoke(context)
			} catch (err: UnresolvedControlThrowable) {
				throw err.resolve(resumable, suspendState)
			}
		}

		return true
	}

	fun index(table: Table, vararg indexes: Any): Supplier<Any> {
		var value: KOptional<Any> = KOptional.empty()

		for (index in indexes) {
			add {
				if (!value.isPresent) {
					value = KOptional.ofNullable(Dispatch.index(it, table, index))
				}
			}
		}

		return Supplier {
			if (!value.isPresent) {
				throw LuaRuntimeException("Unable to locate value in table using following keys: ${indexes.joinToString()}")
			}
		}
	}

	fun optionalIndex(table: Table, vararg indexes: Any): Supplier<KOptional<Any>> {
		var value: KOptional<Any> = KOptional.empty()

		for (index in indexes) {
			add {
				if (!value.isPresent) {
					value = KOptional.ofNullable(Dispatch.index(it, table, index))
				}
			}
		}

		return Supplier { value }
	}

	fun loadVector2i(table: Table): Supplier<Vector2i> {
		val x = index(table, 1, "x")
		val y = index(table, 2, "y")
		var value = KOptional.empty<Vector2i>()

		add {
			val ix = x.get()
			val iy = y.get()

			if (ix !is Number) throw LuaRuntimeException("Invalid 'x' value for vector: ${x.get()}")
			if (iy !is Number) throw LuaRuntimeException("Invalid 'y' value for vector: ${x.get()}")

			value = KOptional(Vector2i(ix.toInt(), iy.toInt()))
		}

		return Supplier { value.value }
	}
}
