package ocelot.desktop.ui.event.handlers

import ocelot.desktop.geometry.Vector2D
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.MouseHandler.Tolerance
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, MouseEvent}
import ocelot.desktop.ui.widget.Widget

import scala.collection.mutable

trait MouseHandler extends Widget {
  private val startPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]()
  private val prevPositions = new mutable.HashMap[MouseEvent.Button.Value, Vector2D]()
  private val dragButtons = new mutable.HashSet[MouseEvent.Button.Value]()

  override def receiveMouseEvents: Boolean = receiveClickEvents || receiveDragEvents

  protected def receiveClickEvents: Boolean = false

  protected def receiveDragEvents: Boolean = false

  /** If `true`, a [[ClickEvent]] will be registered even if the mouse button is released
    * outside the tolerance threshold, as long as it stays within the widget's bounds.
    */
  protected def allowClickReleaseOutsideThreshold: Boolean = !receiveDragEvents

  eventHandlers += {
    case MouseEvent(MouseEvent.State.Press, button) =>
      startPositions += (button -> UiHandler.mousePosition)

    case MouseEvent(MouseEvent.State.Release, button) =>
      val mousePos = UiHandler.mousePosition

      val dragStopped = receiveDragEvents && dragButtons.remove(button)
      val clicked = (
        receiveClickEvents
          && !dragStopped
          && startPositions.get(button).exists(p => {
            if (allowClickReleaseOutsideThreshold) {
              clippedBounds.contains(mousePos)
            } else {
              (p - mousePos).lengthSquared < Tolerance * Tolerance
            }
          })
      )

      if (dragStopped) {
        handleEvent(DragEvent(DragEvent.State.Stop, button, mousePos))
      }

      if (clicked) {
        handleEvent(ClickEvent(button, mousePos))
      }

      startPositions.remove(button)
  }

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

    if (!receiveDragEvents) {
      return
    }

    val mousePos = UiHandler.mousePosition

    for ((button, startPos) <- startPositions) {
      if (!dragButtons.contains(button) && (startPos - mousePos).lengthSquared > Tolerance * Tolerance) {
        handleEvent(DragEvent(DragEvent.State.Start, button, mousePos, startPos, Vector2D(0, 0)))
        dragButtons += button
        prevPositions += (button -> mousePos)
      }
    }

    dragButtons.foreach(button => {
      handleEvent(
        DragEvent(
          DragEvent.State.Drag,
          button,
          mousePos,
          startPositions(button),
          mousePos - prevPositions(button),
        )
      )
      prevPositions(button) = mousePos
    }
    )
  }
}

object MouseHandler {
  private val Tolerance = 8
}
