package ocelot.desktop.ui.widget.window

import ocelot.desktop.Settings
import ocelot.desktop.geometry.{Rect2D, Size2D, Vector2D}
import ocelot.desktop.ui.UiHandler
import ocelot.desktop.ui.event.handlers.MouseHandler
import ocelot.desktop.ui.event.{Capturing, DragEvent, KeyEvent, MouseEvent}
import ocelot.desktop.ui.widget.Widget
import ocelot.desktop.util.Persistable
import org.lwjgl.input.Keyboard
import totoro.ocelot.brain.nbt.NBTTagCompound

object Window {
  object State extends Enumeration {
    val Opening, Open, Closing, Closed = Value
  }
}

trait Window extends Widget with Persistable with MouseHandler {
  private final var _state: Window.State.Value = Window.State.Closed
  private final var _isFocused: Boolean = false
  private final var _isPinned: Boolean = Settings.get.pinNewWindows
  private var grabPoint: Option[Vector2D] = None
  private var disposeOnClose: Boolean = false
  private var isDisposed: Boolean = false
  private var isFitToCenterRequired = true

  private[window] var poolIndex = -1

  override protected def receiveDragEvents: Boolean = true

  eventHandlers += {
    // if the window contains a widget that receives all mouse events, this window will get the event during
    // the capture phase regardless of whether the user actually clicked anywhere inside it,
    // so we have to check the mouse position.
    case Capturing(MouseEvent(MouseEvent.State.Pressed, _)) if bounds.contains(UiHandler.mousePosition) =>
      focus()

    case ev @ DragEvent(DragEvent.State.Start, MouseEvent.Button.Left, mousePos) =>
      if (dragRegions.exists(_.contains(ev.start))) {
        grabPoint = Some(mousePos - position)
      }

    case DragEvent(DragEvent.State.Drag, MouseEvent.Button.Left, mousePos) =>
      for (g <- grabPoint) {
        position = mousePos - g
      }

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

    case KeyEvent(KeyEvent.State.Release, Keyboard.KEY_ESCAPE, _) if canCloseOnEsc =>
      close()
  }

  protected final def windowPool: WindowPool = UiHandler.root.workspaceView.windowPool

  final def state: Window.State.Value = _state

  final def state_=(newState: Window.State.Value): Unit = {
    var prevState = _state
    _state = newState

    while (_state != prevState) {
      prevState = _state

      _state = _state match {
        case Window.State.Opening => onOpening()
        case Window.State.Open => onOpen()
        case Window.State.Closing => onClosing()
        case Window.State.Closed => onClosed()
      }
    }
  }

  final def isOpening: Boolean = state == Window.State.Opening

  final def isOpen: Boolean = state == Window.State.Open

  final def isClosing: Boolean = state == Window.State.Closing

  final def isClosed: Boolean = state == Window.State.Closed

  def fitToCenter(): Unit = {
    size = minimumSize
    position = (windowPool.size / 2 - size / 2).toVector.round
  }

  protected def onOpening(): Window.State.Value = {
    // Newly created window should be aligned to center
    if (isFitToCenterRequired) {
      fitToCenter()

      isFitToCenterRequired = false
    }

    windowPool.addWindow(this)
    Window.State.Open
  }

  protected def onOpen(): Window.State.Value = {
    Window.State.Open
  }

  protected def onClosing(): Window.State.Value = {
    Window.State.Closed
  }

  protected def onClosed(): Window.State.Value = {
    windowPool.removeWindow(this)

    if (disposeOnClose) {
      dispose()
    }

    Window.State.Closed
  }

  final def open(): Unit = {
    if (!isOpen) {
      require(!isDisposed, "tried to open a disposed window")
      state = Window.State.Opening
    }
  }

  final def close(): Unit = {
    if (!isClosed) {
      state = Window.State.Closing
    }
  }

  final def isFocused: Boolean = _isFocused

  protected def onFocused(): Unit = {}

  protected def onUnfocused(): Unit = {}

  private[window] final def setFocused(): Unit = {
    if (!isFocused) {
      _isFocused = true
      onFocused()
    }
  }

  private[window] final def setUnfocused(): Unit = {
    if (isFocused) {
      _isFocused = false
      onUnfocused()
    }
  }

  final def focus(): Unit = {
    windowPool.focusWindow(this)
  }

  final def unfocus(): Unit = {
    windowPool.unfocusWindow(this)
  }

  final def isPinned: Boolean = _isPinned

  protected def onPinned(): Unit = {}

  protected def onUnpinned(): Unit = {}

  final def pin(): Unit = {
    if (!_isPinned) {
      _isPinned = true
      onPinned()
    }
  }

  final def unpin(): Unit = {
    if (_isPinned) {
      _isPinned = false
      onUnpinned()
    }
  }

  /** Updates the window state / render, by reading and applying fresh setting values.
    */
  def refresh(): Unit = {}

  /** Disposes of the window immediately. This will make it dramatically pop out of existence,
    * without playing the closing animation.
    *
    * Unless you know what you're doing, use `closeAndDispose`.
    */
  override def dispose(): Unit = {
    super.dispose()

    isDisposed = true
    windowPool.removeWindow(this)
  }

  def closeAndDispose(): Unit = {
    if (UiHandler.terminating && !isDisposed) {
      dispose()
    } else {
      disposeOnClose = true
      close()
    }
  }

  private def canCloseOnEsc: Boolean = isFocused && !root.get.modalDialogPool.isVisible

  protected def dragRegions: Iterator[Rect2D] = Iterator(bounds)

  override def save(nbt: NBTTagCompound): Unit = {
    nbt.setBoolean("focused", _isFocused)
    nbt.setBoolean("pinned", _isPinned)
    nbt.setInteger("state", _state.id)

    val positionNBT = new NBTTagCompound
    position.save(positionNBT)
    nbt.setTag("position", positionNBT)

    val sizeNBT = new NBTTagCompound
    size.save(sizeNBT)
    nbt.setTag("size", sizeNBT)
  }

  override def load(nbt: NBTTagCompound): Unit = {
    isFitToCenterRequired = false

    _isFocused = nbt.getBoolean("focused")

    _isPinned = {
      if (nbt.hasKey("pinned"))
        nbt.getBoolean("pinned")
      else
        Settings.get.pinNewWindows
    }

    position = {
      if (nbt.hasKey("position"))
        new Vector2D(nbt.getCompoundTag("position"))
      else
        new Vector2D(nbt)
    }

    if (nbt.hasKey("size"))
      size = new Size2D(nbt.getCompoundTag("size"))

    _state = Window.State(nbt.getInteger("state"))

    windowPool.recoverLoadedWindow(this)
  }

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

    if (canCloseOnEsc)
      root.get.statusBar.addKeyEntry("ESC", "Close window")
  }
}
