package ocelot.desktop.ui.event.sources

import ocelot.desktop.ui.event.KeyEvent
import ocelot.desktop.ui.event.KeyEvent.State
import ocelot.desktop.util.Logging
import org.lwjgl.input.Keyboard

import scala.collection.mutable

object KeyEvents extends Logging {
  private val _events = new mutable.ArrayBuffer[KeyEvent]
  private val pressedKeys = new mutable.HashMap[Int, Char]()

  private val noRepeatKeys = Set(
    Keyboard.KEY_LCONTROL,
    Keyboard.KEY_RCONTROL,
    Keyboard.KEY_LMENU,
    Keyboard.KEY_RMENU,
    Keyboard.KEY_LSHIFT,
    Keyboard.KEY_RSHIFT,
    Keyboard.KEY_LMETA,
    Keyboard.KEY_RMETA,
  )

  def events: Iterator[KeyEvent] = _events.iterator

  def init(): Unit = {
    Keyboard.create()
    Keyboard.enableRepeatEvents(true)
  }

  def update(): Unit = {
    _events.clear()

    while (Keyboard.next()) {
      val code = Keyboard.getEventKey

      val state = Keyboard.getEventKeyState match {
        case _ if Keyboard.isRepeatEvent => KeyEvent.State.Repeat
        case true if pressedKeys.contains(code) => KeyEvent.State.Repeat
        case true => KeyEvent.State.Press
        case false => KeyEvent.State.Release
      }

      val char = state match {
        case KeyEvent.State.Repeat if noRepeatKeys.contains(code) =>
          // it seems these keys don't cause repeat events, but for some reason the OC code ensures they don't anyway
          None

        case KeyEvent.State.Press | KeyEvent.State.Repeat =>
          val char = Keyboard.getEventCharacter
          pressedKeys += code -> char

          Some(char)

        case KeyEvent.State.Release =>
          pressedKeys.remove(code)
      }

      if (char.nonEmpty) {
        _events += KeyEvent(state, code, char.get)
      }
    }
  }

  def destroy(): Unit = {
    Keyboard.destroy()
  }

  def isDown(code: Int): Boolean = pressedKeys.contains(code)
  def isShiftDown: Boolean = isDown(Keyboard.KEY_LSHIFT) || isDown(Keyboard.KEY_RSHIFT)
  def isControlDown: Boolean = isDown(Keyboard.KEY_LCONTROL) || isDown(Keyboard.KEY_RCONTROL)

  def isReleased(code: Int): Boolean = _events.exists(event => event.state == State.Release && event.code == code)

  def releaseKeys(): Unit = {
    for ((code, char) <- pressedKeys) {
      _events += KeyEvent(KeyEvent.State.Release, code, char)
    }

    pressedKeys.clear()
  }
}
