package ocelot.desktop.graphics.buffer

import ocelot.desktop.util.{Logging, Resource}
import org.lwjgl.BufferUtils
import org.lwjgl.opengl.GL15

import java.nio.ByteBuffer

class Buffer[T <: BufferPut] extends Logging with Resource {
  val target: Int = GL15.GL_ARRAY_BUFFER
  val buffer: Int = GL15.glGenBuffers()
  var capacity: Int = _
  var stride: Int = _

  override def freeResource(): Unit = {
    super.freeResource()
    GL15.glDeleteBuffers(buffer)
  }

  def this(elements: Seq[T]) = {
    this()
    createStatic(elements)
  }

  def write(offset: Int, buf: ByteBuffer): Unit = {
    bind()
    GL15.glBufferSubData(target, offset * stride, buf)
  }

  def resize(newCap: Int): Unit = {
    createDynamic(this.stride, newCap)
  }

  protected def createDynamic(stride: Int, cap: Int): Unit = {
    bind()
    GL15.glBufferData(target, stride * cap, GL15.GL_DYNAMIC_DRAW)

    this.stride = stride
    capacity = cap
  }

  protected def createStatic(elements: Seq[T]): Unit = {
    assert(elements.nonEmpty, "Zero size immutable buffer")
    extractMeta(elements.head)

    bind()

    val buf = storeElements(elements)

    GL15.glBufferData(target, buf, GL15.GL_STATIC_DRAW)
    capacity = elements.length
  }

  private def storeElements(elements: Seq[T]): ByteBuffer = {
    val data = BufferUtils.createByteBuffer(elements.length * elements.head.stride)

    for (element <- elements) {
      element.put(data)
    }

    data.flip
    data
  }

  protected def extractMeta(head: T): Unit = {
    stride = head.stride
  }

  def bind(): Unit = {
    GL15.glBindBuffer(target, buffer)
  }
}
