#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <string>
#include <string.h>
#include <assert.h>

#include <mutex>
#include <zmq.h>

#include "json11.hpp"

#include "common/util.h"
#include "common/version.h"

#include "swaglog.h"

class LogState {
public:
  LogState() = default;
  ~LogState();
  std::mutex lock;
  bool inited;
  json11::Json::object ctx_j;
  void *zctx;
  void *sock;
  int print_level;
};

LogState::~LogState() {
  zmq_close(sock);
  zmq_ctx_destroy(zctx);
}

static LogState s = {};

static void cloudlog_bind_locked(const char* k, const char* v) {
  s.ctx_j[k] = v;
}

static void cloudlog_init() {
  if (s.inited) return;
  s.ctx_j = json11::Json::object {};
  s.zctx = zmq_ctx_new();
  s.sock = zmq_socket(s.zctx, ZMQ_PUSH);

  int timeout = 100; // 100 ms timeout on shutdown for messages to be received by logmessaged
  zmq_setsockopt(s.sock, ZMQ_LINGER, &timeout, sizeof(timeout));

  zmq_connect(s.sock, "ipc:///tmp/logmessage");

  s.print_level = CLOUDLOG_WARNING;
  const char* print_level = getenv("LOGPRINT");
  if (print_level) {
    if (strcmp(print_level, "debug") == 0) {
      s.print_level = CLOUDLOG_DEBUG;
    } else if (strcmp(print_level, "info") == 0) {
      s.print_level = CLOUDLOG_INFO;
    } else if (strcmp(print_level, "warning") == 0) {
      s.print_level = CLOUDLOG_WARNING;
    }
  }

  // openpilot bindings
  char* dongle_id = getenv("DONGLE_ID");
  if (dongle_id) {
    cloudlog_bind_locked("dongle_id", dongle_id);
  }
  cloudlog_bind_locked("version", COMMA_VERSION);
  s.ctx_j["dirty"] = !getenv("CLEAN");

  // device type
  if (util::file_exists("/EON")) {
    cloudlog_bind_locked("device", "eon");
  } else if (util::file_exists("/TICI")) {
    cloudlog_bind_locked("device", "tici");
  } else {
    cloudlog_bind_locked("device", "pc");
  }

  s.inited = true;
}

void log(int levelnum, const char* filename, int lineno, const char* func, const char* msg, const std::string& log_s) {
  std::lock_guard lk(s.lock);
  cloudlog_init();
  if (levelnum >= s.print_level) {
    printf("%s: %s\n", filename, msg);
  }
  char levelnum_c = levelnum;
  zmq_send(s.sock, &levelnum_c, 1, ZMQ_NOBLOCK | ZMQ_SNDMORE);
  zmq_send(s.sock, log_s.c_str(), log_s.length(), ZMQ_NOBLOCK);
}

void cloudlog_e(int levelnum, const char* filename, int lineno, const char* func,
                const char* fmt, ...) {
  char* msg_buf = nullptr;
  va_list args;
  va_start(args, fmt);
  vasprintf(&msg_buf, fmt, args);
  va_end(args);

  if (!msg_buf) return;

  json11::Json log_j = json11::Json::object {
    {"msg", msg_buf},
    {"ctx", s.ctx_j},
    {"levelnum", levelnum},
    {"filename", filename},
    {"lineno", lineno},
    {"funcname", func},
    {"created", seconds_since_epoch()}
  };
  std::string log_s = log_j.dump();
  log(levelnum, filename, lineno, func, msg_buf, log_s);
  free(msg_buf);
}

void cloudlog_bind(const char* k, const char* v) {
  std::lock_guard lk(s.lock);
  cloudlog_init();
  cloudlog_bind_locked(k, v);
}
