package ocelot.desktop.ui.widget

import ocelot.desktop.ColorScheme
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.graphics.Graphics
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.HoverHandler
import ocelot.desktop.ui.event.sources.KeyEvents
import ocelot.desktop.ui.event.{MouseEvent, ScrollEvent}
import ocelot.desktop.util.Logging
import org.lwjgl.input.Keyboard

class ScrollView(val inner: Widget) extends Widget with Logging with HoverHandler {
  inner.size = Size2D.Zero
  children +:= inner

  var scrollToEnd = false

  private var xOffset = 0f
  private var yOffset = 0f
  private var xAcceleration = 0f
  private var yAcceleration = 0f
  private var dragging = 0
  private var mouseOldPos = Vector2D(0, 0)

  private var vAnim = 0f
  private var vAnimDir = -1
  private var hAnim = 0f
  private var hAnimDir = -1

  def offset: Vector2D = Vector2D(xOffset, yOffset)

  override def receiveScrollEvents = true

  override def receiveMouseEvents = true

  eventHandlers += {
    case ScrollEvent(offset) =>
      if (bounds.contains(UiHandler.mousePosition)) {
        if (KeyEvents.isShiftDown) {
          xAcceleration -= offset * 385f
        } else {
          yAcceleration -= offset * 385f
          scrollToEnd = false
        }
      }

    case event: MouseEvent if event.state == MouseEvent.State.Press =>
      val pos = UiHandler.mousePosition
      mouseOldPos = pos

      dragging = {
        if (vThumbBounds.contains(pos)) 1
        else if (hThumbBounds.contains(pos)) 2
        else 0
      }

      if (dragging == 1) {
        scrollToEnd = false
      }

    case event: MouseEvent if event.state == MouseEvent.State.Release =>
      dragging = 0
  }

  override def minimumSize: Size2D = Size2D.Zero

  override def size_=(value: Size2D): Unit = {
    super.size_=(value)
    clampOffsets()
  }

  override def shouldClip = true

  override def draw(g: Graphics): Unit = {
    if (xOffset.abs > 0 || yOffset.abs > 0) {
      inner.position = position - Vector2D(xOffset, yOffset)
    }

    inner.draw(g)

    if (vThumbVisible)
      drawVThumb(g)

    if (hThumbVisible)
      drawHThumb(g)
  }

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

    xAcceleration *= 1f - 5f * UiHandler.dt
    xAcceleration = xAcceleration.min(1540f).max(-1540f)
    xOffset += xAcceleration * UiHandler.dt

    if (scrollToEnd) {
      yOffset = maxYOffset
      yAcceleration = 0

      if (dragging == 1) {
        dragging = 0
      }
    } else {
      yAcceleration *= 1f - 5f * UiHandler.dt
      yAcceleration = yAcceleration.min(1540f).max(-1540f)
      yOffset += yAcceleration * UiHandler.dt
    }

    clampOffsets()

    val mousePos = UiHandler.mousePosition

    vAnimDir = if (isHovered && vThumbHoverArea.contains(mousePos) || dragging == 1) 1 else -1
    hAnimDir = if (isHovered && hThumbHoverArea.contains(mousePos) || dragging == 2) 1 else -1

    vAnim = math.max(0f, math.min(1f, vAnim + UiHandler.dt / 0.2f * vAnimDir))
    hAnim = math.max(0f, math.min(1f, hAnim + UiHandler.dt / 0.2f * hAnimDir))

    if (dragging == 0) return

    if (dragging == 1) {
      val dy = (mousePos - mouseOldPos).y
      yOffset += dy / vThumbCoeff
    } else if (dragging == 2) {
      val dx = (mousePos - mouseOldPos).x
      xOffset += dx / hThumbCoeff
    }

    clampOffsets()
    mouseOldPos = mousePos
  }

  private def drawVThumb(g: Graphics): Unit = {
    val b = vThumbBounds
    g.rect(position.x + size.width - 11, position.y, 10, size.height, ColorScheme("Scrollbar").mapA(_ * vAnim))
    g.rect(b.x + 3, b.y, b.w - 6, b.h, ColorScheme("ScrollbarThumb").withAlpha(vAnim * 0.5f + 0.4f))
  }

  private def drawHThumb(g: Graphics): Unit = {
    val b = hThumbBounds
    g.rect(position.x, position.y + size.height - 11, size.width - 12, 10, ColorScheme("Scrollbar").mapA(_ * hAnim))
    g.rect(b.x, b.y + 3, b.w, b.h - 6, ColorScheme("ScrollbarThumb").withAlpha(hAnim * 0.5f + 0.4f))
  }

  private def maxXOffset: Float = inner.size.width - size.width

  private def maxYOffset: Float = inner.size.height - size.height

  private def clampOffsets(): Unit = {
    xOffset = xOffset.min(maxXOffset).max(0f)
    yOffset = yOffset.min(maxYOffset).max(0f)
  }

  private def vThumbHoverArea: Rect2D = {
    Rect2D(position.x + size.width - 12, position.y, 12, size.height)
  }

  private def vThumbBounds: Rect2D = {
    val x = position.x + size.width - 12
    val y = position.y + yOffset * vThumbCoeff

    Rect2D(x, y + 2, 12, vThumbCoeff * size.height)
  }

  private def hThumbHoverArea: Rect2D = {
    Rect2D(position.x, position.y + size.height - 12, size.width, 12)
  }

  private def hThumbBounds: Rect2D = {
    val x = position.x + xOffset * hThumbCoeff
    val y = position.y + size.height - 12

    Rect2D(x + 2, y, hThumbCoeff * size.width, 12)
  }

  protected def vThumbVisible: Boolean = inner.size.height > size.height

  protected def hThumbVisible: Boolean = inner.size.width > size.width

  private def vThumbCoeff: Float = (size.height - 4) / inner.size.height

  private def hThumbCoeff: Float = (size.width - 14) / inner.size.width
}
