#include "QJS.hpp"

#ifdef _MSC_VER
#define JS_STRICT_NAN_BOXING
#endif

#include "3rdparty/qjs/nekoray_qjs.h"
#include "main/NekoGui.hpp"

namespace qjs {
#ifndef NKR_NO_QUICKJS
    namespace exception {
        static void js_dump_obj(JSContext *ctx, QString &out, JSValueConst val) {
            const char *str;

            str = JS_ToCString(ctx, val);
            if (str) {
                out.append(str);
                out.append('\n');
                JS_FreeCString(ctx, str);
            } else {
                out += "[exception]\n";
            }
        }

        static void js_std_dump_error1(JSContext *ctx, QString &out, JSValueConst exception_val) {
            JSValue val;
            auto is_error = JS_IsError(ctx, exception_val);
            js_dump_obj(ctx, out, exception_val);
            if (is_error) {
                val = JS_GetPropertyStr(ctx, exception_val, "stack");
                if (!JS_IsUndefined(val)) {
                    js_dump_obj(ctx, out, val);
                }
                JS_FreeValue(ctx, val);
            }
        }

        QString js_std_dump_error(JSContext *ctx) {
            QString result;
            JSValue exception_val;

            exception_val = JS_GetException(ctx);
            js_std_dump_error1(ctx, result, exception_val);
            JS_FreeValue(ctx, exception_val);

            return result;
        }
    } // namespace exception

    JSValue func_log(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
        QString qString;

        int i;
        const char *str;
        size_t len;

        for (i = 0; i < argc; i++) {
            if (i != 0) qString.append(' ');
            str = JS_ToCStringLen(ctx, &len, argv[i]);
            if (!str)
                return JS_EXCEPTION;
            qString.append(str);
            JS_FreeCString(ctx, str);
        }

        MW_show_log(qString);
        qDebug() << "func_log:" << qString;

        return JS_UNDEFINED;
    }
#endif

#define NEKO_CTX ((nekoray_qjs_context *) this->neko_ctx)

    QJS::QJS() {
#ifndef NKR_NO_QUICKJS
        MW_show_log("loading quickjs......");
        //
        this->neko_ctx = malloc(sizeof(nekoray_qjs_context));
        nekoray_qjs_new_arg arg;
        arg.neko_ctx = NEKO_CTX;
        arg.enable_std = NekoGui::dataStore->enable_js_hook == 2 ? 1 : 0;
        arg.func_log = func_log;
        nekoray_qjs_new(arg);
#endif
    }

    QJS::QJS(const QByteArray &jsSource) : QJS() {
        this->Eval(jsSource);
    }

    QJS::~QJS() {
#ifndef NKR_NO_QUICKJS
        nekoray_qjs_free(NEKO_CTX);
        free(this->neko_ctx);
#endif
    }

    QString QJS::Eval(const QByteArray &jsSource) const {
#ifndef NKR_NO_QUICKJS
        auto result = nekoray_qjs_eval(NEKO_CTX, jsSource.data(), jsSource.length());
        if (JS_IsException(result)) {
            MW_show_log(exception::js_std_dump_error(NEKO_CTX->ctx));
            return {};
        }
        auto cString = JS_ToCString(NEKO_CTX->ctx, result);
        QString qString(cString);
        JS_FreeCString(NEKO_CTX->ctx, cString);
        JS_FreeValue(NEKO_CTX->ctx, result);
        return qString;
#else
        return {};
#endif
    }

    QString QJS::Eval(const QString &jsSource) const {
        return this->Eval(jsSource.toUtf8());
    }

    QString QJS::EvalFile(const QString &jsPath) const {
        return this->Eval(ReadFile(jsPath));
    }

    QString QJS::EvalFunction(const QString &funcName, const QString &arg) const {
#ifndef NKR_NO_QUICKJS
        auto ba1 = arg.toUtf8();
        JSValue globalObj = JS_GetGlobalObject(NEKO_CTX->ctx);
        JSValue tempObj = JS_NewStringLen(NEKO_CTX->ctx, ba1.data(), ba1.length());
        JS_SetPropertyStr(NEKO_CTX->ctx, globalObj, "tempObj", tempObj);
        auto result = this->Eval(QString("%1(tempObj)").arg(funcName));
        JS_DeleteProperty(NEKO_CTX->ctx, globalObj, JS_NewAtom(NEKO_CTX->ctx, "tempObj"), 1); // Free tempObj
        JS_FreeValue(NEKO_CTX->ctx, globalObj);
        return result;
#else
        return {};
#endif
    }

    QByteArray ReadHookJS() {
#ifndef NKR_NO_QUICKJS
        if (NekoGui::dataStore->enable_js_hook > 0) {
            return ReadFile(QString("./hook.%1.js").arg(software_name.toLower()));
        }
#endif
        return {};
    }
} // namespace qjs