//
// Created by Allen on 24/08/28.
//

#include "JsiCvr.h"
#include "ImgHO.h"
#include "JsiCore.h"
#include <jni.h>
#define JSI_FUNC [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value

jobject jCvrModule;

void install_cvrMethods(Runtime &runtime) {
    jsi_install::cvr::install_captureImageData(runtime);
    jsi_install::cvr::install_cvr_getOriginalImage(runtime);
    jsi_install::cvr::install_getCurrentDeskewedImages(runtime);
    jsi_install::cvr::install_getCurrentEnhancedImages(runtime);
    jsi_install::cvr::install_captureFile(runtime);
    jsi_install::cvr::install_captureFileBytes(runtime);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_dynamsoft_reactnativelib_cvr_CaptureVisionRouterModuleImpl_nativeInstall(JNIEnv *env, jobject thiz, jlong jsiPtr) {
    if (!env->IsSameObject(jCvrModule, thiz)) {
        if (jCvrModule) {
            env->DeleteWeakGlobalRef(jCvrModule);
        }
        jCvrModule = env->NewWeakGlobalRef(thiz);
    }
    auto runtime = reinterpret_cast<Runtime *>(jsiPtr);
    if (runtime) {
        install_cvrMethods(*runtime);
    }
}

namespace jsiConversion::cvr {
void jCapturedResultToJsiObject(Runtime &rt, JNIEnv *env, jobject jCapturedResult, Object &jsCapturedResult) {
    env->PushLocalFrame(16);
    jclass jclsCapturedResult = env->FindClass("com/dynamsoft/cvr/CapturedResult");
    SET_STRING_PROPERTY_BY_FIELD(jclsCapturedResult, jCapturedResult, jsCapturedResult, originalImageHashId)
    SET_STRING_PROPERTY_BY_FIELD(jclsCapturedResult, jCapturedResult, jsCapturedResult, errorMessage)

    jfieldID errorCode = env->GetFieldID(jclsCapturedResult, "errorCode", "I");
    jsCapturedResult.setProperty(rt, "errorCode", env->GetIntField(jCapturedResult, errorCode));

    jfieldID fiMatrix = env->GetFieldID(jclsCapturedResult, "rotationTransformMatrix", "Landroid/graphics/Matrix;");
    jobject jMatrix = env->GetObjectField(jCapturedResult, fiMatrix);
    if(jMatrix) {
        jclass clsMatrix = env->GetObjectClass(jMatrix);
        jmethodID getValues = env->GetMethodID(clsMatrix, "getValues", "([F)V");
        jfloatArray jValues = env->NewFloatArray(9);
        env->CallVoidMethod(jMatrix, getValues, jValues);
        float cValues[9];
        env->GetFloatArrayRegion(jValues, 0, 9, cValues);
        Array jsMatrix(rt, 9);
        for (int i = 0; i < 9; ++i) {
            jsMatrix.setValueAtIndex(rt, i, cValues[i]);
        }
        env->DeleteLocalRef(clsMatrix);
        env->DeleteLocalRef(jValues);
        env->DeleteLocalRef(jMatrix);
        jsCapturedResult.setProperty(rt, "rotationTransformMatrix", jsMatrix);
    }

    jfieldID items = env->GetFieldID(jclsCapturedResult, "items", "[Lcom/dynamsoft/core/basic_structures/CapturedResultItem;");
    jobjectArray jItems = (jobjectArray) env->GetObjectField(jCapturedResult, items);
    Array jsItemArray(rt, 0);
    int *types = nullptr;

    jsiConversion::core::jCapturedItemArrayToJsArray(rt, env, jItems, types, jsItemArray);
    for (int i = 0; i < jsItemArray.size(rt); ++i) {
        Object jsItem = jsItemArray.getValueAtIndex(rt, i).asObject(rt);
        jobject jItem = env->GetObjectArrayElement(jItems, i);
        int type = types[i];
        if (type == CRIT_BARCODE) {
            jsiConversion::dbr::jBarcodeResultItemToJsObject(rt, env, jItem, jsItem);
        } else if (type == CRIT_TEXT_LINE) {
            jsiConversion::dlr::jTextLineResultItemToJsObject(rt, env, jItem, jsItem);
        } else if (type == CRIT_DETECTED_QUAD) {
            jsiConversion::ddn::jDetectedQuadResultItemToJsObject(rt, env, jItem, jsItem);
        } else if (type == CRIT_DESKEWED_IMAGE) {
            jsiConversion::ddn::jDeskewedImageResultItemToJsObject(rt, env, jItem, jsItem);
        } else if (type == CRIT_PARSED_RESULT) {
            jsiConversion::dcp::jParsedResultItemToJsObject(rt, env, jItem, jsItem);
        } else if(type == CRIT_ENHANCED_IMAGE) {
            jsiConversion::ddn::jEnhancedImageResultItemToJsObject(rt, env, jItem, jsItem);
        }
        env->DeleteLocalRef(jItem);
    }
    if(types) {
        delete[] types;
    }
    jsCapturedResult.setProperty(rt, "items", jsItemArray);
    env->PopLocalFrame(nullptr);
}
}//namespace jsiConversion::cvr

namespace jsi_install::cvr {
void install_captureImageData(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "captureImageData");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto captureImageDataFunc = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
            auto obj = arguments[0].asObject(runtime);
            auto imgHO = ImageDataHostObject::getHOFromObject(runtime, std::move(obj));
            Object jsCapturedResult = captureImageDataHO(imgHO, runtime, arguments[1].asString(runtime).utf8(runtime));
            return jsCapturedResult;
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(captureImageDataFunc));
    }
}
void install_captureFile(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "captureFile");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto captureFileFunc = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
            auto filePath = arguments[0].asString(runtime);
            Object jsCapturedResult = captureFile(filePath.utf8(runtime), runtime, arguments[1].asString(runtime).utf8(runtime));
            return jsCapturedResult;
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(captureFileFunc));
    }
}
void install_captureFileBytes(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "captureFileBytes");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto captureFileBytesFunc = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
            ArrayBuffer fileBytes = arguments[0].asObject(runtime).getArrayBuffer(runtime);
            Object jsCapturedResult = captureFileBytes(fileBytes, runtime, arguments[1].asString(runtime).utf8(runtime));
            return jsCapturedResult;
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(captureFileBytesFunc));
    }
}
void install_getCurrentDeskewedImages(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "getCurrentDeskewedImages");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto getCurrentDeskewedImagesFunc = Function::createFromHostFunction(jsiRuntime, nameId, 0, JSI_FUNC {
            Array images = getCurrentDeskewedImages(runtime);
            return images;
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(getCurrentDeskewedImagesFunc));
    }
}
void install_getCurrentEnhancedImages(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "getCurrentEnhancedImages");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto getCurrentEnhancedImagesFunc = Function::createFromHostFunction(jsiRuntime, nameId, 0, JSI_FUNC {
            Array images = getCurrentEnhancedImages(runtime);
            return images;
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(getCurrentEnhancedImagesFunc));
    }
}
void install_cvr_getOriginalImage(Runtime &jsiRuntime) {
    auto nameId = PropNameID::forAscii(jsiRuntime, "cvr_getOriginalImage");
    if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
        auto cvr_getOriginalImageFunc = Function::createFromHostFunction(jsiRuntime, nameId, 1, JSI_FUNC {
            string hashId = arguments[0].asString(runtime).utf8(runtime);
            if (hashId.empty()) {
                return {};
            }
            ImageDataHostObject imgHO = getOriginalImage(runtime, hashId);
            if(imgHO.width == 0 && imgHO.height == 0) {
                return {};
            } else {
                return Object::createFromHostObject(runtime, make_shared<ImageDataHostObject>(imgHO));
            }
        });
        jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(cvr_getOriginalImageFunc));
    }
}
}//namespace jsi_install::cvr


Object captureImageDataHO(ImageDataHostObject &imageDataHO, Runtime &rt, string templateName) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    env->PushLocalFrame(16);
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto captureMeth = env->GetMethodID(clzCvrModule, "captureImageDataCalledInJSI",
                                        "(Lcom/dynamsoft/core/basic_structures/ImageData;Ljava/lang/String;)Lcom/dynamsoft/cvr/CapturedResult;");
    auto imageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jResult = env->CallObjectMethod(jCvrModule, captureMeth, imageData, env->NewStringUTF(templateName.c_str()));
    Object jsResult(rt);
    jsiConversion::cvr::jCapturedResultToJsiObject(rt, env, jResult, jsResult);
    env->PopLocalFrame(nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return jsResult;
}

Object captureFile(string filePath, Runtime &rt, string templateName) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    env->PushLocalFrame(8);
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto captureMeth = env->GetMethodID(clzCvrModule, "captureFileCalledInJSI",
                                        "(Ljava/lang/String;Ljava/lang/String;)Lcom/dynamsoft/cvr/CapturedResult;");
    auto jResult = env->CallObjectMethod(jCvrModule, captureMeth, env->NewStringUTF(filePath.c_str()), env->NewStringUTF(templateName.c_str()));
    Object jsResult(rt);
    jsiConversion::cvr::jCapturedResultToJsiObject(rt, env, jResult, jsResult);
    env->PopLocalFrame(nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return jsResult;
}
Object captureFileBytes(ArrayBuffer &fileBytes, Runtime &rt, string templateName) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    env->PushLocalFrame(8);
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto captureMeth = env->GetMethodID(clzCvrModule, "captureFileBytesCalledInJSI",
                                        "([BLjava/lang/String;)Lcom/dynamsoft/cvr/CapturedResult;");
    auto pBytes = (jbyte *) fileBytes.data(rt);
    int len = fileBytes.length(rt);
    jbyteArray jBytes = env->NewByteArray(len);
    env->SetByteArrayRegion(jBytes, 0, len, (jbyte *) pBytes);
    auto jResult = env->CallObjectMethod(jCvrModule, captureMeth, jBytes, env->NewStringUTF(templateName.c_str()));
    Object jsResult(rt);
    jsiConversion::cvr::jCapturedResultToJsiObject(rt, env, jResult, jsResult);
    env->PopLocalFrame(nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return jsResult;
}

Array getCurrentDeskewedImages(Runtime &runtime) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto fiCurrentImages = env->GetFieldID(clzCvrModule, "currentDeskewedImages", "[Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jCurrentImages = (jobjectArray) env->GetObjectField(jCvrModule, fiCurrentImages);
    int count = jCurrentImages ? env->GetArrayLength(jCurrentImages) : 0;
    Array array(runtime, count);
    for (int i = 0; i < count; ++i) {
        auto imageData = env->GetObjectArrayElement(jCurrentImages, i);
        ImageDataHostObject imgHO;
        jsiConversion::core::getHOFromJImageData(env, imageData, imgHO);
        auto obj = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imgHO));
        array.setValueAtIndex(runtime, i, obj);
        env->DeleteLocalRef(imageData);
    }
    env->DeleteLocalRef(clzCvrModule);
    if (jCurrentImages) {
        env->DeleteLocalRef(jCurrentImages);
    }
    env->SetObjectField(jCvrModule, fiCurrentImages, nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return array;
}

Array getCurrentEnhancedImages(Runtime &runtime) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto fiCurrentImages = env->GetFieldID(clzCvrModule, "currentEnhancedImages", "[Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jCurrentImages = (jobjectArray) env->GetObjectField(jCvrModule, fiCurrentImages);
    int count = jCurrentImages ? env->GetArrayLength(jCurrentImages) : 0;
    Array array(runtime, count);
    for (int i = 0; i < count; ++i) {
        auto imageData = env->GetObjectArrayElement(jCurrentImages, i);
        ImageDataHostObject imgHO;
        jsiConversion::core::getHOFromJImageData(env, imageData, imgHO);
        auto obj = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imgHO));
        array.setValueAtIndex(runtime, i, obj);
        env->DeleteLocalRef(imageData);
    }
    env->DeleteLocalRef(clzCvrModule);
    if (jCurrentImages) {
        env->DeleteLocalRef(jCurrentImages);
    }
    env->SetObjectField(jCvrModule, fiCurrentImages, nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return array;
}

ImageDataHostObject getOriginalImage(Runtime &runtime, string imageHashId) {
    JNIEnv *env;
    bool needDetach = false;
    int state = gJvm->GetEnv((void **) &env, JNI_VERSION_1_6);
    if (state == JNI_EDETACHED || env == nullptr) {
        gJvm->AttachCurrentThread(&env, nullptr);
        needDetach = true;
    }
    env->PushLocalFrame(8);
    auto clzCvrModule = env->GetObjectClass(jCvrModule);
    auto getOriginalImageCalledInJSI =
            env->GetMethodID(clzCvrModule, "getOriginalImageCalledInJSI", "(Ljava/lang/String;)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = env->CallObjectMethod(jCvrModule, getOriginalImageCalledInJSI, env->NewStringUTF(imageHashId.c_str()));
    ImageDataHostObject imgHO;
    if (jImageData != nullptr) {
        jsiConversion::core::getHOFromJImageData(env, jImageData, imgHO);
    }
    env->PopLocalFrame(nullptr);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imgHO;
}

