package ru.dbotthepony.kstarbound.client.gl.properties

import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.gl.GLObject
import ru.dbotthepony.kstarbound.client.gl.checkForGLError
import java.util.function.Consumer
import java.util.function.IntConsumer
import java.util.function.Supplier
import kotlin.reflect.KProperty

class GLObjectTracker<T : GLObject>(
	private val glFunc: IntConsumer,
	private val nullValue: Int = 0,
	private val message: String? = null
) : Supplier<T?>, Consumer<T?> {
	constructor(glFunc: BiConsumer, type: Int, nullValue: Int = 0, message: String? = null) : this(IntConsumer { glFunc.invoke(type, it) }, nullValue)
	constructor(glFunc: BiConsumer, type: Int, message: String? = null) : this(IntConsumer { glFunc.invoke(type, it) }, 0, message)
	constructor(glFunc: IntConsumer, message: String?) : this(glFunc, 0, message)

	fun interface BiConsumer {
		fun invoke(a: Int, b: Int)
	}

	private val client = StarboundClient.current()
	private var value: T? = null
	private var actualPointer = nullValue
		set(value) {
			if (value != field) {
				glFunc.accept(value)
				checkForGLError()
				field = value
			}
		}

	operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
		return value
	}

	operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T?) {
		accept(value)
	}

	override fun get(): T? {
		return value
	}

	override fun accept(value: T?) {
		client.ensureSameThread()

		if (this.value !== value) {
			if (value != null && value.client != client)
				throw IllegalArgumentException("$value does not belong to $client (it belongs to ${value.client})")

			actualPointer = value?.pointer ?: nullValue
			this.value = value
		}
	}
}
