package ocelot.desktop.graphics.mesh

import ocelot.desktop.color.Color
import ocelot.desktop.geometry.{Rect2D, Vector2D, Vector3D}
import ocelot.desktop.graphics.buffer.Index
import ocelot.desktop.util.Spritesheet

import scala.collection.mutable.ArrayBuffer

class MeshBuilder3D {
  private val vertices = new ArrayBuffer[MeshVertex3D]()
  private val indices = new ArrayBuffer[Index]()
  private var primitiveType: Option[PrimitiveType] = None

  private lazy val defaultUV: Vector2D = Spritesheet.sprites("Empty").min

  var scale: Float = 1
  var spriteUVScale: Float = 1

  def vertex(pos: Vector3D, normal: Vector3D, uv: Vector2D, color: Color = Color.White): Index = {
    vertices += MeshVertex3D(pos * scale, normal, uv, color.toRGBANorm)
    Index(vertices.length - 1)
  }

  def triangleIndices(a: Index, b: Index, c: Index): Unit = {
    if (primitiveType.isDefined && !primitiveType.contains(PrimitiveTriangles))
      throw new RuntimeException("cannot mix different primitives in a mesh")
    primitiveType = Some(PrimitiveTriangles)

    indices += a
    indices += b
    indices += c
  }

  def lineIndices(a: Index, b: Index): Unit = {
    if (primitiveType.isDefined && !primitiveType.contains(PrimitiveLines))
      throw new RuntimeException("cannot mix different primitives in a mesh")
    primitiveType = Some(PrimitiveLines)

    indices += a
    indices += b
  }

  def triangle(a: Vector3D, aUV: Vector2D,
               b: Vector3D, bUV: Vector2D,
               c: Vector3D, cUV: Vector2D): Unit = {
    val normal = (b - a).cross(c - a).normalize
    val aIdx = vertex(a, normal, aUV)
    val bIdx = vertex(b, normal, bUV)
    val cIdx = vertex(c, normal, cUV)
    triangleIndices(aIdx, bIdx, cIdx)
  }

  def quad(a: Vector3D, aUV: Vector2D,
           b: Vector3D, bUV: Vector2D,
           c: Vector3D, cUV: Vector2D,
           d: Vector3D, dUV: Vector2D,
           color: Color): Unit = {
    val normal = (b - a).cross(c - a).normalize
    val aIdx = vertex(a, normal, aUV, color)
    val bIdx = vertex(b, normal, bUV, color)
    val cIdx = vertex(c, normal, cUV, color)
    val dIdx = vertex(d, normal, dUV, color)
    triangleIndices(aIdx, bIdx, cIdx)
    triangleIndices(aIdx, cIdx, dIdx)
  }

  def quad(a: Vector3D, b: Vector3D, c: Vector3D, d: Vector3D, sprite: (String, Rect2D), color: Color): Unit = {
    val spriteRect = Spritesheet.sprites(sprite._1)
    val cutRect = sprite._2
    val rect = Rect2D(
      spriteRect.x + cutRect.x * spriteUVScale * spriteRect.w,
      spriteRect.y + cutRect.y * spriteUVScale * spriteRect.w,
      cutRect.w * spriteUVScale * spriteRect.w,
      cutRect.h * spriteUVScale * spriteRect.h,
    )

    quad(
      a,
      Vector2D(rect.x + rect.w, rect.y),
      b,
      Vector2D(rect.x, rect.y),
      c,
      Vector2D(rect.x, rect.y + rect.h),
      d,
      Vector2D(rect.x + rect.w, rect.y + rect.h),
      color,
    )
  }

  def cuboid(min: Vector3D, max: Vector3D,
             left: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             right: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             front: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             back: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             top: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             bottom: Option[(String, Rect2D)] = Some(("Empty", Rect2D.Unit)),
             color: Color = Color.White): Unit = {
    left.foreach(sprite => {
      quad(
        Vector3D(min.x, max.y, max.z),
        Vector3D(min.x, max.y, min.z),
        Vector3D(min.x, min.y, min.z),
        Vector3D(min.x, min.y, max.z),
        sprite,
        color,
      )
    })

    right.foreach(sprite => {
      quad(
        Vector3D(max.x, max.y, min.z),
        Vector3D(max.x, max.y, max.z),
        Vector3D(max.x, min.y, max.z),
        Vector3D(max.x, min.y, min.z),
        sprite,
        color,
      )
    })

    front.foreach(sprite => {
      quad(
        Vector3D(min.x, max.y, min.z),
        Vector3D(max.x, max.y, min.z),
        Vector3D(max.x, min.y, min.z),
        Vector3D(min.x, min.y, min.z),
        sprite,
        color,
      )
    })

    back.foreach(sprite => {
      quad(
        Vector3D(max.x, max.y, max.z),
        Vector3D(min.x, max.y, max.z),
        Vector3D(min.x, min.y, max.z),
        Vector3D(max.x, min.y, max.z),
        sprite,
        color,
      )
    })

    top.foreach(sprite => {
      quad(
        Vector3D(max.x, max.y, min.z),
        Vector3D(min.x, max.y, min.z),
        Vector3D(min.x, max.y, max.z),
        Vector3D(max.x, max.y, max.z),
        sprite,
        color,
      )
    })

    bottom.foreach(sprite => {
      quad(
        Vector3D(max.x, min.y, max.z),
        Vector3D(min.x, min.y, max.z),
        Vector3D(min.x, min.y, min.z),
        Vector3D(max.x, min.y, min.z),
        sprite,
        color,
      )
    })
  }

  def line(a: Vector3D, b: Vector3D, aColor: Color = Color.White, bColor: Color = Color.White): Unit = {
    val aIdx = vertex(a, Vector3D.Zero, defaultUV, aColor)
    val bIdx = vertex(b, Vector3D.Zero, defaultUV, bColor)
    lineIndices(aIdx, bIdx)
  }

  def build(): Mesh3D = {
    new Mesh3D(vertices.toSeq, indices = Some(indices.toSeq),
      primitiveType = primitiveType.getOrElse(PrimitiveTriangles))
  }
}
