/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80 */
{% if isADrone -%}
/*global arm, console, close, dup2, exit, open, scriptArgs, setTimeout, start,
  stop, stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM*/
{% else -%}
/*global console, close, dup2, exit, open, scriptArgs, setTimeout, stopPubsub,
  Worker, SIGINT, SIGTERM*/
{% endif -%}
import {
{% if isADrone -%}
  arm,
  start,
  stop,
{% endif -%}
  stopPubsub,
{% if isADrone -%}
  takeOffAndWait
{% endif -%}
} from "{{ qjs_wrapper }}";
import {
  Worker,
  SIGINT,
  SIGTERM,
  dup2,
  setTimeout,
  signal
} from "os";
import { err, exit, open, out } from "std";

{% if isADrone -%}
(function (arm, console, dup2, err, exit, open, out, scriptArgs, setTimeout,
           start, stop, stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM) {
{% else -%}
(function (console, dup2, err, exit, open, out, scriptArgs, setTimeout,
           stopPubsub, Worker, SIGINT, SIGTERM) {
{% endif -%}
  "use strict";

  var CONF_PATH = "{{ configuration }}",
    conf_file = open(CONF_PATH, "r"),
    configuration = JSON.parse(conf_file.readAsString()),
    AUTOPILOT_CONNECTION_TIMEOUT = 5,
    MAVSDK_LOG_FILE_PATH =
      "{{ log_dir }}/mavsdk_" + new Date().toISOString() + ".log",
    QUICKJS_LOG_FILE_PATH =
      "{{ log_dir }}/quickjs_" + new Date().toISOString() + ".log",
    QUICKJS_LOG_FILE =
      open(QUICKJS_LOG_FILE_PATH, "w"),
    pubsubWorker,
    worker,
    user_script = scriptArgs[1],
    LOOP_EXECUTION_PERIOD = configuration.loopPeriod,
    previous_timestamp,
    can_update = false;

  conf_file.close();

  // redirect stdout and stderr
  dup2(QUICKJS_LOG_FILE.fileno(), out.fileno());
  dup2(QUICKJS_LOG_FILE.fileno(), err.fileno());

  // Use a Worker to ensure the user script
  // does not block the main script
  // (preventing it to be stopped for example)

  // Create the update loop in the main script
  // to prevent it to finish (and so, exit the quickjs process)
  worker = new Worker("{{ worker_script }}");

  function quit(exit_code) {
    worker.onmessage = null;
    stopPubsub();
{% if isADrone -%}
    stop();
{% endif -%}
    QUICKJS_LOG_FILE.close();
    exit(exit_code);
  }

  function exitWorker(exit_code) {
    worker.postMessage({
      type: "exit",
      code: exit_code
    });
  }

  signal(SIGTERM, exitWorker.bind(null, 0));
  signal(SIGINT, exitWorker.bind(null, 0));

  function exitOnFail(ret, msg) {
    if (ret) {
      console.log(msg);
      exitWorker(1);
    }
  }

{% if isADrone -%}
  function connect() {
    var address = configuration.autopilotIp + ":" + configuration.autopilotPort;
    console.log("Will connect to", address);
    exitOnFail(
      start(
        configuration.autopilotIp,
        configuration.autopilotPort,
        MAVSDK_LOG_FILE_PATH,
        QUICKJS_LOG_FILE_PATH,
        AUTOPILOT_CONNECTION_TIMEOUT,
        configuration.debug
      ),
      "Failed to connect to " + address
    );
  }

  console.log("Connecting to aupilot\n");
  connect();
{% endif -%}

  pubsubWorker = new Worker("{{ pubsub_script }}");
  pubsubWorker.onmessage = function (e) {
    if (!e.data.publishing) {
      pubsubWorker.onmessage = null;
    }
  };

  worker.postMessage({type: "initPubsub"});

{% if isADrone -%}
  function takeOff() {
    exitOnFail(arm(), "Failed to arm");
    takeOffAndWait();
  }
{% endif -%}

  function load() {
{% if isADrone -%}
    if (configuration.isASimulation) {
      takeOff();
    }
{% endif -%}

    // First argument must provide the user script path
    if (user_script === undefined) {
      console.log('Please provide the user_script path.');
      exitWorker(1);
    }

    worker.postMessage({
      type: "load",
      path: user_script
    });
  }

  function loop() {
    var timestamp = Date.now(),
      timeout;
    if (can_update) {
      if (LOOP_EXECUTION_PERIOD <= (timestamp - previous_timestamp)) {
        // Expected timeout between every update
        can_update = false;
        worker.postMessage({
          type: "update",
          timestamp: timestamp
        });
        // Try to stick to the expected LOOP_EXECUTION_PERIOD
        timeout = LOOP_EXECUTION_PERIOD - (timestamp - previous_timestamp - LOOP_EXECUTION_PERIOD);
        previous_timestamp = timestamp;
      } else {
        timeout = LOOP_EXECUTION_PERIOD - (timestamp - previous_timestamp);
      }
    } else {
      // If timeout occurs, but update is not yet finished
      // wait a bit
      timeout = LOOP_EXECUTION_PERIOD / 4;
    }
    // Ensure loop is not done with timeout < 1ms
    setTimeout(loop, Math.max(1, timeout));
  }

  worker.onmessage = function (e) {
    var type = e.data.type;
    if (type === 'initialized') {
      pubsubWorker.postMessage({
        action: "run",
        id: configuration.id,
        interval: LOOP_EXECUTION_PERIOD,
        publish: configuration.isADrone
      });
      load();
    } else if (type === 'loaded') {
      previous_timestamp = -LOOP_EXECUTION_PERIOD;
      can_update = true;
      // Start the update loop
      loop();
    } else if (type === 'updated') {
      err.flush();
      out.flush();
      can_update = true;
    } else if (type === 'exited') {
      worker.onmessage = null;
      quit(e.data.exit);
    } else {
      console.log('Unsupported message type', type);
      exitWorker(1);
    }
  };
{% if isADrone -%}
}(arm, console, dup2, err, exit, open, out, scriptArgs, setTimeout, start, stop,
  stopPubsub, takeOffAndWait, Worker, SIGINT, SIGTERM));
{% else -%}
}(console, dup2, err, exit, open, out, scriptArgs, setTimeout, stopPubsub,
  Worker, SIGINT, SIGTERM));
{% endif -%}
