package ru.dbotthepony.kstarbound.lua

import it.unimi.dsi.fastutil.ints.IntAVLTreeSet
import ru.dbotthepony.kstarbound.lua.userdata.LuaFuture
import ru.dbotthepony.kstarbound.lua.userdata.LuaPathFinder
import ru.dbotthepony.kstarbound.util.random.random
import java.io.Closeable
import java.lang.ref.Cleaner.Cleanable
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.random.RandomGenerator
import kotlin.properties.Delegates

class LuaSharedState(val handlesThread: LuaThread, private val cleanable: Cleanable) : Closeable {
	private val pendingFree = ConcurrentLinkedQueue<Int>()
	private val freeHandles = IntAVLTreeSet()
	private var nextHandle = 0
	// faster code path
	private var handlesInUse = 0

	private val namedHandles = HashMap<Any, LuaHandle>()
	var random: RandomGenerator = random()

	var commonHandles by Delegates.notNull<CommonHandleRegistry>()
		private set

	var isValid = true
		private set

	fun ensureValid() {
		check(isValid) { "Tried to use NULL LuaState!" }
	}

	var errorToStringFunction by Delegates.notNull<LuaHandle>()
		private set

	var errorTrapFunction by Delegates.notNull<LuaHandle>()
		private set

	override fun toString(): String {
		return "LuaSharedState[$handlesThread]"
	}

	override fun close() {
		if (!isValid) return
		isValid = false
		namedHandles.clear()
		cleanable.clean()
		errorToStringFunction = LuaHandle.Nil
		errorTrapFunction = LuaHandle.Nil
		commonHandles = CommonHandleRegistry.EMPTY
	}

	fun initializeHandles(mainThread: LuaThread) {
		val future = LuaFuture.initializeHandle(mainThread)
		val pathFinder = LuaPathFinder.initializeHandle(mainThread)

		commonHandles = CommonHandleRegistry(
			future = future,
			pathFinder = pathFinder,
		)

		handlesThread.push {
			//it.lua.push(it.nextObject<Throwable?>(-1)?.stackTraceToString() ?: it.nextObject<Any?>(-1).toString())
			it.lua.push(it.nextObject<Any?>().toString())
			1
		}

		errorToStringFunction = allocateHandle(null)

		handlesThread.push {
			val peek = it.peek()

			if (peek == LuaType.STRING) {
				val err = LuaRuntimeException(it.lua.traceback(it.lua.getString(), 1))
				it.lua.push(err)
			} else if (peek == LuaType.USERDATA) {
				val obj = it.nextObject<Any>()

				if (obj is Throwable && obj !is LuaRuntimeException) {
					val err = LuaRuntimeException(it.lua.traceback(obj.toString(), 1), cause = obj, writeStackTrace = false)
					it.lua.push(err)
				}
			}

			1
		}

		errorTrapFunction = allocateHandle(null)
	}

	fun freeHandle(handle: Int, key: Any?) {
		if (!isValid) return
		pendingFree.add(handle)

		if (key != null) {
			namedHandles.remove(key)
		}
	}

	fun cleanup() {
		ensureValid()
		if (handlesInUse == 0) return
		var handle = pendingFree.poll()

		while (handle != null) {
			handlesInUse--
			freeHandles.add(handle)
			handlesThread.push()
			handlesThread.copy(-1, handle)
			handlesThread.pop()

			handle = pendingFree.poll()
		}
	}

	fun allocateHandle(name: Any?): LuaHandle {
		ensureValid()
		require(name == null || name !in namedHandles) { "Named handle '$name' already exists" }
		handlesInUse++

		if (freeHandles.isEmpty()) {
			if (nextHandle % 10 == 0) {
				handlesThread.ensureExtraCapacity(20)
			}

			return LuaHandle.Regular(this, ++nextHandle, name).also {
				if (name != null) namedHandles[name] = it
			}
		} else {
			val handle = freeHandles.firstInt()
			freeHandles.remove(handle)

			handlesThread.copy(-1, handle)
			handlesThread.pop()

			return LuaHandle.Regular(this, handle, name).also {
				if (name != null) namedHandles[name] = it
			}
		}
	}

	fun getNamedHandle(key: Any): LuaHandle {
		ensureValid()
		return namedHandles[key] ?: throw NoSuchElementException("No such handle: $key")
	}

	fun findNamedHandle(key: Any): LuaHandle? {
		ensureValid()
		return namedHandles[key]
	}
}