package ocelot.desktop.util

import com.typesafe.config.{Config, ConfigValue, ConfigValueFactory}
import ocelot.desktop.Settings
import ocelot.desktop.ui.event.KeyEvent
import ocelot.desktop.ui.event.KeyEvent.State
import ocelot.desktop.util.Keybind.Keybind
import org.lwjgl.input.Keyboard

import scala.collection.mutable
import scala.jdk.CollectionConverters._


class Keymap extends Logging {
  // default mappings
  val map: mutable.Map[Keybind.Value, Int] = mutable.Map(
    // OpenComputers
    Keybind.Insert -> Keyboard.KEY_INSERT,

    // Workspace
    Keybind.Center -> Keyboard.KEY_HOME,
    Keybind.QuickSave -> Keyboard.KEY_F5,
    Keybind.QuickLoad -> Keyboard.KEY_F9,
    Keybind.Screenshot -> Keyboard.KEY_F12,
    Keybind.PauseEmulation -> Keyboard.KEY_SPACE,

    // Ocelot
    Keybind.UIDebug -> Keyboard.KEY_F1,
    Keybind.FPSCounter -> Keyboard.KEY_F2,
    Keybind.ReloadWorkspace -> Keyboard.KEY_F3,
    Keybind.Profiler -> Keyboard.KEY_F4,
    Keybind.Fullscreen -> Keyboard.KEY_F11,
  )


  /** Retrieves the LWJGL keycode which is associated with the given keybind.
    * Will return `Keyboard.KEY_NONE` if the binding is not found.
    *
    *  @param  keybind  the keybind
    *  @return the actual LWJGL key associated with the given keybind,
    *          or `Keyboard.KEY_NONE` if none is.
    */
  def apply(keybind: Keybind): Int = map.getOrElse(keybind, Keyboard.KEY_NONE)

  /** Adds a new keybind mapping and optionally returns previous value.
    *  If the map already contains a
    *  mapping for the keybind, it will be overridden by the new value.
    *
    * @param  keybind  the keybind to update
    * @param  key      the new LWJGL key code
    * @return an option value containing the LWJGL key code associated with the keybind
    *         before the `set` operation was executed, or `None` if this keybind
    *         was not defined in the keymap before.
    */
  def set(keybind: Keybind.Value, key: Int): Option[Int] = map.put(keybind, key)

  /**
    * Retrieves the name for a keyboard key associated with the given keybind.
    * @param keybind  the keybind
    * @return         the name of the button on the keyboard, for example "HOME", or "F1"
    */
  def name(keybind: Keybind): String = Keyboard.getKeyName(apply(keybind))


  override def clone(): Keymap = {
    val clone = new Keymap
    clone.map.addAll(this.map)
    clone
  }


  /**
    * Tries to find a keybind that corresponds to the specific LWJGL key code
    * @param key  the LWJGL key code
    * @return     Option containing the keybind (if found)
    */
  private def findByCode(key: Int): Option[Keybind] = map.iterator.collectFirst { case (keybind: Keybind, `key`) => keybind }


  /**
    * Attempts to read known keybindings from a config.
    * Will ignore missing or unfamiliar values, retaining default values.
    */
  def load(config: Config): Unit = {
    Keybind.values.foreach(keybind => {
      val path = keybind.toString.toLowerCase
      if (config.hasPath(path)) {
        try {
          set(keybind, config.getInt(path))
        } catch {
          case e: Throwable => logger.error(s"Skipping keymap config for '$path'...", e)
        }
      }
    })
  }

  /**
    * Generates a ConfigValue with this keybindings map,
    * (which can be embedded into any Config).
    */
  def save(): ConfigValue = ConfigValueFactory.fromMap((map.view map {
    case (key: Keybind, value: Int) => (key.toString.toLowerCase(), value)
  }).toMap.asJava)
}

object Keymap {
  object Press {
    def unapply(event: KeyEvent): Option[Keybind] = event match {
      case KeyEvent(State.Press, code, _) =>
        Settings.get.keymap.findByCode(code)
      case _ => None
    }
  }
  object Release {
    def unapply(event: KeyEvent): Option[Keybind] = event match {
      case KeyEvent(State.Release, code, _) =>
        Settings.get.keymap.findByCode(code)
      case _ => None
    }
  }
}
