package ocelot.desktop.windows

import ocelot.desktop.color.RGBAColorNorm
import ocelot.desktop.geometry.{Padding2D, Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.node.nodes.ScreenNode
import ocelot.desktop.node.nodes.ScreenNode.{FontHeight, FontWidth}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{DragEvent, MouseEvent}
import ocelot.desktop.ui.layout.{AlignItems, LinearLayout}
import ocelot.desktop.ui.widget.window.PanelWindow
import ocelot.desktop.ui.widget.{PaddingBox, ScreenView}
import ocelot.desktop.util.{DrawUtils, Logging}
import ocelot.desktop.windows.ScreenWindow._
import totoro.ocelot.brain.nbt.NBTTagCompound

class ScreenWindow(screenNode: ScreenNode) extends PanelWindow with Logging {
  private var startingWidth = 0f
  private var scaleDragPoint: Option[Vector2D] = None

  override protected def title: String = screenNode.label.get

  override protected def titleMaxLength: Int =
    ((screenWidth * FontWidth * View.scaleX - 15) / FontWidth).toInt

  private def screenWidth: Int = screenNode.screenWidth

  private def screenHeight: Int = screenNode.screenHeight

  override def receiveScrollEvents: Boolean = true

  override def maximumSize: Size2D = minimumSize

  private object View extends ScreenView(screenNode) {
    override protected def isFocused: Boolean = ScreenWindow.this.isFocused
  }

  setInner(
    View,
    padding = Padding2D(
      right = BorderRight,
      bottom = BorderBottom,
      left = BorderLeft,
    ),
    titlePadding = Padding2D(
      top = 2,
      right = BorderRight - 4,
      bottom = 2,
      left = BorderLeft - 4,
    ),
    contentFactory =
      (inner, padding) => LinearLayout.WithOptions(align = Some(AlignItems.Center))(new PaddingBox(inner, padding)),
  )

  eventHandlers += {
    case event @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, _) =>
      if (scaleDragRegion.contains(event.start)) {
        scaleDragPoint = Some(event.start)
        startingWidth = screenWidth * FontWidth * View.scaleX
      }

    case DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, mousePos) =>
      for (point <- scaleDragPoint) {
        val sx = point.x - mousePos.x
        val sy = point.y - mousePos.y

        val uiScale = UiHandler.scalingFactor
        var newScale = View.scale.nextValue

        // TODO: refactor this mess, make it consider both sizes and not have two nearby slightly off "snap points"

        if (sx.abs > sy.abs) {
          val newWidth = startingWidth - sx
          val maxWidth = screenWidth * FontWidth
          var midScale = (newWidth / maxWidth).max(0f)

          if (!KeyEvents.isShiftDown && View.scale.nextValue <= 1.001)
            midScale = midScale.min(1f)

          val lowScale = (FontWidth * midScale * uiScale).floor / FontWidth / uiScale
          val highScale = (FontWidth * midScale * uiScale).ceil / FontWidth / uiScale

          newScale = if (midScale - lowScale > highScale - midScale) highScale else lowScale
        } else {
          val newHeight = startingWidth * (screenHeight * FontHeight / screenWidth / FontWidth) - sy
          val maxHeight = screenHeight * FontHeight
          var midScale = (newHeight / maxHeight).max(0f)

          if (!KeyEvents.isShiftDown && View.scale.nextValue <= 1.001)
            midScale = midScale.min(1f)

          val lowScale = (FontHeight * midScale * uiScale).floor / FontHeight / uiScale
          val highScale = (FontHeight * midScale * uiScale).ceil / FontHeight / uiScale

          newScale = if (midScale - lowScale > highScale - midScale) highScale else lowScale
        }

        // enforce minimal screen size
        if (newScale <= 0.249f) {
          newScale = 0.25f
        }

        View.scale.nextValue = newScale
      }

    case DragEvent(DragEvent.State.Stop, MouseEvent.Button.Left, _) =>
      scaleDragPoint = None
  }

  override def save(nbt: NBTTagCompound): Unit = {
    View.save(nbt)
    super.save(nbt)
  }

  override def load(nbt: NBTTagCompound): Unit = {
    super.load(nbt)
    View.load(nbt)
  }

  override def fitToCenter(): Unit = {
    View.scale.nextValue = math.min(
      ((UiHandler.root.width * 0.9f) / (screenWidth * FontWidth + BorderHorizontal)).min(1f).max(0f),
      ((UiHandler.root.height * 0.9f) / (screenHeight * FontHeight + BorderVertical)).min(1f).max(0f),
    )

    super.fitToCenter()
  }

  override protected def dragRegions: Iterator[Rect2D] = Iterator(
    Rect2D(position.x, position.y, size.width, View.position.y - position.y),
    Rect2D(position.x, position.y, View.position.x - position.x, size.height),

    // these two must not include `scaleDragRegion`
    Rect2D(position.x, View.bounds.max.y, size.width - BorderRight, bounds.max.y - View.bounds.max.y),
    Rect2D(View.bounds.max.x, position.y, bounds.max.y - View.bounds.max.y, size.height - BorderBottom),
  )

  private def scaleDragRegion: Rect2D = Rect2D(
    position.x + size.width - BorderRight,
    position.y + size.height - BorderBottom,
    BorderRight.toFloat,
    BorderBottom.toFloat,
  )

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

    if (scaleDragPoint.isDefined || scaleDragRegion.contains(UiHandler.mousePosition)) {
      root.get.statusBar.addMouseEntry(IconSource.Icons.DragLMB, "Scale screen")
      root.get.statusBar.addKeyMouseEntry(IconSource.Icons.DragLMB, "SHIFT", "Scale screen (magnify)")
    }
  }

  override def draw(g: Graphics): Unit = {
    beginDraw(g)
    DrawUtils.shadow(g, position.x - 8, position.y - 8, size.width + 16, size.height + 20, 0.5f)
    DrawUtils.screenBorder(g, bounds, View.bounds, RGBAColorNorm(1, 1, 1))
    drawChildren(g)
    endDraw(g)
  }
}

object ScreenWindow {
  val BorderTop = 20
  val BorderLeft = 16
  val BorderRight = 16
  val BorderBottom = 16

  val BorderVertical: Int = BorderTop + BorderBottom
  val BorderHorizontal: Int = BorderLeft + BorderRight
}
