package ocelot.desktop.windows

import ocelot.desktop.OcelotDesktop
import ocelot.desktop.color.{Color, IntColor}
import ocelot.desktop.util.NumberUtils.ExtendedFloat
import ocelot.desktop.geometry._
import ocelot.desktop.graphics.IconSource
import ocelot.desktop.graphics.mesh.{Mesh3D, MeshBuilder3D}
import ocelot.desktop.graphics.scene.{Scene3D, SceneMesh3D}
import ocelot.desktop.node.nodes.HologramProjectorNode
import ocelot.desktop.ui.widget.window.PanelWindow
import ocelot.desktop.ui.widget.{IconButton, Viewport3DWidget, Widget}
import totoro.ocelot.brain.Settings
import totoro.ocelot.brain.entity.HologramProjector
import totoro.ocelot.brain.util.Tier

object HologramProjectorWindow {
  private lazy val ProjectorMesh: Array[Mesh3D] = Array(Tier.One, Tier.Two).map(tier => {
    val builder = new MeshBuilder3D
    builder.scale = 1.0f / 16.0f
    builder.spriteUVScale = 1.0f / 16.0f

    val spriteTop = s"blocks/HologramProjector${tier.num}Top"
    val spriteBottom = "blocks/Generic"
    val spriteSide = "blocks/HologramProjectorSide"

    builder.cuboid(
      Vector3D(4, 0, 4),
      Vector3D(12, 3, 12),
      left = None,
      right = None,
      front = None,
      back = None,
      top = Some((spriteTop, Rect2D(4, 4, 8, 8))),
      bottom = Some((spriteBottom, Rect2D(4, 4, 8, 8))),
    )

    builder.cuboid(
      Vector3D(0, 0, 0),
      Vector3D(2, 7, 16),
      top = Some((spriteTop, Rect2D(0, 0, 2, 16))),
      bottom = Some((spriteBottom, Rect2D(0, 0, 2, 16))),
      front = Some((spriteSide, Rect2D(2, 9, -2, 7))),
      back = Some((spriteSide, Rect2D(0, 9, 2, 7))),
      left = Some((spriteSide, Rect2D(0, 9, 16, 7))),
      right = Some((spriteSide, Rect2D(0, 9, 16, 7))),
    )

    builder.cuboid(
      Vector3D(14, 0, 0),
      Vector3D(16, 7, 16),
      top = Some((spriteTop, Rect2D(14, 0, 2, 16))),
      bottom = Some((spriteBottom, Rect2D(14, 0, 2, 16))),
      front = Some((spriteSide, Rect2D(0, 9, 2, 7))),
      back = Some((spriteSide, Rect2D(2, 9, -2, 7))),
      left = Some((spriteSide, Rect2D(0, 9, 16, 7))),
      right = Some((spriteSide, Rect2D(0, 9, 16, 7))),
    )

    builder.cuboid(
      Vector3D(2, 0, 0),
      Vector3D(14, 7, 2),
      left = None,
      right = None,
      top = Some((spriteTop, Rect2D(2, 0, 12, 2))),
      bottom = Some((spriteBottom, Rect2D(2, 2, 12, -2))),
      front = Some((spriteSide, Rect2D(2, 9, 12, 7))),
      back = Some((spriteSide, Rect2D(2, 9, 12, 7))),
    )

    builder.cuboid(
      Vector3D(2, 0, 14),
      Vector3D(14, 7, 16),
      left = None,
      right = None,
      top = Some((spriteTop, Rect2D(2, 14, 12, 2))),
      bottom = Some((spriteBottom, Rect2D(2, 16, 12, -2))),
      front = Some((spriteSide, Rect2D(2, 9, 12, 7))),
      back = Some((spriteSide, Rect2D(2, 9, 12, 7))),
    )

    builder.cuboid(
      Vector3D(2, 0, 2),
      Vector3D(4, 5, 14),
      front = None,
      back = None,
      left = None,
      top = Some((spriteTop, Rect2D(2, 2, 2, 12))),
      bottom = Some((spriteBottom, Rect2D(2, 2, 2, 12))),
      right = Some((spriteSide, Rect2D(2, 16, 12, -5))),
    )

    builder.cuboid(
      Vector3D(12, 0, 2),
      Vector3D(14, 5, 14),
      front = None,
      back = None,
      right = None,
      top = Some((spriteTop, Rect2D(12, 2, 2, 12))),
      bottom = Some((spriteBottom, Rect2D(12, 2, 2, 12))),
      left = Some((spriteSide, Rect2D(2, 16, 12, -5))),
    )

    builder.cuboid(
      Vector3D(4, 0, 2),
      Vector3D(12, 5, 4),
      left = None,
      right = None,
      front = None,
      top = Some((spriteTop, Rect2D(4, 2, 8, 2))),
      bottom = Some((spriteBottom, Rect2D(4, 2, 8, 2))),
      back = Some((spriteSide, Rect2D(4, 16, 8, -5))),
    )

    builder.cuboid(
      Vector3D(4, 0, 12),
      Vector3D(12, 5, 14),
      left = None,
      right = None,
      back = None,
      top = Some((spriteTop, Rect2D(4, 12, 8, 2))),
      bottom = Some((spriteBottom, Rect2D(4, 12, 8, 2))),
      front = Some((spriteSide, Rect2D(4, 16, 8, -5))),
    )

    builder.build()
  })
}

class HologramProjectorWindow(val hologramProjectorNode: HologramProjectorNode) extends PanelWindow {
  private def hologram: HologramProjector = hologramProjectorNode.hologramProjector

  private var mesh: Option[Mesh3D] = None

  private val viewport = new Viewport3DWidget {
    private var gridVisible = true

    override protected def setupToolbar(toolbar: Widget): Unit = {
      super.setupToolbar(toolbar)

      toolbar.children :+= new IconButton(
        IconSource.Icons.GridOff,
        IconSource.Icons.Grid,
        pressedColor = Color.White.withAlpha(0.5f),
        releasedColor = Color.White.withAlpha(0.2f),
        mode = IconButton.Mode.Switch,
        darkenActiveColorFactor = 0.5f,
        model = IconButton.ReadOnlyModel(gridVisible),
      ) {
        override def onPressed(): Unit = gridVisible = true

        override def onReleased(): Unit = gridVisible = false
      }
    }

    override protected def createScene(): Scene3D = {
      val scene = new Scene3D
      scene.camera = camera

      scene.lightSource.basis = Basis3D.rotate(Vector3D.AxisZ, 30f.toRadians) *
        Basis3D.rotate(Vector3D.AxisX, 30f.toRadians)

      if (gridVisible) {
        val grid = new SceneMesh3D(Mesh3D.Grid)
        grid.lightingFactor = 0
        grid.textureFactor = 0
        scene.meshes += grid
      }

      val projectorMesh = new SceneMesh3D(HologramProjectorWindow.ProjectorMesh(hologram.tier.id))
      scene.meshes += projectorMesh

      mesh.foreach(mesh => {
        val workspace = hologram.workspace

        val subTick =
          (System.nanoTime() - workspace.getLastTickNanoTime).toFloat / 1e9f / OcelotDesktop.tpsCounter.dt.max(1e-9f)
        val time = workspace.getIngameTime + subTick.clamped(0, 1)

        var transform = Transform3D.translate(0.5f, 0.5f, 0.5f) *
          Transform3D.rotate(
            Vector3D(hologram.rotationX, hologram.rotationY, hologram.rotationZ),
            hologram.rotationAngle.toRadians,
          ) *
          Transform3D.rotate(
            Vector3D(hologram.rotationSpeedX, hologram.rotationSpeedY, hologram.rotationSpeedZ),
            (hologram.rotationSpeed * (time % (360 * 20 - 1)) / 20f).toRadians,
          ) *
          Transform3D.scale(1.001f) *
          Transform3D.translate(
            (hologram.translationX * hologram.width / 16f - 1.5f) * hologram.scale,
            hologram.translationY * hologram.height / 16f * hologram.scale,
            (hologram.translationZ * hologram.width / 16f - 1.5f) * hologram.scale,
          )

        val hologramFlickerFrequency = Settings.get.hologramFlickerFrequency
        val random = workspace.rand

        if (hologramFlickerFrequency > 0 && workspace.rand.nextDouble() < hologramFlickerFrequency) {
          transform *= Transform3D.scale(Vector3D(
            1 + random.nextGaussian() * 0.01,
            1 + random.nextGaussian() * 0.001,
            1 + random.nextGaussian() * 0.01,
          ))
          transform *= Transform3D.translate(Vector3D(
            random.nextGaussian() * 0.01,
            random.nextGaussian() * 0.01,
            random.nextGaussian() * 0.01,
          ))
        }

        transform *= Transform3D.scale(Vector3D(hologram.scale / 16f, hologram.scale / 16f, hologram.scale / 16f))

        val hologramMesh = new SceneMesh3D(mesh)
        hologramMesh.isOpaque = false
        hologramMesh.transform = transform
        hologramMesh.color = Color.White.withAlpha(0.75f)
        scene.meshes += hologramMesh
      })

      scene
    }
  }

  setInner(viewport)

  override protected def title: String = "Hologram Projector " + hologramProjectorNode.label.getOrElse("")

  override protected def titleMaxLength: Int = 69

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

    if (hologram.isDirty) {
      updateMesh()
      hologram.isDirty = false
    }
  }

  private def updateMesh(): Unit = {
    val builder = new MeshBuilder3D

    def getValue(x: Int, y: Int, z: Int): Int = {
      if (x >= 0 && y >= 0 && z >= 0 && x < hologram.width && z < hologram.width && y < hologram.height)
        hologram.getColor(x, y, z)
      else
        0
    }

    def isEmpty(x: Int, y: Int, z: Int): Boolean = getValue(x, y, z) == 0

    def getColor(x: Int, y: Int, z: Int): Color = IntColor(hologram.colors(getValue(x, y, z) - 1))

    var hologramIsEmpty = true

    for (x <- 0 until hologram.width) {
      for (z <- 0 until hologram.width) {
        for (y <- 0 until hologram.height) {
          if (!isEmpty(x, y, z)) {
            hologramIsEmpty = false
            val color = getColor(x, y, z)
            val face = Some(("blocks/HologramEffect", Rect2D.Unit))

            builder.cuboid(
              Vector3D(x, y, z),
              Vector3D(x + 1, y + 1, z + 1),
              color = color,
              left = face.filter(_ => isEmpty(x - 1, y, z)),
              right = face.filter(_ => isEmpty(x + 1, y, z)),
              top = face.filter(_ => isEmpty(x, y + 1, z)),
              bottom = face.filter(_ => isEmpty(x, y - 1, z)),
              front = face.filter(_ => isEmpty(x, y, z - 1)),
              back = face.filter(_ => isEmpty(x, y, z + 1)),
            )
          }
        }
      }
    }

    if (!hologramIsEmpty) {
      mesh = Some(builder.build())
    } else {
      mesh = None
    }
  }

  override def dispose(): Unit = {
    viewport.dispose()
  }
}
