package ru.dbotthepony.kstarbound.client.gl

import org.lwjgl.opengl.GL45.*
import org.lwjgl.system.MemoryUtil
import ru.dbotthepony.kstarbound.client.StarboundClient
import java.nio.ByteBuffer

sealed class BufferObject : GLObject() {
	final override val client = StarboundClient.current()
	abstract val glType: Int

	@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
	override fun bind() {
		// do nothing
	}

	@Deprecated("Has no effect", level = DeprecationLevel.ERROR)
	override fun unbind() {
		// do nothing
	}

	open fun bufferData(data: ByteBuffer, usage: Int): BufferObject {
		client.ensureSameThread()
		glNamedBufferData(pointer, data, usage)
		checkForGLError()
		return this
	}

	open fun bufferData(data: ByteBuffer, usage: Int, length: Long): BufferObject {
		client.ensureSameThread()

		if (length > data.remaining().toLong()) {
			throw IndexOutOfBoundsException("Tried to upload $data into $pointer with offset at ${data.position()} with length of $length, but that is longer than remaining data length of ${data.remaining()}!")
		}

		nglNamedBufferData(pointer, length, MemoryUtil.memAddress(data), usage)

		checkForGLError()
		return this
	}

	open fun bufferData(data: IntArray, usage: Int): BufferObject {
		client.ensureSameThread()
		glNamedBufferData(pointer, data, usage)
		checkForGLError()
		return this
	}

	open fun bufferData(data: FloatArray, usage: Int): BufferObject {
		client.ensureSameThread()
		glNamedBufferData(pointer, data, usage)
		checkForGLError()
		return this
	}

	open fun bufferData(data: DoubleArray, usage: Int): BufferObject {
		client.ensureSameThread()
		glNamedBufferData(pointer, data, usage)
		checkForGLError()
		return this
	}

	open fun bufferData(data: LongArray, usage: Int): BufferObject {
		client.ensureSameThread()
		glNamedBufferData(pointer, data, usage)
		checkForGLError()
		return this
	}

	class EBO : BufferObject() {
		override val glType: Int
			get() = GL_ELEMENT_ARRAY_BUFFER

		override val pointer: Int = glCreateBuffers()

		init {
			checkForGLError("Creating Element Buffer Object")
			client.registerCleanable(this, ::glDeleteBuffers, pointer)
		}

		override fun bufferData(data: ByteBuffer, usage: Int): EBO {
			return super.bufferData(data, usage) as EBO
		}

		override fun bufferData(data: ByteBuffer, usage: Int, length: Long): EBO {
			return super.bufferData(data, usage, length) as EBO
		}

		override fun bufferData(data: IntArray, usage: Int): EBO {
			return super.bufferData(data, usage) as EBO
		}

		override fun bufferData(data: FloatArray, usage: Int): EBO {
			return super.bufferData(data, usage) as EBO
		}

		override fun bufferData(data: DoubleArray, usage: Int): EBO {
			return super.bufferData(data, usage) as EBO
		}

		override fun bufferData(data: LongArray, usage: Int): EBO {
			return super.bufferData(data, usage) as EBO
		}
	}

	class VBO : BufferObject() {
		override val glType: Int
			get() = GL_ARRAY_BUFFER

		override val pointer: Int = glCreateBuffers()

		init {
			checkForGLError("Creating Vertex Buffer Object")
			client.registerCleanable(this, ::glDeleteBuffers, pointer)
		}

		override fun bufferData(data: ByteBuffer, usage: Int): VBO {
			return super.bufferData(data, usage) as VBO
		}

		override fun bufferData(data: ByteBuffer, usage: Int, length: Long): VBO {
			return super.bufferData(data, usage, length) as VBO
		}

		override fun bufferData(data: IntArray, usage: Int): VBO {
			return super.bufferData(data, usage) as VBO
		}

		override fun bufferData(data: FloatArray, usage: Int): VBO {
			return super.bufferData(data, usage) as VBO
		}

		override fun bufferData(data: DoubleArray, usage: Int): VBO {
			return super.bufferData(data, usage) as VBO
		}

		override fun bufferData(data: LongArray, usage: Int): VBO {
			return super.bufferData(data, usage) as VBO
		}
	}
}
