package ocelot.desktop.graphics

import ocelot.desktop.color.{Color, RGBAColorNorm}
import ocelot.desktop.geometry.Transform2D
import ocelot.desktop.graphics.mesh.{Mesh2D, MeshInstance2D, MeshVertex2D}
import ocelot.desktop.graphics.render.InstanceRenderer
import ocelot.desktop.util.{Resource, Spritesheet}
import org.lwjgl.opengl.{ARBFramebufferObject, GL11, GL21, GL30}

import java.nio.ByteBuffer

class ScreenViewport(graphics: Graphics, private var _width: Int, private var _height: Int) extends Resource {
  private[graphics] var texture = new Texture(_width, _height, GL21.GL_SRGB8, GL11.GL_UNSIGNED_BYTE, GL11.GL_RGB)
  private val framebuffer = ARBFramebufferObject.glGenFramebuffers()
  GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer)
  GL30.glFramebufferTexture2D(GL30.GL_FRAMEBUFFER, GL30.GL_COLOR_ATTACHMENT0, GL11.GL_TEXTURE_2D, texture.texture, 0)
  GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, 0)

  private val shaderProgram = graphics.screenShaderProgram
  private val renderer = new InstanceRenderer[MeshVertex2D, MeshInstance2D](Mesh2D.quad, MeshInstance2D, shaderProgram)

  private val _font = Font.NormalFont
  private val spriteRect = Spritesheet.sprites("Empty")

  private val emptySpriteTrans =
    Transform2D.translate(spriteRect.x, spriteRect.y) >> Transform2D.scale(spriteRect.w, spriteRect.h)

  private var _foreground: RGBAColorNorm = RGBAColorNorm(0f, 0f, 0f)
  private var _background: RGBAColorNorm = RGBAColorNorm(1f, 1f, 1f)

  private var mipmapDirty = false

  override def freeResource(): Unit = {
    super.freeResource()

    GL30.glDeleteFramebuffers(framebuffer)

    texture.freeResource()
    renderer.freeResource()
  }

  def font: Font = _font

  def width: Int = _width

  def height: Int = _height

  def foreground: RGBAColorNorm = _foreground

  def foreground_=(color: Color): Unit = {
    _foreground = color.toRGBANorm
  }

  def background: RGBAColorNorm = _background

  def background_=(color: Color): Unit = {
    _background = color.toRGBANorm
  }

  def begin(): Unit = {
    shaderProgram.set("uProj", Transform2D.viewport(_width, _height))
  }

  def resize(width: Int, height: Int): Boolean = {
    if (_width != width || _height != height) {
      _width = width
      _height = height

      texture.bind()
      GL11.glTexImage2D(
        GL11.GL_TEXTURE_2D,
        0,
        GL21.GL_SRGB8,
        _width,
        _height,
        0,
        GL11.GL_RGB,
        GL11.GL_UNSIGNED_BYTE,
        null.asInstanceOf[ByteBuffer],
      )

      true
    } else false
  }

  def char(x: Float, y: Float, c: Int): Unit = {
    val rect = _font.map.getOrElse(c, _font.map('?'))

    val uvTransform = Transform2D.translate(rect.x, rect.y) >> Transform2D.scale(
      rect.w - 0.25f / _font.AtlasWidth,
      rect.h - 0.25f / _font.AtlasHeight,
    )

    val transform =
      Transform2D.translate(x.round, y.round) >>
        Transform2D.scale(_font.charWidth(c), _font.fontSize)

    renderer.schedule(MeshInstance2D(_background, _foreground, transform, emptySpriteTrans, uvTransform))
  }

  def flush(): Unit = {
    if (renderer.isEmpty) return

    GL30.glBindFramebuffer(GL30.GL_FRAMEBUFFER, framebuffer)
    GL11.glEnable(GL30.GL_FRAMEBUFFER_SRGB)

    GL11.glViewport(0, 0, _width, _height)
    GL11.glDisable(GL11.GL_SCISSOR_TEST)

    GL11.glClearColor(0f, 0f, 0f, 1f)
    GL11.glClear(GL11.GL_COLOR_BUFFER_BIT)

    Spritesheet.texture.bind()
    _font.texture.bind(1)
    renderer.flush()

    mipmapDirty = true
  }

  def renderWith(f: => Unit): Unit = {
    begin()

    try {
      f
    } finally {
      flush()
    }
  }

  def generateMipmap(): Unit = {
    if (mipmapDirty) {
      texture.generateMipmap()
      mipmapDirty = false
    }
  }
}
