package me.eater.threedom.gl.vertex

import me.eater.threedom.gl.vertex.VertexDataPoint.*
import org.joml.*
import java.nio.ByteBuffer
import kotlin.reflect.KProperty
import kotlin.reflect.KProperty1

abstract class VertexData() {
    private val items = linkedMapOf<String, Pair<Int, VertexDataPoint<*>>>()
    private var offset = 0

    val length
        get() = items.map { (_, v) -> v.component2().length }.sum()

    class VertexDataRegister<T>(private val default: T, val provider: (default: T) -> VertexDataPoint<T>) {
        operator fun provideDelegate(thisRef: VertexData, prop: KProperty<*>): VertexDataPoint<T> {
            val delegate = provider(default)
            thisRef.items[prop.name] = thisRef.offset to delegate
            thisRef.offset += delegate.length
            return delegate
        }
    }

    fun int(value: Int = 0): VertexDataRegister<Int> = VertexDataRegister(value, ::SingleInt)
    fun double(value: Double = 0.0): VertexDataRegister<Double> = VertexDataRegister(value, ::SingleDouble)
    fun float(value: Float = 0f): VertexDataRegister<Float> = VertexDataRegister(value, ::SingleFloat)
    fun bool(value: Boolean = false): VertexDataRegister<Boolean> = VertexDataRegister(value, ::SingleBoolean)
    fun vec2(value: Vector2fc = Vector2f()): VertexDataRegister<Vector2fc> = VertexDataRegister(value, ::Vec2)
    fun vec3(value: Vector3fc = Vector3f()): VertexDataRegister<Vector3fc> = VertexDataRegister(value, ::Vec3)
    fun vec4(value: Vector4fc = Vector4f()): VertexDataRegister<Vector4fc> = VertexDataRegister(value, ::Vec4)
    fun mat3(value: Matrix3fc = Matrix3f()): VertexDataRegister<Matrix3fc> = VertexDataRegister(value, ::Mat3)
    fun mat4(value: Matrix4fc = Matrix4f()): VertexDataRegister<Matrix4fc> = VertexDataRegister(value, ::Mat4)

    fun getDataPoint(prop: KProperty1<out VertexData, *>): VertexDataPoint<*>? = items[prop.name]?.component2()

    fun getOffset(prop: KProperty1<out VertexData, *>): Int? = items[prop.name]?.component1()

    fun writeNonInterleaved(position: Int, size: Int, byteBuffer: ByteBuffer) {
        var offset = 0;
        for ((_, pair) in items) {
            val (_, item) = pair
            item.write(offset + (position * item.length), byteBuffer)
            offset += item.length * size
        }
    }

    fun write(position: Int, byteBuffer: ByteBuffer) {
        var pos = position
        for ((_, pair) in items) {
            val (_, item) = pair
            item.write(pos, byteBuffer)
            pos += item.length
        }
    }
}
