#include "include/main.h"

std::mutex mutex;
std::queue<std::function<void()>> &messageQueue()
{
  static std::queue<std::function<void()>> _messageQueue;
  return _messageQueue;
}
std::thread serverThread;
std::thread recordingThread;
std::atomic<bool> wantsExit;

std::unique_ptr<germanairlinesva::file::config::Config> configuration;
std::unique_ptr<germanairlinesva::file::simdata::SimDatabase> database;
std::unique_ptr<germanairlinesva::websocket::Websocket> connector;
int xplaneVersion;

/* Datarefs */
XPLMDataRef pauseIndicator;
XPLMDataRef parkingBrake;
XPLMDataRef acftOnGrnd;
XPLMDataRef totalFuelKgs;
XPLMDataRef trueHeading;
XPLMDataRef acftAlt;
XPLMDataRef acftGS;
XPLMDataRef acftIAS;
XPLMDataRef acftVS;
XPLMDataRef acftLatitude;
XPLMDataRef acftLongitude;
XPLMDataRef fuelFlow;
XPLMDataRef maxSpeed;
XPLMDataRef uptime;
XPLMDataRef magHeading;
XPLMDataRef timeZulu;
XPLMDataRef payloadKgs;
XPLMDataRef totalWeightKgs;

XPLMDataRef localX;
XPLMDataRef localY;
XPLMDataRef localZ;
XPLMDataRef elevation;
XPLMDataRef pitch;
XPLMDataRef roll;
XPLMDataRef quaternion;

struct germanairlinesva::websocket::data toSend;
germanairlinesva::file::recording::Recording p;

/*
 * Callbacks
 */

PLUGIN_API int XPluginStart(char *outName, char *outSig, char *outDesc)
{
  XPLMEnableFeature("XPLM_USE_NATIVE_PATHS", 1);
  XPLMGetVersions(&xplaneVersion, NULL, NULL);

  toLog("Plugin using X-Plane Version " + std::to_string(xplaneVersion));

  wantsExit.store(false);

  /* First we must fill in the passed-in buffers to describe our
   * plugin to the plugin-system. */

  strcpy(outName, "GAConnector");
  strcpy(outSig, "de.german-airlines.GAConnector");
  strcpy(outDesc, "GAConnector");

  /* Flight Loop */
  XPLMRegisterFlightLoopCallback(flightLoop, -1, nullptr);

  /* Setup DataRefs */
  pauseIndicator = XPLMFindDataRef(
      "sim/time/sim_speed"); // INT 0=paused, doubles as setting pause(1)
  parkingBrake =
      XPLMFindDataRef("sim/flightmodel/controls/parkbrake"); // FLOAT 1=max
  acftOnGrnd =
      XPLMFindDataRef("sim/flightmodel/failures/onground_any"); // INT 1=true
  totalFuelKgs =
      XPLMFindDataRef("sim/flightmodel/weight/m_fuel_total"); // FLOAT
  trueHeading = XPLMFindDataRef(
      "sim/flightmodel/position/true_psi"); // FLOAT degrees north
  acftAlt =
      XPLMFindDataRef("sim/flightmodel/position/elevation"); // DOUBLE meters
  acftGS = XPLMFindDataRef(
      "sim/flightmodel/position/groundspeed"); // FLOAT meters/second
  acftIAS = XPLMFindDataRef(
      "sim/flightmodel/position/indicated_airspeed"); // FLOAT kias
  acftVS = XPLMFindDataRef(
      "sim/flightmodel/position/vh_ind_fpm"); // FLOAT feet/minute
  acftLatitude =
      XPLMFindDataRef("sim/flightmodel/position/latitude"); // DOUBLE degrees
  acftLongitude =
      XPLMFindDataRef("sim/flightmodel/position/longitude"); // DOUBLE degrees
  fuelFlow =
      XPLMFindDataRef("sim/flightmodel/engine/ENGN_FF_");  // FLOAT[8] kg/second
  maxSpeed = XPLMFindDataRef("sim/aircraft/view/acf_Vne"); // FLOAT kias
  uptime = XPLMFindDataRef("sim/time/total_running_time_sec"); // FLOAT seconds
  magHeading = XPLMFindDataRef(
      "sim/flightmodel/position/mag_psi"); // FLOAT degrees north
  timeZulu =
      XPLMFindDataRef("sim/time/zulu_time_sec"); // FLOAT seconds since midnight
  payloadKgs = XPLMFindDataRef("sim/flightmodel/weight/m_fixed");     // FLOAT
  totalWeightKgs = XPLMFindDataRef("sim/flightmodel/weight/m_total"); // FLOAT

  localX = XPLMFindDataRef("sim/flightmodel/position/local_x");      // DOUBLE
  localY = XPLMFindDataRef("sim/flightmodel/position/local_y");      // DOUBLE
  localZ = XPLMFindDataRef("sim/flightmodel/position/local_z");      // DOUBLE
  elevation = XPLMFindDataRef("sim/flightmodel/position/elevation"); // DOUBLE
  pitch = XPLMFindDataRef("sim/flightmodel/position/theta");         // FLOAT
  roll = XPLMFindDataRef("sim/flightmodel/position/phi");            // FLOAT
  quaternion = XPLMFindDataRef("sim/flightmodel/position/q");        // FLOAT[4]

  configuration = std::make_unique<germanairlinesva::file::config::Config>();
  toLog("Config loaded");

  connector = std::make_unique<germanairlinesva::websocket::Websocket>(
      "wss://ws.hofmannnet.myhome-server.de:8000",
      configuration->getUser(),
      toLog);
  toLog("WebSocket started");

  char hash[2 * MD5LEN + 1] = "";
  if (germanairlinesva::util::generateMD5(XPLANE_CUSTOM_SCENERY, hash, toLog) ==
      0) {
    database = std::make_unique<germanairlinesva::file::simdata::SimDatabase>(
        xplaneVersion,
        hash,
        configuration,
        toLog);
  }

  toLog("Readback test of sim database using EDDF");
  auto ap = (*database)["EDDF"];
  for (const auto &it : ap.first) {
    toLog("  " + it.to_string());
  }
  for (const auto &it : ap.second) {
    toLog("  " + it.to_string());
  }
  toLog("Readback test of sim database using XXXX");
  auto ap2 = (*database)["XXXX"];
  ap2.first.size() == 0 ? toLog("  SUCCESS") : toLog("  ERROR");


  // Thread for sending data to websocket
  serverThread = std::thread(&serverWorker);
  recordingThread = std::thread(&recordingWorker);
  toLog("Workers started");

  toLog("Logbook Test");
  germanairlinesva::file::logbook::Logbook logbook;
  logbook.addEntry("08.09.2022",
                   "F",
                   "1000",
                   "L049",
                   "D-ALFA",
                   "John F. Kennedy International Aiport / EDDF",
                   "A1",
                   "14L",
                   "Gander International Airport / CYQX",
                   "10",
                   "03",
                   "10:00",
                   "10:20",
                   "13:20",
                   "13:30",
                   210.5,
                   20.1,
                   5012.4156,
                   8.87,
                   5041.3856,
                   7.1,
                   971.14,
                   2.41,
                   980.65,
                   -165.23,
                   1,
                   1.2012,
                   "2022-09-08_VGA1000",
                   5.5,
                   1);
  logbook.toFile();

  return 1;
}

PLUGIN_API void XPluginStop(void)
{
  /* Flight Loop */
  XPLMUnregisterFlightLoopCallback(flightLoop, nullptr);
  /* End threads */
  wantsExit = true;
  serverThread.join();
  recordingThread.join();

  delete connector.release();
  delete database.release();
  delete configuration.release();

  p.toFile("flight.rec");

  toLog("Plugin stopped");
}

PLUGIN_API void XPluginDisable(void) {}

PLUGIN_API int XPluginEnable(void) { return 1; }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
PLUGIN_API void
    XPluginReceiveMessage(XPLMPluginID inFromWho, long inMessage, void *inParam)
{
}
#pragma clang diagnostic pop

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
float flightLoop(float elapsedMe, float elapsedSim, int counter, void *refcon)
{
  const std::lock_guard<std::mutex> lock(mutex);

  std::memset(&toSend, 0, sizeof(germanairlinesva::websocket::data));

  toSend.pause = XPLMGetDatai(pauseIndicator);
  toSend.pBrake = XPLMGetDataf(parkingBrake);
  toSend.onGrnd = XPLMGetDatai(acftOnGrnd);
  toSend.totFuelKg = XPLMGetDataf(totalFuelKgs);
  toSend.truHdg = XPLMGetDataf(trueHeading);
  toSend.alt = XPLMGetDatad(acftAlt);
  toSend.gs = XPLMGetDataf(acftGS);
  toSend.ias = XPLMGetDataf(acftIAS);
  toSend.vs = XPLMGetDataf(acftVS);
  toSend.lat = XPLMGetDatad(acftLatitude);
  toSend.lon = XPLMGetDatad(acftLongitude);
  XPLMGetDatavf(fuelFlow, toSend.ff, 0, 8);
  toSend.maxSpd = XPLMGetDataf(maxSpeed);

  char *name = (char *)calloc(256, sizeof(char));
  XPLMGetNthAircraftModel(0, name, toSend.path);
  free(name);

  toSend.uptime = XPLMGetDataf(uptime);
  toSend.magHeading = XPLMGetDataf(magHeading);

  toSend.payloadKg = XPLMGetDataf(payloadKgs);
  toSend.totalWeightKg = XPLMGetDataf(totalWeightKgs);

  if (!messageQueue().empty()) {
    auto op = std::move(messageQueue().front());
    messageQueue().pop();
    op();
  }

  return -1;
}
#pragma clang diagnostic pop

void serverWorker()
{
  germanairlinesva::util::setThreadName("GAServerWorker");

  while (!wantsExit) {
    struct germanairlinesva::websocket::data copy;
    {
      const std::lock_guard<std::mutex> lock(mutex);

      std::memcpy(&copy, &toSend, sizeof(germanairlinesva::websocket::data));
    }

    connector->sendData(copy);

    std::this_thread::sleep_for(std::chrono::milliseconds(250));
  }

  toLog("Server thread stopped");
}

void recordingWorker()
{
  germanairlinesva::util::setThreadName("GARecordingWorker");

  germanairlinesva::file::recording::RecordingEntry lastPath;
  std::uint32_t segment = 0;

  auto ap = (*database)["EDDF"];
  auto rwys = ap.second;

  while (!wantsExit) {
    germanairlinesva::websocket::data copy;
    {
      const std::lock_guard<std::mutex> lock(mutex);

      std::memcpy(&copy, &toSend, sizeof(germanairlinesva::websocket::data));
    }

    germanairlinesva::file::recording::RecordingEntry currentPath(
        segment,
        static_cast<std::uint16_t>(copy.alt),
        static_cast<std::uint16_t>(copy.gs),
        {copy.lat, copy.lon});

    if (strcmp(copy.path, "") != 0 && copy.pause && lastPath != currentPath) {
      p.addEntry(currentPath);
      lastPath = currentPath;

      for (const auto &it : rwys) {
        if (it.containsPoint({copy.lat, copy.lon})) {
          toLog("On Runway: " + it.to_string());
        }
      }
    }

    segment++;

    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }

  toLog("Recording thread stopped");
}

void toLog(const std::string &message)
{
  std::time_t utc = std::time(nullptr);
  std::tm *time = std::gmtime(&utc);
  std::stringstream msg;
  msg << std::put_time(time, "%Y-%m-%d %H:%M:%S UTC ")
      << "German Airlines VA: " << message << std::endl;
  XPLMDebugString(msg.str().c_str());
}
