package ocelot.desktop.graphics

import ocelot.desktop.graphics.Texture.MinFilteringMode
import ocelot.desktop.util.{Logging, Resource}
import org.lwjgl.BufferUtils
import org.lwjgl.opengl._

import java.awt.image.BufferedImage
import java.nio.ByteBuffer

class Texture extends Logging with Resource {
  val texture: Int = GL11.glGenTextures()

  bind()
  GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST)
  GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST)
  GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE)
  GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE)

  def this(image: BufferedImage) {
    this()

    val pixels = new Array[Int](image.getWidth * image.getHeight)
    image.getRGB(0, 0, image.getWidth, image.getHeight, pixels, 0, image.getWidth)

    val buf = BufferUtils.createByteBuffer(image.getWidth * image.getHeight * 4)

    for (y <- 0 until image.getHeight) {
      for (x <- 0 until image.getWidth) {
        val pixel = pixels(y * image.getWidth + x)
        buf.put(((pixel >> 16) & 0xff).toByte)
        buf.put(((pixel >> 8) & 0xff).toByte)
        buf.put((pixel & 0xff).toByte)
        buf.put(((pixel >> 24) & 0xff).toByte)
      }
    }

    buf.flip()

    bind()
    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL21.GL_SRGB8_ALPHA8, image.getWidth, image.getHeight, 0, GL11.GL_RGBA,
      GL11.GL_UNSIGNED_BYTE, buf)
  }

  def this(width: Int, height: Int, internalFormat: Int, dataType: Int, format: Int) = {
    this()
    bind()
    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, dataType,
      null.asInstanceOf[ByteBuffer])
  }

  def this(width: Int, height: Int, format: Int, dataType: Int) = {
    this(width, height, format, dataType, format)
  }

  def set(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
    bind()
    GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, format, width, height, 0, format, dataType, buf)
  }

  def this(width: Int, height: Int, format: Int, dataType: Int, buf: ByteBuffer) = {
    this()
    set(width, height, format, dataType, buf)
  }

  def write(x: Int, y: Int, w: Int, h: Int, format: Int, dataType: Int, buf: ByteBuffer): Unit = {
    bind()
    GL11.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, x, y, w, h, format, dataType, buf)
  }

  def bind(unit: Int = 0): Unit = {
    GL13.glActiveTexture(GL13.GL_TEXTURE0 + unit)
    GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture)
  }

  def setMinFilter(filter: MinFilteringMode): Unit = {
    bind()
    GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, filter.glValue)
  }

  def generateMipmap(): Unit = {
    bind()
    GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D)
  }

  override def freeResource(): Unit = {
    super.freeResource()
    GL11.glDeleteTextures(texture)
  }
}

object Texture {
  class MinFilteringMode private (private[Texture] val glValue: Int, val needsMipmap: Boolean)

  object MinFilteringMode {
    val Nearest = new MinFilteringMode(GL11.GL_NEAREST, false)
    val NearestMipmapNearest = new MinFilteringMode(GL11.GL_NEAREST_MIPMAP_NEAREST, true)
    val NearestMipmapLinear = new MinFilteringMode(GL11.GL_NEAREST_MIPMAP_LINEAR, true)
    val LinearMipmapNearest = new MinFilteringMode(GL11.GL_LINEAR_MIPMAP_NEAREST, true)
    val LinearMipmapLinear = new MinFilteringMode(GL11.GL_LINEAR_MIPMAP_LINEAR, true)
  }
}
