#include <malloc.h>
#include <chrono>
#include <stdint.h>
#include <string.h>
#include <thread>

#ifdef _WIN32
#include <windows.h>
#include <filesystem>
#else
#include <stdlib.h>
#include <unistd.h>
#include <spawn.h>
#include <filesystem>
#include <sys/types.h>
#include <pwd.h>
#endif

#include <fcntl.h>
#include "rktwebview.h"
#include "rkt_protocol.h"
#include "shmqueue.h"
#include "memqueue.h"
#include "json.h"
#include "utils.h"

#define SHM_SIZE (10 * 1024 * 1024)     // 10MB
#define COMMAND_SLOT        1
#define COMMAND_RESULT_SLOT 2
#define EVENT_SLOT          3
#define ALIVE_SLOT          4
#define ALIVE_ACK_SLOT      5

#define RKT_WEBVIEW_INIT    rkt_webview_init(__FUNCTION__)

#ifdef _WIN32
#define strdup _strdup
#define sleep_ms(ms)  Sleep(static_cast<DWORD>(ms))
#endif

#ifdef __linux
static int sleep_ms(long msec)
{
    struct timespec ts;
    int res;

    if (msec < 0) {
        return -1;
    }

    ts.tv_sec = msec / 1000;
    ts.tv_nsec = (msec % 1000) * 1000000;

    res = nanosleep(&ts, &ts);
    return res;
}
#endif

#define YIELD() sleep_ms(5)


/////////////////////////////////////////////////////////////////////
// Utility functions
/////////////////////////////////////////////////////////////////////

typedef struct {
    std::string name;
    size_t      shm_size;
    Shm *shm;
    ShmQueue *command_queue;
    ShmQueue *command_result_queue;
    ShmQueue *event_queue;
    ShmQueue *alive_queue;
    ShmQueue *alive_ack_queue;
#ifdef _WIN32
    HANDLE rkt_webview_prg_pid;
#else
    pid_t rkt_webview_prg_pid;
#endif

    bool rkt_webview_prg_started;
    bool valid;
    int  function_calls;
    int  events;
    std::string log_file;

    void (*evt_cb)(int entries);
    std::mutex evt_cb_mutex;
    MemQueue *evt_queue;
    std::thread *guard_thread;
    bool guard_go_on;

    bool alive_go_on;
    std::thread *alive_thread;
    bool alive_error;

    bool verbose;
} Handle_t;

Handle_t *handler = nullptr;

static bool fileExists(const char *filename) {
#ifdef _WIN32
    DWORD dwAttrib = GetFileAttributes(filename);
    return (dwAttrib != INVALID_FILE_ATTRIBUTES &&
            !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
#else
    return access(filename, X_OK) != -1;
#endif
}

uint64_t current_ms() {
    using namespace std::chrono;
    return duration_cast<milliseconds>(system_clock::now().time_since_epoch()).count();
}

bool runRktWebview(Handle_t *handler)
{
    char *rkt_webview_prg_path = getenv("RKT_WEBVIEW_PRG");
    if (rkt_webview_prg_path == nullptr) {
        ERROR0("RKT_WEBVIEW_PRG environment variable not set, cannot start webview program\n");
        return false;
    }
    if (!fileExists(rkt_webview_prg_path)) {
        ERROR1("%s does not exist or is not executable\n", rkt_webview_prg_path);
        return false;
    }

    std::string path = basedir(rkt_webview_prg_path);

    char shm_size_str[30];
    char command_slot[10];
    char command_result_slot[10];
    char event_slot[10];
    char alive_slot[10];
    char alive_ack_slot[10];

    snprintf(shm_size_str, 30, "%d", static_cast<int>(handler->shm_size));
    snprintf(command_slot, 10, "%d", COMMAND_SLOT);
    snprintf(command_result_slot, 10, "%d", COMMAND_RESULT_SLOT);
    snprintf(event_slot, 10, "%d", EVENT_SLOT);
    snprintf(alive_slot, 10, "%d", ALIVE_SLOT);
    snprintf(alive_ack_slot, 10, "%d", ALIVE_ACK_SLOT);


    // run rktwebview_prg using the environment variable RKT_WEBVIEW_PRG
#ifdef _WIN32

    const char *td = getenv("TEMP");
    if (td == nullptr) { td = "C:\\"; }
    std::string tmpdir = td;
    std::string logfile = tmpdir + "\\" + "rktwebview.log"; //handler->name + ".log";
    handler->log_file = logfile;

    SECURITY_ATTRIBUTES sa;
    sa.nLength = sizeof(sa);
    sa.lpSecurityDescriptor = NULL;
    sa.bInheritHandle = TRUE;

    HANDLE h = CreateFile(logfile.c_str(),
                          FILE_WRITE_DATA, //FILE_APPEND_DATA,
                          FILE_SHARE_WRITE | FILE_SHARE_READ,
                          &sa,
                          CREATE_ALWAYS,
                          FILE_ATTRIBUTE_NORMAL,
                          NULL);

    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    ZeroMemory( &si, sizeof(si) );
    ZeroMemory( &pi, sizeof(pi) );

    si.cb = sizeof(si);
    si.dwFlags |= STARTF_USESTDHANDLES;
    si.hStdInput= NULL;
    si.hStdOutput = h;
    si.hStdError = h;

    DWORD flags = CREATE_NO_WINDOW | NORMAL_PRIORITY_CLASS;

    std::string cmdargs = std::string("") + handler->name + " " + shm_size_str + " " +
                          command_slot + " " + command_result_slot + " " + event_slot +
                          " " + alive_slot + " " + alive_ack_slot;
    std::string exe = std::string(rkt_webview_prg_path);
    std::string dir = basedir(exe);

    std::string cmdline = exe + " " + cmdargs;

    char *cmdline_str = const_cast<char *>(cmdline.c_str());

    bool r = CreateProcessA(NULL, cmdline_str, NULL, NULL, TRUE, flags, NULL, path.c_str(), &si, &pi);
    if (!r) {
        ERROR2("Cannot create process '%s' (error = %ld)\n", cmdline_str, GetLastError());
    }
    return r;
#else
    char *argv[] = { rkt_webview_prg_path, const_cast<char *>(handler->name.c_str()), shm_size_str,
                    command_slot, command_result_slot, event_slot,
                    alive_slot, alive_ack_slot,
                    nullptr };

    struct passwd *pw = getpwuid(getuid());
    const char *homedir = pw->pw_dir;

    std::string log_file = std::string(homedir) + "/.racket-webview.log";
    handler->log_file = log_file;

    posix_spawn_file_actions_t action;
    posix_spawn_file_actions_init(&action);
    unlink(log_file.c_str());
    posix_spawn_file_actions_addopen(&action, STDOUT_FILENO, log_file.c_str(), O_CREAT|O_WRONLY, 0600);
    posix_spawn_file_actions_addopen(&action, STDERR_FILENO, log_file.c_str(), O_WRONLY, 0600);

    int r = posix_spawn(&handler->rkt_webview_prg_pid, rkt_webview_prg_path, &action, nullptr, argv, environ);

    posix_spawn_file_actions_destroy(&action);

    return (r == 0);
#endif
}

/////////////////////////////////////////////////////////////////////
// Main C Interface
/////////////////////////////////////////////////////////////////////

#define EVT_GUARD_STOP -93273
#define EVT_ALIVE_ERROR -94328
#define EVT_NULL -95372

#define WAIT_ON_EVENT_MS (2 * 1000)
#define ALIVE_MESSAGE_INTERVAL_S 5                  // Should be smaller than 10 seconds
#define MAX_WAIT_RESULT     (2 * 1000)               // Maximum wait in milliseconds for a result per time

#ifdef DEBUG_RKT_WEBVIEW
#define MAX_ALIVE_ACK_TIME  (300 * 1000)
#else
#define MAX_ALIVE_ACK_TIME  (10 * 1000)
#endif

void rkt_evt_guard(void)
{
    INFO0("Starting rkt_evt_guard\n");
    while(handler->guard_go_on) {
        int wv;
        std::string data;
        if (handler->event_queue->dequeue(wv, data, WAIT_ON_EVENT_MS)) {
            if (handler->verbose) {
                INFO2("Got event wv = %d, %s\n", wv, data.c_str());
            }
            if (wv != EVT_GUARD_STOP && wv != EVT_NULL) {
                handler->evt_queue->enqueue(wv, data);
                handler->evt_cb_mutex.lock();
                if (handler->evt_cb != nullptr) {       // evt_cb could be cleared to null
                    //INFO2("Calling evt_cb with %d, %s\n", wv, data.c_str());
                    handler->evt_cb(1);
                    //INFO0("done\n");
                } else {
                    INFO0("evt_cb = null\n");
                }
                handler->evt_cb_mutex.unlock();
            } else if (wv == EVT_NULL) {
                INFO0("Got NULL message (evt_guard)\n");
            } else {
                INFO0("Got stop message (evt_guard)\n");
                handler->guard_go_on = false;
            }
        } else {
            if (handler->verbose) {
                INFO0("Got nothing, verbose = true\n");
            }
        }
    }
    INFO0("Leaving rkt_evt_guard\n");

}


void queue_alive_message()
{
    int seconds = 0;
    int ping = 0;
    DEBUG0("Starting queue_alive thread\n");
    while(handler->alive_go_on) {
        std::this_thread::sleep_for(std::chrono::seconds(1));    // one second
        seconds += 1;
        //DEBUG2("seconds = %d, %d\n", seconds, ALIVE_MESSAGE_INTERVAL_S);
        if (seconds >= ALIVE_MESSAGE_INTERVAL_S && handler->alive_go_on) {
            seconds = 0;
            ++ping;
            DEBUG1("Pinging with %d\n", ping);
            handler->alive_queue->enqueue(ping);
            if (ping > 1000000000) { ping = 0; }
            int ping_ack;
            std::string ping_ack_str;
            if (!handler->alive_ack_queue->dequeue(ping_ack, ping_ack_str, MAX_ALIVE_ACK_TIME)) {
                ERROR0("No acknowledgement on alive ping from backend\n");
                handler->event_queue->enqueue(EVT_ALIVE_ERROR);
                handler->alive_go_on = false;
                handler->alive_error = true;
            }
        }
    }
    handler->alive_queue->enqueue(CMD_ALIVE_QUIT);
}

void rkt_webview_cleanup()
{
    if (handler != nullptr) {
        // Does nothing.
        // See QTBUG-145033
        // QtWebEngine cannot be started as part of QApplication more than once in an application run.
        // So we would need to cleanup at exit of racket/drracket.
        // Cleaning up when exiting the current custodian is not enough.
        INFO0("rkt_webview_cleanup - cleaning up\n");

        handler->verbose = true;
        handler->event_queue->enqueue(EVT_NULL);

        // I finally found out that this second is not important.
        // The call to racket must be in a mutex in order to make sure
        // this side of the library isn't cleaned before the asynchronous
        // racket call returns. We need to serialize it. This has now
        // been done using the evt_cb_mutex.
        //sleep_ms(1000);     // Why is this second so important?
        // I just don't get why the evt_guard thread is blocking and getting core dumped
        // on the thread join, if this second is not given
        // (to racket?).

        if (handler->valid) {
            if (handler->guard_thread != nullptr) {
                handler->evt_cb_mutex.lock();
                if (handler->evt_cb != nullptr) {
                    INFO0("Clearing evt_guard callback to null\n");
                    handler->evt_cb = nullptr;
                } else {
                    INFO0("evt_guard callback is already null\n");
                }
                INFO0("Stopping evt_guard (enqueuing evt_guard_stop)\n");
                handler->event_queue->enqueue(EVT_GUARD_STOP);
                handler->evt_cb_mutex.unlock();
                INFO0("yielding current thread and hopefully activating evt_guard thread\n");
                YIELD();
                INFO0("joining guard thread\n");
                handler->guard_thread->join();
                INFO0("Deleting guard thread\n");
                delete handler->guard_thread;
                handler->guard_thread = nullptr;
            }

            handler->alive_go_on = false;
            handler->alive_thread->join();
            delete handler->alive_thread;
            handler->alive_thread = nullptr;

            INFO0("Sending quit message\n");
            handler->command_queue->enqueue(CMD_QUIT);
            INFO0("Message sent\n");
            bool stopped = false;
            while(!stopped) {
                int cmd = -1;
                std::string s;
                INFO0("Getting result of quit message\n");
                if (!handler->command_result_queue->dequeue(cmd, s, MAX_WAIT_RESULT)) {
                    ERROR0("Other side has probably stopped working, no result on quit message\n");
                    cmd = RESULT_QUIT;
                }
                INFO1("got %d\n", cmd);
                if (cmd == RESULT_QUIT) {
                    stopped = true;
                }
            }
        }

        // Cleanup shared memory
        delete handler->event_queue;
        delete handler->command_result_queue;
        delete handler->command_queue;
        delete handler->alive_queue;
        delete handler->alive_ack_queue;
        delete handler->shm;

        // Cleanup Memory Queue
        delete handler->evt_queue;

        // Cleanup Handler
        delete handler;
        handler = nullptr;
    } else {
        WARN0("rkt_webview_cleanup called, but handler == null\n");
    }
}


void rkt_webview_init(const char *from)
{
    if (handler == nullptr) {
        // Create shared memory and communication queues

        INFO1("rkt_webview_init called from %s\n", from);

        char buf[1024];
#ifdef DEBUG_RKT_WEBVIEW
        sprintf(buf, "rktwebview-dbg");
#else
#ifdef _WIN32
        DWORD p = GetCurrentProcessId();
        snprintf(buf, 1024, "rktwebview-%ld", p);
#else
        pid_t p = getpid();
        sprintf(buf, "rktwebview-%x", p);
#endif
#endif

        handler = new Handle_t;
        handler->valid = true;
        handler->name = buf;
        handler->function_calls = 0;
        handler->events = 0;
        handler->verbose = false;

        handler->shm_size = SHM_SIZE;
        handler->shm = new Shm(handler->name.data(), handler->shm_size, true);
        if (!handler->shm->isValid()) { handler->valid = false; }
        handler->command_queue = new ShmQueue(handler->shm, COMMAND_SLOT, true);
        handler->command_result_queue = new ShmQueue(handler->shm, COMMAND_RESULT_SLOT, true);
        handler->event_queue = new ShmQueue(handler->shm, EVENT_SLOT, true);

        handler->alive_queue = new ShmQueue(handler->shm, ALIVE_SLOT, true);
        handler->alive_ack_queue = new ShmQueue(handler->shm, ALIVE_ACK_SLOT, true);

        // Start rktwebview_prg application with the right information
#ifndef DEBUG_RKT_WEBVIEW
        handler->rkt_webview_prg_started = runRktWebview(handler);
        if (!handler->rkt_webview_prg_started) { handler->valid = false; }
#endif
        handler->guard_thread = nullptr;
        handler->evt_cb = nullptr;
        handler->evt_queue = new MemQueue();
        handler->guard_go_on = false;

        handler->alive_go_on = true;
        handler->alive_error = false;
        handler->alive_thread = new std::thread(queue_alive_message);

    } else {
        handler->function_calls++;
    }
}

bool rkt_webview_valid(rktwebview_t wv)
{
    RKT_WEBVIEW_INIT;
    if (handler != nullptr && handler->valid) {
        handler->command_queue->enqueue(CMD_HANDLE_IS_VALID);
        int result;
        std::string json_result;
        handler->command_result_queue->dequeue(result, json_result, true);
        return result == 0;
    } else {
        return false;
    }
}

static inline bool validHandle()
{
    return (handler != nullptr) && handler->valid;
}

#define FAIL_HANDLE if (!validHandle()) { return result_t::invalid_handle; }
#define FAIL_CONTEXT if (!validHandle()) { return 0; }
#define FAIL_WV if (!validHandle()) { return 0; }
#define NOOP_HANDLE if (!validHandle()) { return; }
#define FAIL_CALL_JS if (!validHandle()) { return nullptr; }
#define FAIL_INFO FAIL_CALL_JS

int rkt_webview_register_evt_callback(void (*f)(int))
{
    RKT_WEBVIEW_INIT;

    if (handler->evt_cb_mutex.try_lock()) {
        INFO1("registering callback %p\n", f);

        handler->evt_cb = f;
        if (handler->guard_thread == nullptr) {
            handler->guard_go_on = true;
            handler->guard_thread = new std::thread(rkt_evt_guard);
            INFO0("guard thread started\n");
        }

        handler->evt_cb_mutex.unlock();
        return 1;
    } else {
        WARN1("Cannot aquire lock for registering callback %p\n", f);
        return 0;
    }
}

rkt_wv_context_t rkt_webview_new_context(const char *boilerplate_js, const char *optional_server_cert_pem)
{
    RKT_WEBVIEW_INIT;
    FAIL_CONTEXT

    JSON j;

    std::string bpj(boilerplate_js);
    bool has_pem_cert = optional_server_cert_pem != nullptr;
    std::string osc(has_pem_cert ? optional_server_cert_pem : "");

    j["boilerplate_js"] = bpj;
    j["has_pem_cert"] = has_pem_cert;
    j["pem_cert"] = osc;

    handler->command_queue->enqueue(CMD_CONTEXT_NEW, j.dump());

    int result;
    std::string json_result;
    while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            return result_t::failed;
        }
    }
    return result;
}

int rkt_webview_create(rkt_wv_context_t context, rktwebview_t parent)
{
    RKT_WEBVIEW_INIT;
    FAIL_WV

    JSON j;
    j["context"] = context;
    j["parent"] = parent;

    handler->command_queue->enqueue(CMD_CREATE_WV, j.dump());

    int result;
    std::string json_result;
    while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            return result_t::failed;
        }
    }

    return result;
}

void rkt_webview_close(rktwebview_t wv)
{
    RKT_WEBVIEW_INIT;
    NOOP_HANDLE

    JSON j;
    j["wv"] = wv;
    handler->command_queue->enqueue(CMD_CLOSE_WV, j.dump());
}

static inline result_t DO_CMDRES(int cmd, rktwebview_t wv, const JSON &j)
{
    RKT_WEBVIEW_INIT;
    FAIL_HANDLE
    std::string json_str = j.dump();
    handler->command_queue->enqueue(cmd, json_str);
    int result;
    std::string json_result;
    while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            return result_t::failed;
        }
    }
    result_t r = static_cast<result_t>(result);
    return r;
}

static inline result_t CMDRES4(int cmd, rktwebview_t wv,
                               const char *key, const char *val,
                               const char *key2, const char *val2,
                               const char *key3, const char *val3,
                               const char *key4, const char *val4)
{
    JSON j;
    j["wv"] = wv;
    j[key] = val;
    j[key2] = val2;
    j[key3] = val3;
    j[key4] = val4;
    return DO_CMDRES(cmd, wv, j);
}

static inline result_t CMDRES4(int cmd, rktwebview_t wv,
                               const char *key, int val,
                               const char *key2, int val2,
                               const char *key3, const char *val3,
                               const char *key4, const char *val4)
{
    JSON j;
    j["wv"] = wv;
    j[key] = val;
    j[key2] = val2;
    j[key3] = val3;
    j[key4] = val4;
    return DO_CMDRES(cmd, wv, j);
}

static inline result_t CMDRES4(int cmd, rktwebview_t wv,
                               const char *key, const char *val,
                               const char *key2, const char *val2,
                               const char *key3, const char *val3,
                               const char *key4, int val4)
{
    JSON j;
    j["wv"] = wv;
    j[key] = val;
    j[key2] = val2;
    j[key3] = val3;
    j[key4] = val4;
    return DO_CMDRES(cmd, wv, j);
}

#define CMDRES3(cmd, wv, key, val, key2, val2, key3, val3) \
return CMDRES4(cmd, wv, key, val, key2, val2, key3, val3, "nil3", "none");

#define CMDRES2(cmd, wv, key, val, key2, val2) \
    CMDRES3(cmd, wv, key, val, key2, val2, "nil2", "none")

#define CMDRES(cmd, wv, key, val) \
    CMDRES2(cmd, wv, key, val, "nil1", "none")

#define CMDRES0(cmd, wv) \
    CMDRES(cmd, wv, "nil0", "none")

result_t rkt_webview_set_url(rktwebview_t wv, const char *url)
{
    CMDRES(CMD_SET_URL, wv, "url", url);
}

result_t rkt_webview_set_html(rktwebview_t wv, const char *html)
{
    CMDRES(CMD_SET_HTML, wv, "html", html)
}


result_t rkt_webview_run_js(rktwebview_t wv, const char *js)
{
    CMDRES(CMD_RUN_JS, wv, "js", js)
}

rkt_data_t *rkt_webview_call_js(rktwebview_t wv, const char *js)
{
    RKT_WEBVIEW_INIT;
    FAIL_CALL_JS

    JSON j;
    j["wv"] = wv;
    j["js"] = std::string(js);
    handler->command_queue->enqueue(CMD_CALL_JS, j.dump());

    int result;
    std::string json_result;
    while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            rkt_data_t *r = new rkt_data_t();
            r->kind = rkt_data_kind_t::js_result;
            r->data.js_result.result = result_t::eval_js_failed;
            r->data.js_result.value = strdup("");
            return r;
        }
    }

    rkt_data_t *r = new rkt_data_t();
    r->kind = rkt_data_kind_t::js_result;
    r->data.js_result.result = static_cast<result_t>(result);
    r->data.js_result.value = strdup(json_result.c_str());

    return r;
}

result_t rkt_webview_open_devtools(rktwebview_t wv)
{
    CMDRES(CMD_OPEN_DEVTOOLS, wv, "nil", "none")
}

/*
void rkt_webview_process_events(int for_ms)
{
    rkt_webview_init();

    uint64_t start_ms = current_ms();
    uint64_t end_ms = start_ms + for_ms;

    while (current_ms() < end_ms) {
        QThread::usleep(500); // sleep 0.5 ms
        handler->doEvents();
    }
}
*/

result_t rkt_webview_move(rktwebview_t wv, int x, int y)
{
    CMDRES2(CMD_MOVE, wv, "x", x, "y", y)
}


result_t rkt_webview_resize(rktwebview_t wv, int width, int height)
{
    CMDRES2(CMD_RESIZE, wv, "w", width, "h", height)
}


result_t rkt_webview_hide(rktwebview_t w)
{
    CMDRES0(CMD_HIDE, w)
}

result_t rkt_webview_show(rktwebview_t w)
{
    CMDRES0(CMD_SHOW, w)
}

result_t rkt_webview_present(rktwebview_t w)
{
    CMDRES0(CMD_PRESENT, w)
}

result_t rkt_webview_maximize(rktwebview_t w)
{
    CMDRES0(CMD_MAXIMIZE, w)
}

result_t rkt_webview_minimize(rktwebview_t w)
{
    CMDRES0(CMD_MINIMIZE, w)
}

result_t rkt_webview_show_normal(rktwebview_t w)
{
    CMDRES0(CMD_SHOW_NORMAL, w)
}

void rkt_webview_set_loglevel(rkt_webview_loglevel_t l)
{
    setLogLevel(l);     // library side
    auto f = [l]() {
        CMDRES0(CMD_SET_LOGLEVEL, static_cast<int>(l));     // webview process side
    };
    f();
}


window_state_t rkt_webview_window_state(rktwebview_t w)
{
    auto f = [w]() {
        CMDRES0(CMD_WINDOW_STATE, w)
    };
    int r = f();
    return static_cast<window_state_t>(r);
}

result_t rkt_webview_set_title(rktwebview_t wv, const char *title)
{
    CMDRES(CMD_SET_TITLE, wv, "title", title)
}

result_t rkt_webview_set_icon(rktwebview_t wv, const char *icon_file)
{
    CMDRES(CMD_SET_ICON, wv, "icon", icon_file)
}

result_t rkt_webview_choose_dir(rktwebview_t w, const char *title, const char *base_dir)
{
    CMDRES2(CMD_CHOOSE_DIR, w, "title", title, "base_dir", base_dir)
}

result_t rkt_webview_file_open(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
    CMDRES3(CMD_FILE_OPEN, w, "title", title, "base_dir", base_dir, "permitted_exts", permitted_exts)
}

result_t rkt_webview_file_save(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
    CMDRES3(CMD_FILE_SAVE, w, "title", title, "base_dir", base_dir, "permitted_exts", permitted_exts)
}

void rkt_webview_set_ou_token(rktwebview_t wv, const char *token)
{
    RKT_WEBVIEW_INIT;
    NOOP_HANDLE

    JSON j;
    j["wv"] = wv;
    j["token"] = std::string(token);

    handler->command_queue->enqueue(CMD_SET_OU_TOKEN, j.dump());
}

void rkt_webview_free_data(rkt_data_t *d)
{
    do_free_data(d);
}

rkt_data_t *rkt_webview_version()
{
    rkt_data_t *d = static_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
    d->kind = version;
    d->data.version.api_major = RKT_WEBVIEW_API_MAJOR;
    d->data.version.api_minor = RKT_WEBVIEW_API_MINOR;
    d->data.version.api_patch = RKT_WEBVIEW_API_PATCH;
    return d;
}

result_t rkt_webview_message_box(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type)
{
    return CMDRES4(CMD_MSG_BOX, w, "title", title, "message", message, "submessage", submessage, "type", static_cast<int>(type));
}

int rkt_webview_events_waiting()
{
    if (handler->evt_cb != nullptr) {
        return handler->evt_queue->depth();
    } else {
        return handler->event_queue->depth();
    }
}

rkt_data_t *rkt_webview_get_event()
{
    //fprintf(stderr, "rkt_webview_get_event\n");
    if (handler->evt_cb != nullptr) {
        int wv;
        std::string data;
        if (handler->evt_queue->dequeue(wv, data)) {
            rkt_data_t *d = reinterpret_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
            d->kind = rkt_data_kind_t::event;
            handler->events += 1;
            size_t ds = data.size();
            d->data.event.event = reinterpret_cast<char *>(malloc(ds + 1));
            memcpy(d->data.event.event, data.c_str(), ds);
            d->data.event.event[ds] = '\0';
            d->data.event.wv = wv;
            return d;
        } else {
            return nullptr;
        }
    } else {
        int wv;
        std::string data;
        if (handler->event_queue->dequeue(wv, data, 0)) {
            //fprintf(stderr, "got event %d %s\n", wv, data.c_str());
            rkt_data_t *d = reinterpret_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
            d->kind = rkt_data_kind_t::event;

            handler->events += 1;
            size_t ds = data.size();
            d->data.event.event = reinterpret_cast<char *>(malloc(ds + 1));
            memcpy(d->data.event.event, data.c_str(), ds);
            d->data.event.event[ds] = '\0';
            d->data.event.wv = wv;
            //fprintf(stderr, "event: %d - '%s'\n", d->data.event.wv, d->data.event.event);fflush(stderr);
            return d;
        } else {
            return nullptr;
        }
    }
}

void rkt_webview_env(const char *env_cmds[])
{
    int i;
    for(i = 0; env_cmds[i] != nullptr && strcmp(env_cmds[i], "") != 0; i++) {
#ifdef WIN32
        _putenv(env_cmds[i]);
#else
        putenv(const_cast<char *>(env_cmds[i]));
#endif
    }
}


rkt_data_t *rkt_webview_info()
{
    RKT_WEBVIEW_INIT;
    FAIL_INFO

    rkt_data_t *d = new rkt_data_t();
    d->kind = metrics;

    handler->shm->info(d->data.metrics.shm_usage,
                       d->data.metrics.shm_free_depth,
                       d->data.metrics.shm_free_size,
                       d->data.metrics.shm_item_depth,
                       d->data.metrics.shm_item_size,
                       d->data.metrics.shm_item_usage_factor);

    JSON j;
    handler->command_queue->enqueue(CMD_INFO, j.dump());

    int open_windows_result;
    std::string none;
    while (!handler->command_result_queue->dequeue(open_windows_result, none, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            d->data.metrics.open_windows = -1;
            d->data.metrics.function_calls = -1;
            d->data.metrics.log_file = strdup("");
            d->data.metrics.events = -1;
            return d;
        }
    }

    d->data.metrics.open_windows = open_windows_result;
    d->data.metrics.function_calls = handler->function_calls;
    d->data.metrics.log_file = strdup(handler->log_file.c_str());
    d->data.metrics.events = handler->events + handler->event_queue->depth();

    return d;
}



void rkt_webview_exit_done(int done)
{
    if (!done) {
        ERROR0("rkt_webview_exit_done called with 'false', i.e. this library did not have a cleanup call\n");
    }
}

/////////////////////////////////////////////////////////////////////
// Tray specific functions
/////////////////////////////////////////////////////////////////////

rktwebview_t rkt_webview_tray_create(const char *icon_file,
                                     const char *tooltip)
{
    RKT_WEBVIEW_INIT;
    FAIL_WV

    JSON j;
    j["icon"] = icon_file;
    j["tooltip"] = tooltip;

    handler->command_queue->enqueue(CMD_CREATE_TRAY, j.dump());

    int result;
    std::string json_result;
    while (!handler->command_result_queue->dequeue(result, json_result, MAX_WAIT_RESULT)) {
        if (handler->alive_error) {
            return result_t::failed;
        }
    }

    return result;
}

result_t rkt_webview_tray_set_icon(rktwebview_t tray, const char *icon_file)
{
    CMDRES(CMD_TRAY_SET_ICON, tray, "icon", icon_file)
}

result_t rkt_webview_tray_set_tooltip(rktwebview_t tray, const char *tooltip)
{
    CMDRES(CMD_TRAY_SET_TOOLTIP, tray, "tooltip", tooltip)
}

result_t rkt_webview_tray_show_message(rktwebview_t tray, const char *title, const char *message)
{
    CMDRES2(CMD_TRAY_SHOW_MESSAGE, tray, "title", title, "message", message)
}

result_t rkt_webview_tray_set_menu(rktwebview_t tray, const char *menu_json)
{
    if (menu_json == nullptr) {
        CMDRES0(CMD_TRAY_CLEAR_MENU, tray)
    } else {
        CMDRES(CMD_TRAY_SET_MENU, tray, "menu_json", menu_json)
    }
}

