package ocelot.desktop.ui.widget.slot

import ocelot.desktop.audio.SoundSource
import ocelot.desktop.color.Color
import ocelot.desktop.geometry.{Size2D, Vector2D}
import ocelot.desktop.graphics.{Graphics, IconSource}
import ocelot.desktop.inventory.{Inventory, Item, ItemFactory}
import ocelot.desktop.ui.Const.{SlotHighlightAlpha, SlotHighlightAnimationSpeedEnter, SlotHighlightAnimationSpeedLeave}
import ocelot.desktop.ui.event.handlers.{HoverHandler, MouseHandler}
import ocelot.desktop.ui.event.{ClickEvent, DragEvent, HoverEvent, MouseEvent}
import ocelot.desktop.ui.widget.Widget
import ocelot.desktop.ui.widget.contextmenu.{ContextMenu, ContextMenuEntry}
import ocelot.desktop.ui.widget.itemdrag.DraggedItem
import ocelot.desktop.ui.widget.tooltip.{ItemTooltip, Tooltip}
import ocelot.desktop.util.animation.ValueAnimation
import totoro.ocelot.brain.util.Tier
import totoro.ocelot.brain.util.Tier.Tier

import scala.math.Ordering.Implicits.infixOrderingOps
import scala.reflect.ClassTag

class SlotWidget[I <: Item](private val slot: Inventory#Slot)(implicit slotItemTag: ClassTag[I])
    extends Widget with MouseHandler with HoverHandler {

  slotWidget =>

  private var disposed = false

  // NOTE: this must remain as a separate object
  // (i.e. don't you dare inline it into the addObserver call below):
  // this is the only strong reference to the observer
  private val observer = new Inventory.SlotObserver {
    override def onItemAdded(): Unit = {
      slotWidget.onItemAdded()
    }

    override def onItemRemoved(removedItem: Item, replacedBy: Option[Item]): Unit = {
      slotWidget.onItemRemoved(removedItem.asInstanceOf[I], replacedBy.map(_.asInstanceOf[I]))
    }

    override def onItemNotification(notification: Item.Notification): Unit = {
      slotWidget.onItemNotification(notification)
    }
  }

  slot.addObserver(observer)

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

    closeTooltip()
    slot.removeObserver(observer)
    disposed = true
  }

  def ghostIcon: Option[IconSource] = None

  def slotTier: Option[Tier] = None

  def tierIcon: Option[IconSource] = slotTier.map(IconSource.Icons.Tier)

  def itemIcon: Option[IconSource] = item.map(_.icon)

  def item: Option[I] = slot.get.filter(_ => !disposed).map(_.asInstanceOf[I])

  def item_=(item: Option[I]): Unit = if (!disposed) {
    slot.set(item.map(_.asInstanceOf[slot.inventory.I]))
  }

  def item_=(item: I): Unit = if (!disposed) {
    slot.put(item.asInstanceOf[slot.inventory.I])
  }

  def setItem(item: Item): Unit = if (!disposed) {
    slot.put(item.asInstanceOf[slot.inventory.I])
  }

  private def isTierCompatible(tier: Option[Tier]): Boolean = (tier, slotTier) match {
    case (Some(lhs), Some(rhs)) => (lhs min Tier.Three) <= rhs
    case (Some(_), None) => true
    case (None, Some(_)) => false
    case (None, None) => true
  }

  def isItemAccepted(factory: ItemFactory): Boolean = {
    slotItemTag.runtimeClass.isAssignableFrom(factory.itemClass) && isTierCompatible(factory.tier)
  }

  protected def onItemAdded(): Unit = {}

  protected def onItemRemoved(removedItem: I, replacedBy: Option[I]): Unit = {
    closeTooltip()
  }

  protected def onItemNotification(notification: Item.Notification): Unit = {}

  override final def minimumSize: Size2D = Size2D(36, 36)
  override final def maximumSize: Size2D = minimumSize

  override protected def receiveClickEvents: Boolean = true

  override protected def receiveDragEvents: Boolean = true

  protected def lmbMenuEnabled: Boolean = true

  protected def rmbMenuEnabled: Boolean = item.nonEmpty

  protected def fillRmbMenu(menu: ContextMenu): Unit = {
    for (item <- item) {
      item.fillRmbMenu(menu)
      menu.addEntry(
        ContextMenuEntry("Remove", IconSource.Icons.Delete, SoundSource.InterfaceClickLow) {
          slot.removeAndDispose()
        }
      )
    }
  }

  private var _tooltip: Option[Tooltip] = None

  private val highlightAlpha = new ValueAnimation(0f)
  private var _hovered = false
  private var _lmbMenuOpen = false
  private var _rmbMenuOpen = false

  private def hovered: Boolean = _hovered
  private def hovered_=(value: Boolean): Unit = updatingHighlight { _hovered = value }

  private def lmbMenuOpen: Boolean = _lmbMenuOpen
  private def lmbMenuOpen_=(value: Boolean): Unit = updatingHighlight { _lmbMenuOpen = value }

  private def rmbMenuOpen: Boolean = _rmbMenuOpen
  private def rmbMenuOpen_=(value: Boolean): Unit = updatingHighlight { _rmbMenuOpen = value }

  private def highlightShown: Boolean = _hovered || _lmbMenuOpen || _rmbMenuOpen

  private def updatingHighlight(f: => Unit): Unit = {
    val prev = highlightShown
    f

    if (highlightShown != prev) {
      if (highlightShown) {
        highlightAlpha.speed = SlotHighlightAnimationSpeedEnter
        highlightAlpha.goto(SlotHighlightAlpha)
      } else {
        highlightAlpha.speed = SlotHighlightAnimationSpeedLeave
        highlightAlpha.goto(0f)
      }
    }
  }

  def onHoverEnter(): Unit = {
    hovered = true

    for (item <- item) {
      // just in case
      closeTooltip()

      val tooltip = new ItemTooltip
      item.fillTooltip(tooltip)
      _tooltip = Some(tooltip)
      root.get.tooltipPool.addTooltip(tooltip)
    }
  }

  def onHoverLeave(): Unit = {
    hovered = false

    closeTooltip()
  }

  private def closeTooltip(): Unit = {
    for (tooltip <- _tooltip) {
      root.get.tooltipPool.closeTooltip(tooltip)
    }
  }

  protected def onLeftMouseButtonClick(): Unit = {
    if (lmbMenuEnabled) {
      root.get.contextMenus.open(new ItemChooser(this) {
        override def open(): Unit = {
          super.open()
          lmbMenuOpen = true
        }

        override def close(): Unit = {
          super.close()
          lmbMenuOpen = false
        }
      })
    }
  }

  protected def onRightMouseButtonClick(): Unit = {
    if (rmbMenuEnabled) {
      val menu = new ContextMenu {
        override def open(): Unit = {
          super.open()
          rmbMenuOpen = true
        }

        override def close(): Unit = {
          super.close()
          rmbMenuOpen = false
        }
      }
      fillRmbMenu(menu)
      root.get.contextMenus.open(menu)
    }
  }

  eventHandlers += {
    case ClickEvent(MouseEvent.Button.Left, _) => onLeftMouseButtonClick()
    case ClickEvent(MouseEvent.Button.Right, _) => onRightMouseButtonClick()

    case HoverEvent(HoverEvent.State.Enter) => onHoverEnter()
    case HoverEvent(HoverEvent.State.Leave) => onHoverLeave()

    case evt @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, _) =>
      val pos = evt.start
      val iconBounds = bounds.inflated(-2)

      if (iconBounds.contains(pos)) {
        for (item <- item) {
          val grabPoint = pos - position - Vector2D(2, 2)
          root.get.draggedItemPool.draggedItem = DraggedItem(item, this, grabPoint)
          slot.remove()
        }
      }
  }

  override final def draw(g: Graphics): Unit = {
    g.sprite(IconSource.EmptySlot, bounds)

    val iconBounds = bounds.inflated(-2)

    itemIcon match {
      case Some(itemIcon) => g.sprite(itemIcon, iconBounds)

      case None =>
        for (tierIcon <- tierIcon) {
          g.sprite(tierIcon, iconBounds)
        }

        for (ghostIcon <- ghostIcon) {
          g.sprite(ghostIcon, iconBounds)
        }
    }

    g.rect(iconBounds, Color.White.mapA(_ => highlightAlpha.value))
  }

  override def update(): Unit = {
    highlightAlpha.update()
    item.foreach(_.update())

    super.update()
  }
}
