#include <QThread>
#include <QFile>
#include <QApplication>

#include "rkt_protocol.h"
#include "shm.h"
#include "shmqueue.h"
#include "command.h"
#include "utils.h"

#include <QJsonDocument>
#include <QJsonObject>

#include "rktwebview_qt.h"


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

static void free_data(rkt_data_t *d)
{
    do_free_data(d);
}

class Handler : public QThread
{
public:
    Shm *shm;
    ShmQueue *command_queue;
    ShmQueue *result_queue;
    ShmQueue *event_queue;
    Rktwebview_qt *webview_handler;
    bool quit;

    // QThread interface
protected:
    void run();
};

class Alive : public QThread
{
public:
    Handler *handler;
    ShmQueue *alive_queue;
    ShmQueue *alive_ack_queue;
    bool alive_timeout;

protected:
    void run();
};

static Handler *_handler;
static Alive   *_alive;

static void event_cb(rkt_data_t *data)
{
    if (data->kind != rkt_data_kind_t::event) {
        return;
    }

    int wv = data->data.event.wv;
    char *evt = data->data.event.event;

    std::string evt_d(evt);
    _handler->event_queue->enqueue(wv, evt_d);

    free_data(data);
}


int main(int argc, char *argv[])
{
    const char *me = argv[0];

    {
        time_t my_time = time(NULL);
        INFO1("Starting at %s", ctime(&my_time));
    }

    if (argc < 8) {
        ERROR1("%s: wrong number of arguments\n", me);
        exit(1);
    }

    const char *shm_name = argv[1];
    const char *shm_size_str = argv[2];
    const char *cmd_slot_str = argv[3];
    const char *res_slot_str = argv[4];
    const char *evt_slot_str = argv[5];
    const char *alive_slot_str = argv[6];
    const char *alive_ack_slot_str = argv[7];

    size_t shm_size = atoi(shm_size_str);
    int cmd_slot = atoi(cmd_slot_str);
    int res_slot = atoi(res_slot_str);
    int evt_slot = atoi(evt_slot_str);
    int alive_slot = atoi(alive_slot_str);
    int alive_ack_slot = atoi(alive_ack_slot_str);

    MKLOGSTMT(LOG_INFO, fprintf(stderr, "%s %s %s %s %s %s %s %s\n", me, shm_name, shm_size_str,
                                cmd_slot_str, res_slot_str, evt_slot_str, alive_slot_str, alive_ack_slot_str));
    MKLOGSTMT(LOG_INFO, fprintf(stderr, "%s %s %d %d %d %d %d %d\n", me, shm_name, static_cast<int>(shm_size),
                                cmd_slot, res_slot, evt_slot, alive_slot, alive_ack_slot));

    if (!(shm_size > 0 && cmd_slot > 0 && res_slot > 0 && evt_slot > 0 && alive_slot > 0 && alive_ack_slot > 0)) {
        ERROR1("%s: Invalid shm size or slots\n", me);
        exit(2);
    }

    Handler *handler = new Handler();
    _handler = handler;
    handler->shm = new Shm(shm_name, shm_size, false);
    handler->command_queue = new ShmQueue(handler->shm, cmd_slot, false);
    handler->result_queue = new ShmQueue(handler->shm, res_slot, false);
    handler->event_queue = new ShmQueue(handler->shm, evt_slot, false);
    handler->webview_handler = new Rktwebview_qt(argc, argv);
    handler->start();

    Alive *alive = new Alive();
    _alive = alive;
    alive->handler = handler;
    alive->alive_timeout = false;
    alive->alive_queue = new ShmQueue(handler->shm, alive_slot, false);
    alive->alive_ack_queue = new ShmQueue(handler->shm, alive_ack_slot, false);
    alive->start();

    handler->webview_handler->initApp();
    handler->webview_handler->execApp();

    INFO0("waiting for thread to end\n");
    handler->wait();
    INFO0("Handler thread stopped\n");
    alive->wait();
    INFO0("Alive thread stopped\n");

    if (alive->alive_timeout) {
        // take ownership over all queues and SHM, because we didn't get an alive message
        // and probably the racket side has crashed because of that.
        ERROR0("Taking ownership of shared memory, shared queues and shared semaphores\n");
        handler->command_queue->takeOwnership();
        handler->result_queue->takeOwnership();
        handler->event_queue->takeOwnership();
        handler->shm->takeOwnership();
        alive->alive_queue->takeOwnership();
        alive->alive_ack_queue->takeOwnership();
    }

    INFO0("cleaning up shm\n");

    delete alive->alive_queue;
    delete alive;

    delete handler->webview_handler;
    delete handler->command_queue;
    delete handler->result_queue;
    delete handler->event_queue;
    delete handler->shm;
    delete handler;

    {
        time_t my_time = time(NULL);
        INFO1("Exiting at %s", ctime(&my_time));
    }

    return 0;
}

void Handler::run()
{
    int wait_ms = WAIT_TIME; // 10 seconds.
    while (!quit) {
        int cmd;
        std::string data;
        bool something_came = command_queue->dequeue(cmd, data, wait_ms);
        if (!something_came) {
            DEBUG1("No command received last %d seconds\n", wait_ms / 1000);
            cmd = CMD_NOOP;
        }
        QJsonObject data_obj = QJsonDocument::fromJson(data.c_str()).object();

        switch(cmd) {
            case CMD_NOOP: {
                if (quit) {
                    DEBUG1("Alive timeout, quit = %d\n", quit);
                    webview_handler->closeAllWindows();
                    DEBUG0("Closed all windows\n");
                    webview_handler->rktQuit();
                    DEBUG0("Quit application\n");
                }
            }
            break;
            case CMD_QUIT: {
                INFO0("Got quit message\n");
                webview_handler->rktQuit();
                INFO0("Enqueing RESULT_QUIT to result queue\n");
                result_queue->enqueue(RESULT_QUIT);
                quit = true;
            }
            break;
            case CMD_INFO: {
                int open_windows = webview_handler->openWindows();
                result_queue->enqueue(open_windows);
            }
            break;
            case CMD_HANDLE_IS_VALID: {
                int wv = data_obj["wv"].toInt();
                bool oke = webview_handler->rktValid(wv);
                result_queue->enqueue(oke);
            }
            break;
            case CMD_SET_LOGLEVEL: {
                int ll = data_obj["wv"].toInt();
                setLogLevel(ll);
                bool oke = true;
                result_queue->enqueue(oke);
            }
            break;
            case CMD_CONTEXT_NEW: {
                QString boilerplate_js = data_obj["boilerplate_js"].toString();
                bool has_pem_cert = data_obj["has_pem_cert"].toBool();
                QString pem_cert = data_obj["pem_cert"].toString();
                int context = webview_handler->newContext(boilerplate_js, has_pem_cert, pem_cert);
                result_queue->enqueue(context);
            }
            break;
            case CMD_CREATE_WV: {
                int context = data_obj["context"].toInt();
                int parent = data_obj["parent"].toInt();

                int wv = webview_handler->rktWebViewCreate(context, parent, event_cb);
                result_queue->enqueue(wv);
            }
            break;
            case CMD_CLOSE_WV: {
                int wv = data_obj["wv"].toInt();
                webview_handler->rktWebViewClose(wv);
            }
            break;
            case CMD_SET_URL: {
                int wv = data_obj["wv"].toInt();
                QString url = data_obj["url"].toString();
                result_t r = webview_handler->rktSetUrl(wv, url.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_SET_HTML: {
                int wv = data_obj["wv"].toInt();
                QString html = data_obj["html"].toString();
                result_t r = webview_handler->rktSetHtml(wv, html.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_RUN_JS: {
                int wv = data_obj["wv"].toInt();
                QString js = data_obj["js"].toString();
                result_t r = webview_handler->rktRunJs(wv, js);
                result_queue->enqueue(r);
            }
            break;
            case CMD_CALL_JS: {
                int wv = data_obj["wv"].toInt();
                QString js = data_obj["js"].toString();
                rkt_data_t *res = webview_handler->rktCallJs(wv, js.toUtf8().constData());
                result_queue->enqueue(res->data.js_result.result, res->data.js_result.value);
                free_data(res);
            }
            break;
            case CMD_OPEN_DEVTOOLS: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktOpenDevtools(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_MOVE: {
                int wv = data_obj["wv"].toInt();
                int x = data_obj["x"].toInt();
                int y = data_obj["y"].toInt();
                result_t r = webview_handler->rktMove(wv, x, y);
                result_queue->enqueue(r);
            }
            break;
            case CMD_RESIZE: {
                int wv = data_obj["wv"].toInt();
                int w = data_obj["w"].toInt();
                int h = data_obj["h"].toInt();
                result_t r = webview_handler->rktResize(wv, w, h);
                result_queue->enqueue(r);
            }
            break;
            case CMD_HIDE: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktHideWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_SHOW: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktShowWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_PRESENT: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktPresentWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_MAXIMIZE: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktMaximizeWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_MINIMIZE: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktMinimizeWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_SHOW_NORMAL: {
                int wv = data_obj["wv"].toInt();
                result_t r = webview_handler->rktShowNormalWindow(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_WINDOW_STATE: {
                int wv = data_obj["wv"].toInt();
                window_state_t r = webview_handler->rktWindowState(wv);
                result_queue->enqueue(r);
            }
            break;
            case CMD_SET_TITLE: {
                int wv = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                result_t r = webview_handler->rktWindowSetTitle(wv, title.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_SET_ICON: {
                int wv = data_obj["wv"].toInt();
                QString icon_file = data_obj["icon"].toString();
                result_t r = webview_handler->rktWindowSetIcon(wv, icon_file.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_CREATE_TRAY: {
                QString icon_file = data_obj["icon"].toString();
                QString tooltip = data_obj["tooltip"].toString();

                int tray = webview_handler->rktTrayCreate(icon_file.toUtf8().constData(),
                                                          tooltip.toUtf8().constData(),
                                                          event_cb);
                result_queue->enqueue(tray);
            }
            break;
            case CMD_TRAY_SET_ICON: {
                int tray = data_obj["wv"].toInt();
                QString icon_file = data_obj["icon"].toString();

                result_t r = webview_handler->rktTraySetIcon(tray,
                                                             icon_file.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_TRAY_SET_TOOLTIP: {
                int tray = data_obj["wv"].toInt();
                QString tooltip = data_obj["tooltip"].toString();

                result_t r = webview_handler->rktTraySetTooltip(tray,
                                                                tooltip.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_TRAY_SHOW_MESSAGE: {
                int tray = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                QString message = data_obj["message"].toString();

                result_t r = webview_handler->rktTrayShowMessage(tray,
                                                                 title.toUtf8().constData(),
                                                                 message.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_TRAY_CLEAR_MENU: {
                int tray = data_obj["wv"].toInt();
                result_t r = webview_handler->rktTraySetMenu(tray, nullptr);
                result_queue->enqueue(r);
            }
            break;
            case CMD_TRAY_SET_MENU: {
                int tray = data_obj["wv"].toInt();
                QString menu_json = data_obj["menu_json"].toString();

                result_t r = webview_handler->rktTraySetMenu(tray,
                                                             menu_json.toUtf8().constData());
                result_queue->enqueue(r);
            }
            break;
            case CMD_CHOOSE_DIR: {
                int wv = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                QString base_dir = data_obj["base_dir"].toString();
                result_t r = webview_handler->rktChooseDir(wv,
                                                          title.toUtf8().constData(),
                                                          base_dir.toUtf8().constData()
                                                         );
                result_queue->enqueue(r);
            }
            break;
            case CMD_FILE_OPEN: {
                int wv = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                QString base_dir = data_obj["base_dir"].toString();
                QString permitted_exts = data_obj["permitted_exts"].toString();
                result_t r = webview_handler->rktFileOpen(wv,
                                                          title.toUtf8().constData(),
                                                          base_dir.toUtf8().constData(),
                                                          permitted_exts.toUtf8().constData()
                                                          );
                result_queue->enqueue(r);
            }
            break;
            case CMD_FILE_SAVE: {
                int wv = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                QString base_dir = data_obj["base_dir"].toString();
                QString permitted_exts = data_obj["permitted_exts"].toString();
                result_t r = webview_handler->rktFileSave(wv,
                                                          title.toUtf8().constData(),
                                                          base_dir.toUtf8().constData(),
                                                          permitted_exts.toUtf8().constData()
                                                          );
                result_queue->enqueue(r);
            }
            break;
            case CMD_SET_OU_TOKEN: {
                int wv = data_obj["wv"].toInt();
                QString token = data_obj["token"].toString();
                webview_handler->rktSetOUToken(wv, token.toUtf8().constData());
            }
            break;
            case CMD_MSG_BOX: {
                int wv = data_obj["wv"].toInt();
                QString title = data_obj["title"].toString();
                QString message = data_obj["message"].toString();
                QString submsg = data_obj["submessage"].toString();
                int type = data_obj["type"].toInt();
                result_t r = webview_handler->rktMessageBox(wv,
                                                            title.toUtf8().constData(),
                                                            message.toUtf8().constData(),
                                                            submsg.toUtf8().constData(),
                                                            static_cast<rkt_messagetype_t>(type)
                                                            );
                result_queue->enqueue(r);
            }
            break;
            default: {
                ERROR1("Unknown command: %d\n", cmd);
            }
        }
    }
    DEBUG0("Exiting handler thread\n");
}

void Alive::run()
{
    int wait_ms = WAIT_TIME;
    int ping_no;
    std::string data;
    bool go_on = true;
    while (go_on) {
        bool something_came = alive_queue->dequeue(ping_no, data, wait_ms);
        if (!something_came) {
            ERROR0("No alive message received, stopping alive loop and quitting rktwebview_prg\n");
            handler->quit = true;
            handler->command_queue->enqueue(CMD_NOOP);
            alive_timeout = true;
            go_on = false;
        } else {
            if (ping_no == CMD_ALIVE_QUIT) {
                DEBUG0("Got Quit ping for alive thread\n");
                go_on = false;
            } else {
                DEBUG1("Got alive ping: %d\n", ping_no);
                alive_ack_queue->enqueue(ping_no);
            }
        }
    }
    DEBUG0("Exiting alive thread\n");
}
