import {p5} from "./p5.js"
import {svg as svg_2} from "./svg.js"
import {get_code} from "./keys.js"

if (window) window.ludus = {run, kill, flush_stdout, stdout, p5, flush_commands, commands, result, flush_result, input, is_running, key_down, key_up, is_starting_up, p5, svg}

const worker_url = new URL("worker.js", import.meta.url)
const worker = new Worker(worker_url, {type: "module"})

let outbox = []
let ludus_console = ""
let ludus_commands = []
let ludus_result = null
let code = null
let running = false
let ready = false
let io_interval_id = null
let keys_down = new Set();

worker.onmessage = handle_messages

async function handle_messages (e) {
  let msgs
  try {
    msgs = JSON.parse(e.data)    
  } catch {
    console.log(e.data)
    throw Error("Main: bad json from Ludus")
  }
  for (const msg of msgs) {
    switch (msg.verb) {
      case "Complete": {
        console.log("Main: ludus completed with => ", msg.data)
        ludus_result = msg.data
        running = false
        ready = false
        outbox = []
        break
      }
      case "Error": {
        console.log("Main: ludus errored with => ", msg.data)
        ludus_result = msg.data
        running = false
        ready = false
        outbox = []
        break
      }
      // TODO: do more than report these
      case "Console": {
        let new_lines = msg.data.join("\n");
        ludus_console = ludus_console + new_lines
        console.log("Main: ludus says => ", new_lines)
        break
      }
      case "Commands": {
        console.log("Main: ludus commands => ", msg.data)
        for (const command of msg.data) {
          // commands will arrive asynchronously; ensure correct ordering
          ludus_commands[command[1]] = command
        }
        break
      }
      case "Fetch": {
        console.log("Main: ludus requests => ", msg.data)
        const res = await fetch(msg.data, {mode: "cors"})
        const text = await res.text()
        console.log("Main: js responds => ", text)
        outbox.push({verb: "Fetch", data: [msg.data, res.status, text]})
      }
      case "Ready": {
        console.log("Main: ludus is ready")
        ready = true
      }
    }
  }
}

function io_poller () {
  if (io_interval_id && !running) {
    // flush the outbox one last time
    // (presumably, with the kill message)
    worker.postMessage(outbox)
    // cancel the poller
    clearInterval(io_interval_id)
    outbox = []
  }
  if (ready && running) {
    bundle_keys()
    worker.postMessage(outbox)
    outbox = []
  }
}

function bundle_keys () {
  outbox.push({verb: "Keys", data: Array.from(keys_down)})
}

function start_io_polling () {
  io_interval_id = setInterval(io_poller, 100)
}

// runs a ludus script; does not return the result
// the result must be explicitly polled with `result`
export function run (source) {
  if (running || ready) {
    console.log("Main: received bouncy `run` call");
    return "TODO: handle this? should not be running"
  }
  // start the vm
  // wrapping the Run message in an array for the worker
  worker.postMessage([{verb: "Run", data: source}])
  // update state for this run
  code = source
  running = true
  // reset the rest of my state
  outbox = []
  ludus_console = ""
  ludus_commands = []
  ludus_result = null
  ready = false
  keys_down = new Set();
  // start the polling loop
  start_io_polling()
}

export function is_starting_up() {
  return running && !ready
}

// tells if the ludus script is still running
export function is_running() {
  return running && ready
}

// kills a ludus script
export function kill () {
  running = false
  outbox.push({verb: "Kill"})
  console.log("Main: Killed Ludus")
}

// sends text into ludus (status: not working)
export function input (text) {
  console.log("Main: calling `input` with ", text)
  outbox.push({verb: "Input", data: text})
}

// returns the contents of the ludus console and resets the console
export function flush_stdout () {
  let out = ludus_console
  ludus_console = ""
  return out
}

// returns the contents of the ludus console, retaining them
export function stdout () {
  return ludus_console
}

// returns the array of turtle commands
export function commands () {
  return ludus_commands
}

// returns the array of turtle commands and clears it
export function flush_commands () {
  let out = ludus_commands
  ludus_commands = []
  return out
}

// returns the ludus result
// this is effectively Option<String>:
// null if no result has been returned, or
// a string representation of the result
export function result () {
  return ludus_result
}

export function flush_result () {
  let out = ludus_result
  ludus_result = null
  return out
}

export function key_down (key) {
  if (is_running()) keys_down.add(get_code(key))
}

export function key_up (key) {
  if (is_running()) keys_down.delete(get_code(key))
}

export {p5} from "./p5.js"

export function svg (commands) {
  console.log("generating svg for ${code}")
  return svg_2(commands, code)
}

