# adapted from illwill's examples/keycodes.nim
import std/[math, os, strformat, times]
import std/[atomics, strutils]
import illwill, nimler

var
  uiThread: Thread[void]
  gLastKeyPressed: Atomic[Key]
  stopFlag: Atomic[bool]
gLastKeypressed.store(Key.None)
stopFlag.store(false)

proc controlC {.noconv.} =
  gLastKeyPressed.store(Key.CtrlC)

proc updateScreen(tb: var TerminalBuffer) =
  let
    x1 = 3
    y1 = 1
    x2 = max(tb.width-4, x1)
    y2 = max(tb.height-2, y1)
    fillChars = "|/-\\"

  let t = epochTime()
  let ch = fillChars[((t * 5) mod fillChars.len.float).int]

  tb.setForegroundColor(fgBlack, bright=true)
  tb.fill(x1,y1, x2, y2, $ch)

  tb.setForegroundColor(fgWhite)
  tb.drawRect(x1, y1, x2, y2)

  tb.setForegroundColor(fgWhite, bright=true)
  tb.write(x1+1, y1+1, "Press keys/key combinations on the keyboard to view their keycode")
  tb.write(x1+1, y1+2, "Press Q, Esc or Ctrl-C to quit")
  tb.write(x1+1, y1+4, "Resize the terminal window and see what happens :)")

  let
    text = " Key pressed:                 "
    tx = max(x1 + ((x2 - x1 - text.len) / 2).int, 0)
    ty = max(y1 + ((y2 - y1) / 2).int, 0)

  tb.setBackgroundColor(bgRed)
  tb.setForegroundColor(fgYellow)
  tb.drawRect(tx-1, ty-1, tx + text.len, ty+1, doubleStyle=true)

  tb.write(tx, ty, text)

  tb.setForegroundColor(fgWhite, bright=true)
  tb.write(tx+14, ty, $gLastKeyPressed.load)


proc main() {.thread.} =
  illwillInit(fullscreen=true)
  setControlCHook(controlC)
  hideCursor()
  defer:
    illwillDeinit()
    showCursor()

  while not(stopFlag.load):
    var tb = newTerminalBuffer(terminalWidth(), terminalHeight())

    let key = getKey()
    case key
    of Key.None: discard
    else: gLastKeyPressed.store(key)

    tb.updateScreen()
    tb.display()

    sleep(50)

using
  env: ptr ErlNifEnv
  argc: cint
  argv: ptr UncheckedArray[ErlNifTerm]

proc lastKey(env, argc, argv): ErlNifTerm {.nif, arity: 0, dirtyIo.} =
  env.toTerm(ErlAtom($gLastKeyPressed.load))

proc start(env, argc, argv): ErlNifTerm {.nif, arity: 0, dirtyIo.} =
  createThread uiThread, main
  env.toTerm(ErlAtom("ok"))

proc setKey(env, argc, argv): ErlNifTerm {.nif, arity: 1, dirtyIo.} =
  var key = Key.None
  let arg = fromTerm(env, argv[0], ErlAtom)
  if not(arg.isSome):
    env.toTerm(ErlAtom("what"))
  else:
    try:
      key = parseEnum[Key](arg.unsafeGet().string)
    except ValueError:
      discard
    if key != Key.None:
      gLastKeyPressed.store(key)
      env.toTerm(ErlAtom("ok"))
    else:
      env.toTerm(ErlAtom("error"))

proc stop(env, argc, argv): ErlNifTerm {.nif, arity: 0, dirtyIo.} =
  stopFlag.store(true)
  joinThread uiThread
  env.toTerm(ErlAtom("ok"))

exportNifs "keycodes", [start, lastKey, setKey, stop]
