package ru.dbotthepony.kstarbound.lua

import com.google.gson.JsonElement
import it.unimi.dsi.fastutil.objects.ObjectIterators
import org.classdump.luna.LuaRuntimeException
import org.classdump.luna.Table
import org.classdump.luna.TableFactory
import org.classdump.luna.impl.NonsuspendableFunctionException
import org.classdump.luna.lib.ArgumentIterator
import org.classdump.luna.runtime.AbstractFunction0
import org.classdump.luna.runtime.AbstractFunction1
import org.classdump.luna.runtime.AbstractFunction2
import org.classdump.luna.runtime.AbstractFunction3
import org.classdump.luna.runtime.AbstractFunction4
import org.classdump.luna.runtime.AbstractFunctionAnyArg
import org.classdump.luna.runtime.Dispatch
import org.classdump.luna.runtime.ExecutionContext
import org.classdump.luna.runtime.LuaFunction
import org.classdump.luna.runtime.UnresolvedControlThrowable

fun ExecutionContext.indexNoYield(table: Any, key: Any): Any? {
	return try {
		Dispatch.index(this, table, key)
		returnBuffer.get0()
	} catch (err: UnresolvedControlThrowable) {
		throw RuntimeException("attempt to yield across C boundary", err)
	}
}

fun ExecutionContext.indexSetNoYield(table: Any, key: Any, value: Any?) {
	try {
		Dispatch.setindex(this, table, key, value)
	} catch (err: UnresolvedControlThrowable) {
		throw RuntimeException("attempt to yield across C boundary", err)
	}
}

fun ArgumentIterator.nextOptionalFloat(): Double? {
	if (hasNext()) {
		return nextFloat()
	} else {
		return null
	}
}

fun ArgumentIterator.nextOptionalInteger(): Long? {
	if (hasNext()) {
		return nextInteger()
	} else {
		return null
	}
}

operator fun Table.set(index: Any, value: Any?) {
	rawset(index, value)
}

@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Any, value: JsonElement?) {
	rawset(index, value)
}

operator fun Table.set(index: Long, value: Any?) {
	rawset(index, value)
}

@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Long, value: JsonElement?) {
	rawset(index, value)
}

operator fun Table.set(index: Int, value: Any?) {
	rawset(index.toLong(), value)
}

@Deprecated("Trying to set JSON to Lua table", level = DeprecationLevel.ERROR)
operator fun Table.set(index: Int, value: JsonElement?) {
	rawset(index.toLong(), value)
}

operator fun Table.get(index: Any): Any? = rawget(index)
operator fun Table.get(index: Long): Any? = rawget(index)
operator fun Table.get(index: Int): Any? = rawget(index.toLong())

operator fun Table.iterator(): Iterator<Map.Entry<Any, Any>> {
	var key: Any? = initialKey() ?: return ObjectIterators.emptyIterator()
	data class Pair(override val key: Any, override val value: Any) : Map.Entry<Any, Any>

	return object : Iterator<Map.Entry<Any, Any>> {
		override fun hasNext(): Boolean {
			return key != null
		}

		override fun next(): Map.Entry<Any, Any> {
			val ikey = key ?: throw NoSuchElementException()
			val value = get(ikey)!!
			key = successorKeyOf(ikey)
			return Pair(ikey, value)
		}
	}
}

fun TableFactory.tableOf(vararg values: Any?): Table {
	val table = newTable(values.size, 0)

	for ((i, v) in values.withIndex()) {
		table[i + 1L] = v
	}

	return table
}

@Deprecated("Function is a stub")
fun luaStub(message: String = "not yet implemented"): LuaFunction<Any?, Any?, Any?, Any?, Any?> {
	return object : LuaFunction<Any?, Any?, Any?, Any?, Any?>() {
		override fun resume(context: ExecutionContext?, suspendedState: Any?) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, arg1: Any?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, arg1: Any?, arg2: Any?, arg3: Any?, arg4: Any?, arg5: Any?) {
			throw LuaRuntimeException("NYI: $message")
		}

		override fun invoke(context: ExecutionContext?, args: Array<out Any>?) {
			throw LuaRuntimeException("NYI: $message")
		}
	}
}

fun luaFunctionN(name: String, callable: ExecutionContext.(ArgumentIterator) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
	return object : AbstractFunctionAnyArg() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, args: Array<out Any>) {
			callable.invoke(context, ArgumentIterator.of(context, name, args))
		}
	}
}

fun luaFunctionNS(name: String, callable: ExecutionContext.(ArgumentIterator) -> StateMachine): LuaFunction<Any, Any, Any, Any, Any> {
	return object : AbstractFunctionAnyArg() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			(suspendedState as StateMachine).run(context)
		}

		override fun invoke(context: ExecutionContext, args: Array<out Any>) {
			callable.invoke(context, ArgumentIterator.of(context, name, args)).run(context)
		}
	}
}

fun luaFunctionArray(callable: ExecutionContext.(Array<out Any?>) -> Unit): LuaFunction<Any, Any, Any, Any, Any> {
	return object : AbstractFunctionAnyArg() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, args: Array<out Any?>) {
			callable.invoke(context, args)
		}
	}
}

fun luaFunction(callable: ExecutionContext.() -> Unit): LuaFunction<*, *, *, *, *> {
	return object : AbstractFunction0() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext) {
			callable.invoke(context)
		}
	}
}

fun <T> luaFunction(callable: ExecutionContext.(T) -> Unit): LuaFunction<T, *, *, *, *> {
	return object : AbstractFunction1<T>() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, arg1: T) {
			callable.invoke(context, arg1)
		}
	}
}

fun <T, T2> luaFunction(callable: ExecutionContext.(T, T2) -> Unit): LuaFunction<T, T2, *, *, *> {
	return object : AbstractFunction2<T, T2>() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, arg1: T, arg2: T2) {
			callable.invoke(context, arg1, arg2)
		}
	}
}

fun <T, T2, T3> luaFunction(callable: ExecutionContext.(T, T2, T3) -> Unit): LuaFunction<T, T2, T3, *, *> {
	return object : AbstractFunction3<T, T2, T3>() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3) {
			callable.invoke(context, arg1, arg2, arg3)
		}
	}
}

fun <T, T2, T3, T4> luaFunction(callable: ExecutionContext.(T, T2, T3, T4) -> Unit): LuaFunction<T, T2, T3, T4, *> {
	return object : AbstractFunction4<T, T2, T3, T4>() {
		override fun resume(context: ExecutionContext, suspendedState: Any) {
			throw NonsuspendableFunctionException(this::class.java)
		}

		override fun invoke(context: ExecutionContext, arg1: T, arg2: T2, arg3: T3, arg4: T4) {
			callable.invoke(context, arg1, arg2, arg3, arg4)
		}
	}
}
