#include "rktwebview_qt.h"
#include "webviewqt.h"
#include "rktwebview_types.h"
#include "webviewwindow.h"
#include "rktutils.h"
#include "utils.h"
#include <QApplication>
#include <QTimer>
#include <QSemaphore>
#include <QJsonDocument>
#include <QJsonObject>
#include <QAbstractEventDispatcher>
#include <QWebEngineProfile>
#include <QWebEngineProfileBuilder>
#include <QWebEngineScriptCollection>
#include <QWebEngineScript>
#include "command.h"
#include <QFileDialog>
#include <QMessageBox>
#include <QAbstractButton>
#include <QSystemTrayIcon>
#include <QMenu>
#include <QAction>
#include "build_menu_from_json.h"

static inline char *copyString(const char *s)
{
    int l = static_cast<int>(strlen(s));
    char *cpy = static_cast<char *>(malloc(l + 1));
    memcpy(cpy, s, l + 1);
    return cpy;
}

static QString activationReasonToString(QSystemTrayIcon::ActivationReason r)
{
    switch (r) {
    case QSystemTrayIcon::Context:
        return "context-menu";     // rechtsklik / menu openen
    case QSystemTrayIcon::DoubleClick:
        return "double-click";
    case QSystemTrayIcon::Trigger:
        return "click";            // meestal linkerklik
    case QSystemTrayIcon::MiddleClick:
        return "middle-click";
    case QSystemTrayIcon::Unknown:
    default:
        return "unknown";
    }
}

void Rktwebview_qt::processCommand(Command *cmd)
{
    switch(cmd->cmd) {
    case COMMAND_QUIT: { // Quit application
        _app->quit();
        cmd->done = true;
    }
    break;
    case COMMAND_NEW_CONTEXT: {
        QString boilerplate_js = cmd->args[0].toString();
        bool has_pem = cmd->args[1].toBool();
        QString optional_server_cert_pem = cmd->args[2].toString();

        DEBUG1("bjs: %s\n", boilerplate_js.toUtf8().constData());
        DEBUG1("oscp: %s\n", optional_server_cert_pem.toUtf8().constData());

        QWebEngineProfileBuilder b;
        if (has_pem) {
            QByteArray scp = optional_server_cert_pem.toUtf8();
            DEBUG1("Installing cert: %s\n", scp.constData());
            QList<QSslCertificate> certs;
            QSslCertificate cert(scp);
            certs.append(cert);
            b.setAdditionalTrustedCertificates(certs);
        }
        _context_counter += 1;
        QString name = QString::asprintf("profile-%d", _context_counter);

        QString code = "if (window.rkt_event_queue === undefined) { window.rkt_event_queue = []; }\n"
                       "window.rkt_evt_frame_el = null;\n"
                       "window.rkt_evt_frame_win = null;\n"
                       "window.rkt_send_event = function(obj) {\n"
                       "    obj.timestamp = Date.now();\n"
                       "    //console.log('Sending event: ' + obj);\n"
                       "    window.rkt_event_queue.push(obj);\n"
                       "    if (window.rkt_evt_frame_el) {\n"
                       "       window.rkt_evt_frame_win.print();\n"
                       "    }\n"
                       "};\n"
                       "window.rkt_get_events = function() {\n"
                       "    let q = window.rkt_event_queue;\n"
                       "    window.rkt_event_queue = [];\n"
                       "    let json_q = JSON.stringify(q);\n"
                       "    return json_q;\n"
                       "};\n"
                       "// add hidden hover element to body if necessary\n"
                       "setInterval(function () {\n"
                       "   if (window.rkt_evt_frame_el === null || window.rkt_evt_frame_el === undefined) {\n"
                       "      window.rkt_evt_frame_el = document.createElement('iframe');\n"
                       "      window.rkt_evt_frame_el.style.display = 'none';\n"
                       "      window.rkt_evt_frame_el.setAttribute('id', 'rkt-evt-frame');\n"
                       "      window.rkt_evt_frame_el.setAttribute('name', 'rkt-evt-frame');\n"
                       "      document.body.append(window.rkt_evt_frame_el);\n"
                       "      window.rkt_evt_frame_win = window.rkt_evt_frame_el.contentWindow;\n"
                       "   } else {"
                       "      if (window.rkt_event_queue.length > 0) {\n"
                       "         window.rkt_evt_frame_win.print();\n"
                       "      }\n"
                       "   }\n"
                       "},\n"
                       "10);\n"
                       "";

        QList<QWebEngineScript> scripts;
        QWebEngineProfile *p = b.createProfile(name);
        QWebEngineScriptCollection *col = p->scripts();
        QWebEngineScript s1;
        s1.setInjectionPoint(QWebEngineScript::InjectionPoint::DocumentReady);
        s1.setName("eventing");
        s1.setSourceCode(code);
        s1.setWorldId(QWebEngineScript::MainWorld);
        scripts.append(s1);

        QWebEngineScript s2;
        s2.setInjectionPoint(QWebEngineScript::InjectionPoint::DocumentReady);
        s2.setName("boilerplate");
        s2.setSourceCode(boilerplate_js);
        s2.setWorldId(QWebEngineScript::MainWorld);
        scripts.append(s2);

        col->insert(scripts);

        QWebEngineScriptCollection *c = p->scripts();
        QList<QWebEngineScript> l =  c->toList();

        _contexts[_context_counter] = p;

        cmd->result = _context_counter;
        cmd->done = true;
    }
    break;
    case COMMAND_CREATE: {
        rkt_wv_context_t context = cmd->args[0].toInt();
        rktwebview_t parent = cmd->args[1].toInt();

        void *f = cmd->args[2].value<void *>();
        event_cb_t js_event_cb = reinterpret_cast <event_cb_t>(f);

        WebviewWindow *p;
        if (_views.contains(parent)) {
            p = _views[parent];
        } else {
            p = nullptr;
        }

        if (!_contexts.contains(context)) {
            cmd->result = -1;
            cmd->done = true;
        } else {
            QWebEngineProfile *profile = _contexts[context];
            WebviewWindow *w = new WebviewWindow(profile, p);
            WebViewQt *view = new WebViewQt(nextHandle(), w);
            w->addView(view, this);

            int id = view->id();

            _views[id] = w;
            _view_js_callbacks[id] = js_event_cb;

            w->show();
            while(!w->windowCreated()) {
                _app->processEvents();
            }

            cmd->result = id;
            cmd->done = true;
        }
    }
    break;
    case COMMAND_CLOSE: {
        int wv = cmd->args[0].toInt();
        if (_views.contains(wv)) {
            WebviewWindow *w= _views[wv];
            _views.remove(wv);
            w->closeView();
            cmd->result = true;
            while(w->isVisible()) {
                _app->processEvents();
            }
            _view_js_callbacks.remove(wv);
            delete w;
        } else if (_trays.contains(wv)) {
            QSystemTrayIcon *tray = _trays[wv];
            _trays.remove(wv);

            if (_tray_menus.contains(wv)) {
                QMenu *menu = _tray_menus[wv];
                _tray_menus.remove(wv);
                delete menu;
            }

            tray->hide();
             _view_js_callbacks.remove(wv);
            delete tray;
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SET_OU_TOKEN: {
        int wv = cmd->args[0].toInt();
        QString ou_token = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w= _views[wv];
            w->setOUToken(ou_token);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SET_URL: {
        int wv = cmd->args[0].toInt();
        QString url = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            WebViewQt *v = w->view();
            QUrl u(url);
            v->setUrl(u);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SET_HTML: {
        int wv = cmd->args[0].toInt();
        QString html = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            WebViewQt *v = w->view();
            v->setHtml(html);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SET_TITLE: {
        int wv = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            w->setWindowTitle(title);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SET_ICON: {
        int wv = cmd->args[0].toInt();
        QString icon_file = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            QIcon icn(icon_file);
            w->setWindowIcon(icn);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_RUN_JS: {
        int wv = cmd->args[0].toInt();
        QString js = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            DEBUG1("Running %s\n", js.toUtf8().constData());
            w->runJs(js);
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_CALL_JS: {
        int wv = cmd->args[0].toInt();
        QString js = cmd->args[1].toString();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            w->callJs(js, cmd);
        } else {
            cmd->result = false;
            cmd->js_result_ok = false;
            cmd->done = true;
        }
    }
    break;
    case COMMAND_DEV_TOOLS: {
        int wv = cmd->args[0].toInt();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            w->openDevTools();
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_SHOW_WIN:
    case COMMAND_HIDE_WIN:
    case COMMAND_PRESENT_WIN:
    case COMMAND_MAX_WIN:
    case COMMAND_MIN_WIN:
    case COMMAND_SHOW_NORMAL_WIN:
    {
        int wv = cmd->args[0].toInt();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            int c = cmd->cmd;
            if (c == COMMAND_SHOW_WIN) w->show();
            else if (c == COMMAND_HIDE_WIN) w->hide();
            else if (c == COMMAND_MAX_WIN) w->showMaximized();
            else if (c == COMMAND_MIN_WIN) {
                w->showMinimized();
            }
            else if (c == COMMAND_SHOW_NORMAL_WIN) w->showNormal();
            else if (c == COMMAND_PRESENT_WIN) {
                                w->show();
                                w->raise();
                                w->activateWindow();
            }
            cmd->result = true;
        } else if (_trays.contains(wv)) {
            QSystemTrayIcon *tray = _trays[wv];

            if (cmd->cmd == COMMAND_SHOW_WIN) {
                tray->show();
                cmd->result = true;
            } else if (cmd->cmd == COMMAND_HIDE_WIN) {
                tray->hide();
                cmd->result = true;
            } else {
                cmd->result = false;
            }
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_WINDOW_STATUS:
    {
        int wv = cmd->args[0].toInt();
        window_state_t ws = window_state_t::invalid;
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            if (w->isHidden()) {
                ws = window_state_t::hidden;
            } else if (w->isMinimized()) {
                ws = window_state_t::minimized;
            } else if (w->isMaximized()) {
                if (w->isActiveWindow()) {
                    ws = window_state_t::maximized_active;
                } else {
                    ws = window_state_t::maximized;
                }
            } else if (w->isVisible()) {
                if (w->isActiveWindow()) {
                    ws = window_state_t::normal_active;
                } else {
                    ws = window_state_t::normal;
                }
            }
        }
        cmd->result = static_cast<int>(ws);
        cmd->done = true;
    }
    break;
    case COMMAND_MOVE: {
        int wv = cmd->args[0].toInt();
        int x = cmd->args[1].toInt();
        int y = cmd->args[2].toInt();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            int move_count = w->moveCount();
            w->move(x, y);
            while (w->moveCount() == move_count) {
                _app->processEvents();
            }
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_RESIZE: {
        int wv = cmd->args[0].toInt();
        int width = cmd->args[1].toInt();
        int height = cmd->args[2].toInt();
        if (_views.contains(wv)) {
            WebviewWindow *w = _views[wv];
            int resize_count = w->resizeCount();
            w->resize(width, height);
            while (w->resizeCount() == resize_count) {
                _app->processEvents();
            }
            cmd->result = true;
        } else {
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_CHOOSE_DIR: {
        int wv = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        QString base_dir = cmd->args[2].toString();
        if (_views.contains(wv)) {
            result_t r = fileDlg(wv, title, base_dir, "",
                                 QFileDialog::Directory, QFileDialog::AcceptOpen,
                                 "choose-dir-ok", "choose-dir-cancel"
                                 );
            cmd->js_result_ok = true;
            cmd->result = (r == result_t::oke);
        } else {
            cmd->js_result_ok = false;
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_FILE_OPEN: {
        int wv = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        QString base_dir = cmd->args[2].toString();
        QString exts = cmd->args[3].toString();
        if (_views.contains(wv)) {
            result_t r = fileDlg(wv, title, base_dir, exts,
                                 QFileDialog::ExistingFile, QFileDialog::AcceptOpen,
                                 "file-open-ok", "file-open-cancel"
                                 );
            cmd->js_result_ok = true;
            cmd->result = (r == result_t::oke);
        } else {
            cmd->js_result_ok = true;
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_FILE_SAVE: {
        int wv = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        QString base_dir = cmd->args[2].toString();
        QString exts = cmd->args[3].toString();
        if (_views.contains(wv)) {
            result_t r = fileDlg(wv, title, base_dir, exts,
                                 QFileDialog::AnyFile, QFileDialog::AcceptSave,
                                 "file-open-ok", "file-open-cancel"
                                 );
            cmd->js_result_ok = true;
            cmd->result = (r == result_t::oke);
        } else {
            cmd->js_result_ok = true;
            cmd->result = false;
        }
        cmd->done = true;
    }
    break;
    case COMMAND_MESSAGE: {
        int wv = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        QString msg = cmd->args[2].toString();
        QString submsg = cmd->args[3].toString();
        rkt_messagetype_t type = static_cast<rkt_messagetype_t>(cmd->args[4].toInt());

        if (_views.contains(wv)) {
            msgBox(wv, title, msg, submsg, type);
            cmd->js_result_ok = true;
            cmd->result = true;
        } else {
            cmd->js_result_ok = true;
            cmd->result = false;
        }
        cmd->done = true;
    }
    case COMMAND_CREATE_TRAY: {
        QString icon_file = cmd->args[0].toString();
        QString tooltip = cmd->args[1].toString();

        void *f = cmd->args[2].value<void *>();
        event_cb_t js_event_cb = reinterpret_cast <event_cb_t>(f);

        int id = nextHandle();

        QSystemTrayIcon *tray = new QSystemTrayIcon(this);
        tray->setIcon(QIcon(icon_file));
        tray->setToolTip(tooltip);
        tray->setProperty("tray-id", id);

        connect(tray, &QSystemTrayIcon::activated, this, &Rktwebview_qt::handleTrayActivation);
        connect(tray, &QSystemTrayIcon::messageClicked, this, &Rktwebview_qt::handleTrayMessageClick);

        _trays[id] = tray;
        _view_js_callbacks[id] = js_event_cb;

        tray->show();

        cmd->result = id;
        cmd->done = true;
    }
    break;
    case COMMAND_TRAY_SET_ICON: {
        int tray_id = cmd->args[0].toInt();
        QString icon_file = cmd->args[1].toString();

        if (_trays.contains(tray_id)) {
            _trays[tray_id]->setIcon(QIcon(icon_file));
            cmd->result = true;
        } else {
            cmd->result = false;
        }

        cmd->done = true;
    }
    break;
    case COMMAND_TRAY_SET_TOOLTIP: {
        int tray_id = cmd->args[0].toInt();
        QString tooltip = cmd->args[1].toString();

        if (_trays.contains(tray_id)) {
            _trays[tray_id]->setToolTip(tooltip);
            cmd->result = true;
        } else {
            cmd->result = false;
        }

        cmd->done = true;
    }
    break;
    case COMMAND_TRAY_SHOW_MESSAGE: {
        int tray_id = cmd->args[0].toInt();
        QString title = cmd->args[1].toString();
        QString message = cmd->args[2].toString();

        if (_trays.contains(tray_id)) {
            _trays[tray_id]->showMessage(title, message);
            cmd->result = true;
        } else {
            cmd->result = false;
        }

        cmd->done = true;
    }
    break;
    case COMMAND_TRAY_SET_MENU: {
        int tray_id = cmd->args[0].toInt();
        int has_menu = cmd->args[1].toInt();
        QString menu_json = cmd->args[2].toString();

        if (!_trays.contains(tray_id)) {
            cmd->result = false;
            cmd->done = true;
            break;
        }

        if (_tray_menus.contains(tray_id)) {
            QMenu *old = _tray_menus[tray_id];
            _tray_menus.remove(tray_id);
            delete old;
        }

        QMenu *menu = (has_menu) ? buildMenuFromJson(this, menu_json, tray_id) : nullptr;
        _tray_menus[tray_id] = menu;

        /*
        if (menu == nullptr) {
            if (!has_menu) {
                INFO1("null menu, setting null menu on tray %d\n", tray_id);
                _trays[tray_id]->setContextMenu(nullptr);
                INFO0("Done setting tray menu to null menu\n");
                WARN0("Note: on Linux, this will not clear the context menu\n");
                cmd->result = true;
                cmd->done = true;
            } else {
                ERROR2("menu json '%s' for tray %d is not oke\n", menu_json.toUtf8().constData(), tray_id);
                cmd->result = false;
                cmd->done = true;
            }
        } else {
            _tray_menus[tray_id] = menu;
            _trays[tray_id]->setContextMenu(menu);
        }
        */

        cmd->result = true;
        cmd->done = true;
    }
    break;
    default: {
        cmd->result = false;
        cmd->done = true;
    }
    break;
    }
}

void Rktwebview_qt::handleTrayActivation(QSystemTrayIcon::ActivationReason reason)
{
    QObject *obj = sender();
    int tray_wv = obj->property("tray-id").toInt();

    QSystemTrayIcon *icn = qobject_cast<QSystemTrayIcon *>(obj);

    // If a context menu has been set, pop it up and leave.
    if (reason == QSystemTrayIcon::ActivationReason::Context) {
        QMenu *mnu = nullptr;
        if (_tray_menus.contains(tray_wv)) {
            mnu = _tray_menus[tray_wv];
        }
        if (mnu != nullptr) {
            mnu->popup(QCursor::pos());
            return;
        }
    }

    QString evt = "tray-activated";
    QString reason_str = activationReasonToString(reason);

    //INFO3("triggering event: %s for tray %d with reason %s\n",
    //      evt.toUtf8().constData(), tray_wv, reason_str.toUtf8().constData());

    EventContainer e(evt);
    e["reason"] = reason_str;

    this->triggerEvent(tray_wv, e);
}

void Rktwebview_qt::handleTrayMessageClick()
{
    QObject *obj = sender();

    //INFO0("triggering message click\n");

    int tray_wv = obj->property("tray-id").toInt();
    QString evt = "tray-message-clicked";

    EventContainer e(evt);
    this->triggerEvent(tray_wv, e);
}

void Rktwebview_qt::removeView(int id)
{
    if (_views.contains(id)) {
        //WebviewWindow *win = _views[id];
        _views.remove(id);
        _view_js_callbacks.remove(id);
    }
}

int Rktwebview_qt::nextHandle()
{
    int h = ++_current_handle;
    return h;
}

void Rktwebview_qt::closeAllWindows()
{
    auto wvs = _views.keys();
    int i;
    for(i = 0; i < wvs.size(); i++) {
        int wv = wvs[i];
        rktWebViewClose(wv);
    }
}

rkt_wv_context_t Rktwebview_qt::newContext(const QString &boilerplate_js, bool has_pem, const QString &optional_server_cert_pem)
{
    Command c(COMMAND_NEW_CONTEXT);

    c.args.push_back(boilerplate_js);
    c.args.push_back(has_pem);
    c.args.push_back(optional_server_cert_pem);

    postCommand(&c);

    while(!c.done) { doEvents(); }

    int id = c.result.toInt();
    return id;
}

int Rktwebview_qt::rktWebViewCreate(rkt_wv_context_t context, rktwebview_t parent, event_cb_t js_evt_cb)
{
    Command c(COMMAND_CREATE);
    c.args.push_back(context);
    c.args.push_back(parent);
    void *function = reinterpret_cast<void *>(js_evt_cb);
    QVariant f(QVariant::fromValue(function));
    c.args.push_back(f);

    postCommand(&c);

    while(!c.done) { doEvents(); }

    int id = c.result.toInt();
    return id;
}

void Rktwebview_qt::rktWebViewClose(int wv)
{
    Command c(COMMAND_CLOSE);
    c.args.push_back(wv);

    postCommand(&c);

    while(!c.done) { doEvents(); }
}

void Rktwebview_qt::rktSetOUToken(rktwebview_t wv, const char *ou_token)
{
    Command c(COMMAND_SET_OU_TOKEN);
    c.args.push_back(wv);
    QString ou_tok(ou_token);
    c.args.push_back(ou_tok);

    postCommand(&c);

    while(!c.done) { doEvents(); }
}

result_t Rktwebview_qt::rktSetUrl(rktwebview_t wv, const char *url)
{
    Command c(COMMAND_SET_URL);
    QString _url(url);
    c.args.push_back(wv);
    c.args.push_back(_url);
    postCommand(&c);

    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();

    return r ? result_t::oke : result_t::set_navigate_failed;
}

result_t Rktwebview_qt::rktSetHtml(rktwebview_t wv, const char *html)
{
    Command c(COMMAND_SET_HTML);
    QString _html(html);
    c.args.push_back(wv);
    c.args.push_back(_html);
    postCommand(&c);

    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();

    return r ? result_t::oke : result_t::set_html_failed;
}

rkt_data_t *Rktwebview_qt::rktCallJs(rktwebview_t wv, const char *js)
{
    DEBUG1("calljs: %s\n", js);

    Command c(COMMAND_CALL_JS);
    QString _js(js);
    c.args.push_back(wv);
    c.args.push_back(_js);
    postCommand(&c);
    while(!c.done) { doEvents(); }

    rkt_data_t *r = static_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
    r->kind = js_result;
    r->data.js_result.result = c.js_result_ok ? result_t::oke : result_t::eval_js_failed;
    r->data.js_result.value = copyString(c.result.toString().toUtf8());
    DEBUG1("js-result: %s\n", r->data.js_result.value);

    return r;
}

result_t Rktwebview_qt::rktRunJs(rktwebview_t wv, const QString &js)
{
    DEBUG1("rktRunJs: %s\n", js.toUtf8().constData());
    Command c(COMMAND_RUN_JS);
    c.args.push_back(wv);
    c.args.push_back(js);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::eval_js_failed;
}

result_t Rktwebview_qt::rktMove(rktwebview_t wv, int x, int y)
{
    Command c(COMMAND_MOVE);
    c.args.push_back(wv);
    c.args.push_back(x);
    c.args.push_back(y);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::move_failed;
}

result_t Rktwebview_qt::rktResize(rktwebview_t wv, int w, int h)
{
    Command c(COMMAND_RESIZE);
    c.args.push_back(wv);
    c.args.push_back(w);
    c.args.push_back(h);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::resize_failed;
}

result_t Rktwebview_qt::rktHideWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_HIDE_WIN, result_t::failed);
}

result_t Rktwebview_qt::rktShowWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_SHOW_WIN, result_t::failed);
}

result_t Rktwebview_qt::rktPresentWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_PRESENT_WIN, result_t::failed);
}

result_t Rktwebview_qt::rktMaximizeWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_MAX_WIN, result_t::failed);
}

result_t Rktwebview_qt::rktMinimizeWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_MIN_WIN, result_t::failed);
}

result_t Rktwebview_qt::rktShowNormalWindow(rktwebview_t w)
{
    return doWindow(w, COMMAND_SHOW_NORMAL_WIN, result_t::failed);
}

window_state_t Rktwebview_qt::rktWindowState(rktwebview_t w)
{
    Command c(COMMAND_WINDOW_STATUS);
    c.args.push_back(w);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    int r = c.result.toInt();
    window_state_t ws = static_cast<window_state_t>(r);
    return ws;
}

int Rktwebview_qt::openWindows()
{
    return _views.size();
}

void Rktwebview_qt::rktQuit()
{
    Command c(COMMAND_QUIT);
    postCommand(&c);

    while(!c.done) { doEvents(); }
}

result_t Rktwebview_qt::fileDlg(rktwebview_t w,
                                const QString &title,
                                const QString &base,
                                const QString &filters,
                                QFileDialog::FileMode mode,
                                QFileDialog::AcceptMode am,
                                QString evt_ok,
                                QString evt_cancel)
{
    if (_views.contains(w)) {
        WebviewWindow *win = _views[w];

        QFileDialog *dlg = new QFileDialog(win, title, base);
        dlg->setFileMode(mode);
        dlg->setAcceptMode(am);

        QString pe = filters;
        QStringList p = pe.split(";;");

        dlg->setNameFilters(p);
        dlg->setProperty("rktwebview", w);
        dlg->setProperty("evt-ok", evt_ok);
        dlg->setProperty("evt-cancel", evt_cancel);

        connect(dlg, &QFileDialog::accepted, this, [this]() {
            QFileDialog *dlg = static_cast<QFileDialog *>(sender());

            rktwebview_t w = dlg->property("rktwebview").toInt();
            QString evt_ok = dlg->property("evt-ok").toString();

            QStringList l = dlg->selectedFiles();
            QString file;
            if (l.size() > 0) {
                file = l[0];
            }

            EventContainer e(evt_ok);
            e["choosen"] = file;
            e["filter"] = dlg->selectedNameFilter();
            e["dir"] = dlg->directory().path();

            this->triggerEvent(w, e);
        });

        connect(dlg, &QDialog::rejected, this, [this]() {
            QFileDialog *dlg = static_cast<QFileDialog *>(sender());

            rktwebview_t w = dlg->property("rktwebview").toInt();
            QString evt_cancel = dlg->property("evt-cancel").toString();

            EventContainer e(evt_cancel);
            this->triggerEvent(w, e);
        });
        dlg->show();
        return result_t::oke;
    } else {
        return result_t::failed;
    }
}

result_t Rktwebview_qt::rktChooseDir(rktwebview_t w, const char *title, const char *base_dir)
{
    Command c(COMMAND_CHOOSE_DIR);
    c.args.push_back(w);
    QString s_title(title);
    c.args.push_back(s_title);
    QString s_base_dir(base_dir);
    c.args.push_back(s_base_dir);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::resize_failed;
}

result_t Rktwebview_qt::rktFileOpen(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
    Command c(COMMAND_FILE_OPEN);
    c.args.push_back(w);
    QString s_title(title);
    c.args.push_back(s_title);
    QString s_base_dir(base_dir);
    c.args.push_back(s_base_dir);
    QString s_pe(permitted_exts);
    c.args.push_back(s_pe);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::resize_failed;
}

result_t Rktwebview_qt::rktFileSave(rktwebview_t w, const char *title, const char *base_dir, const char *permitted_exts)
{
    Command c(COMMAND_FILE_SAVE);
    c.args.push_back(w);
    QString s_title(title);
    c.args.push_back(s_title);
    QString s_base_dir(base_dir);
    c.args.push_back(s_base_dir);
    QString s_pe(permitted_exts);
    c.args.push_back(s_pe);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::resize_failed;
}

result_t Rktwebview_qt::rktWindowSetTitle(rktwebview_t wv, const char *title)
{
    Command c(COMMAND_SET_TITLE);
    c.args.push_back(wv);
    c.args.push_back(title);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::failed;
}

result_t Rktwebview_qt::rktWindowSetIcon(rktwebview_t wv, const char *icon_file)
{
    Command c(COMMAND_SET_ICON);
    c.args.push_back(wv);
    c.args.push_back(icon_file);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::failed;
}

void Rktwebview_qt::msgBox(rktwebview_t w, const QString &title, const QString &message, const QString &submessage, rkt_messagetype_t type)
{

    WebviewWindow *win = _views[w];
    QMessageBox *msg = new QMessageBox(win);

    auto prepButton = [msg, w, this](QMessageBox::StandardButton b, QString event) {
        msg->addButton(b);
        QAbstractButton *btn = msg->button(b);
        QVariant v = QVariant::fromValue(msg);
        btn->setProperty("messagebox", v);
        btn->setProperty("event", event);
        btn->setProperty("rktwebview", w);
        connect(btn, &QAbstractButton::clicked, this, [this]() {
            QAbstractButton *btn = static_cast<QAbstractButton *>(sender());
            QMessageBox *mb = btn->property("messagebox").value<QMessageBox *>();
            rktwebview_t w = btn->property("rktwebview").toInt();

            EventContainer e(btn->property("event").toString());
            this->triggerEvent(w, e);

            mb->close();
        });
    };

    msg->setWindowTitle(title);

    QString m = QString("<p>") + message + "</p>";
    m += QString::asprintf("<p><small>%s</small></p>", submessage.toUtf8().constData());
    msg->setInformativeText(m);

    QMessageBox::Icon icn;

    if (type == rkt_messagetype_t::error) {
        icn = QMessageBox::Icon::Critical;
        prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok");
    } else if (type == rkt_messagetype_t::warning) {
        icn = QMessageBox::Icon::Warning;
        prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok");
    } else if (type == rkt_messagetype_t::yes_no || type == rkt_messagetype_t::oke_cancel) {
        icn = QMessageBox::Icon::Question;
        if (type == rkt_messagetype_t::yes_no) {
            prepButton(QMessageBox::StandardButton::No, "msgbox-no");
            prepButton(QMessageBox::StandardButton::Yes, "msgbox-yes");
        } else {
            prepButton(QMessageBox::StandardButton::Cancel, "msgbox-cancel");
            prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok");
        }
    } else { // informatio
        icn = QMessageBox::Icon::Information;
        prepButton(QMessageBox::StandardButton::Ok, "msgbox-ok");
    }
    msg->setIcon(icn);
    msg->show();
}

result_t Rktwebview_qt::rktMessageBox(rktwebview_t w, const char *title, const char *message, const char *submessage, rkt_messagetype_t type)
{
    Command c(COMMAND_MESSAGE);
    c.args.push_back(w);
    QString s_title(title);
    c.args.push_back(s_title);
    QString s_msg(message);
    c.args.push_back(s_msg);
    QString s_submsg(submessage);
    c.args.push_back(s_submsg);
    c.args.push_back(static_cast<int>(type));

    postCommand(&c);

    while(!c.done) { doEvents(); }

    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::failed;
}

rktwebview_t Rktwebview_qt::rktTrayCreate(const char *icon_file, const char *tooltip, event_cb_t evt_cb)
{
    Command c(COMMAND_CREATE_TRAY);
    c.args.push_back(icon_file);
    c.args.push_back(tooltip);

    void *function = reinterpret_cast<void *>(evt_cb);
    QVariant f(QVariant::fromValue(function));
    c.args.push_back(f);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    return c.result.toInt();
}

result_t Rktwebview_qt::rktTraySetIcon(rktwebview_t tray, const char *icon_file)
{
    Command c(COMMAND_TRAY_SET_ICON);
    c.args.push_back(tray);
    c.args.push_back(icon_file);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    return c.result.toBool() ? result_t::oke : result_t::failed;
}

result_t Rktwebview_qt::rktTraySetTooltip(rktwebview_t tray, const char *tooltip)
{
    //INFO2("rktTraySetTooltip: %d %s\n", tray, tooltip);

    Command c(COMMAND_TRAY_SET_TOOLTIP);
    c.args.push_back(tray);
    c.args.push_back(tooltip);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    //INFO1("rktTraySetTooltip done: %s\n", tooltip);

    return c.result.toBool() ? result_t::oke : result_t::failed;
}

result_t Rktwebview_qt::rktTrayShowMessage(rktwebview_t tray, const char *title, const char *message)
{
    Command c(COMMAND_TRAY_SHOW_MESSAGE);
    c.args.push_back(tray);
    c.args.push_back(title);
    c.args.push_back(message);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    return c.result.toBool() ? result_t::oke : result_t::failed;
}

result_t Rktwebview_qt::rktTraySetMenu(rktwebview_t tray, const char *menu_json)
{
    Command c(COMMAND_TRAY_SET_MENU);
    c.args.push_back(tray);
    c.args.push_back(menu_json != nullptr);
    c.args.push_back((menu_json == nullptr) ? "" : menu_json);

    postCommand(&c);
    while(!c.done) { doEvents(); }

    return c.result.toBool() ? result_t::oke : result_t::failed;
}

result_t Rktwebview_qt::doWindow(rktwebview_t w, int cmd, result_t on_error)
{
    Command c(cmd);
    c.args.push_back(w);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : on_error;
}

bool Rktwebview_qt::rktValid(rktwebview_t wv)
{
    return _views.contains(wv);
}

result_t Rktwebview_qt::rktOpenDevtools(rktwebview_t wv)
{
    Command c(COMMAND_DEV_TOOLS);
    c.args.push_back(wv);
    postCommand(&c);
    while(!c.done) { doEvents(); }
    bool r = c.result.toBool();
    return r ? result_t::oke : result_t::eval_js_failed;
}

void Rktwebview_qt::onPageLoad(rktwebview_t w)
{
}

void Rktwebview_qt::pageLoaded(rktwebview_t w, bool ok)
{
    if (!ok) {
        // Inject code of the profile to this page
        WebviewWindow *win = _views[w];

        if (win->navigationEventSent()) {
            return;
        }

        QWebEngineProfile *p = win->profile();
        QWebEngineScriptCollection *col = p->scripts();
        QList<QWebEngineScript> l = col->toList();
        int i;
        for(i = 0; i < l.size(); i++) {
            runJs(w, l[i].sourceCode().toUtf8().constData());
        }
    }


    // trigger page loaded.
    EventContainer e("page-loaded");
    e["oke"] = ok;
    triggerEvent(w, e);
}

void Rktwebview_qt::triggerEvent(rktwebview_t wv, const EventContainer &e)
{
    triggerEvent(wv, mkEventJson(e));
}

void Rktwebview_qt::triggerEvent(rktwebview_t wv, const QString &msg)
{
    if (_view_js_callbacks.contains(wv)) {
        event_cb_t js_event_cb = _view_js_callbacks[wv];
        char *evt = copyString(msg.toUtf8().constData());
        rkt_data_t *d = static_cast<rkt_data_t *>(malloc(sizeof(rkt_data_t)));
        d->kind = rkt_data_kind_t::event;
        d->data.event.wv = wv;
        d->data.event.event = evt;
        js_event_cb(d);
    }
}

void Rktwebview_qt::execApp()
{
    _app->exec();
}

QApplication *Rktwebview_qt::app()
{
    return _app;
}

void Rktwebview_qt::deleteApp()
{
    delete _app;
    _app = nullptr;
}

void Rktwebview_qt::initApp()
{
    _app = new QApplication(_argc, _argv);
    _app->setQuitOnLastWindowClosed(false);

    // See Qt 6.10 remark at doEvents.
    //connect(&_evt_loop_timer, &QTimer::timeout, this, &Rktwebview_qt::stopEventloop);

    // Because we are using processEvents only (Qt 6.10), we need this dispatcher to
    // handle deferred Deletes.

    /*const auto *eventDispatcher = QThread::currentThread()->eventDispatcher();
    QObject::connect(eventDispatcher, &QAbstractEventDispatcher::aboutToBlock,
                     QThread::currentThread(), []{
                         if (QThread::currentThread()->loopLevel() == 0)
                             QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
                     }
                     );
    */
}


void Rktwebview_qt::runJs(rktwebview_t wv, const char *js)
{
    if (_views.contains(wv)) {
        QString _js(js);
        WebviewWindow *win = _views[wv];
        win->runJs(_js);
    }
}


void Rktwebview_qt::postCommand(Command *cmd)
{
    CommandEvent *e = new CommandEvent(cmd);
    QApplication::postEvent(this, e);
}

void Rktwebview_qt::handleCommandEvent(CommandEvent *e)
{
    Command *c = e->cmd();
    processCommand(c);
}

void Rktwebview_qt::customEvent(QEvent *event)
{
    if (event->type() == COMMAND_EVENT) {
        handleCommandEvent(static_cast<CommandEvent *>(event));
    }
}

void Rktwebview_qt::doEvents()
{
    //QTime ct = QTime::currentTime();
    //QTime expire = QTime::currentTime().addMSecs(2);
    //while(QTime::currentTime() <= expire) {
    //_app->processEvents();
    //}

    // Qt 6.10 --> this leads to a core dump
    // together with the stopEventloop stuff.
    // Qt 6.4 seem stable.
    /*if (_evt_loop_depth == 0) {
        _evt_loop_depth += 1;
        _evt_loop_timer.setSingleShot(true);
        _evt_loop_timer.start(2);
        //_evt_loop.exec();
        _app->exec();
    }*/
    QThread::usleep(500);
}

void Rktwebview_qt::stopEventloop()
{
    //_evt_loop.exit(0);
    //_app->exit(0);
    //_evt_loop_depth -= 1;
}



Rktwebview_qt::Rktwebview_qt(int argc, char *argv[]) : QObject()
{
    _context_counter = 0;
    _current_handle = 0;
    _evt_loop_depth = 0;

    _argc = argc;
    _argv = argv;

    _app = nullptr;
}

Rktwebview_qt::~Rktwebview_qt()
{
    QList<int> win_keys = _views.keys();
    int i, N;
    for(i = 0, N = win_keys.size(); i < N; i++) {
        WebviewWindow *w = _views[win_keys[i]];
        delete w;
    }

    QList<int> c_keys = _contexts.keys();
    for(i = 0, N = c_keys.size(); i < N; i++) {
        QWebEngineProfile *p = _contexts[c_keys[i]];
        delete p;
    }

    QList<int> tray_keys = _trays.keys();
    for(i = 0, N = tray_keys.size(); i < N; i++) {
        QSystemTrayIcon *tray = _trays[tray_keys[i]];
        tray->hide();
        delete tray;
    }

    QList<int> menu_keys = _tray_menus.keys();
    for(i = 0, N = menu_keys.size(); i < N; i++) {
        QMenu *menu = _tray_menus[menu_keys[i]];
        delete menu;
    }

    delete _app;
}
