//
// Created by Allen on 24/09/23.
//

#include "JsiUtility.h"
#include "RawBuffer.h"

jobject jImageManagerModule = nullptr;
jclass jclzQuad = nullptr;
jclass jclzPoint = nullptr;
jclass jclzDSRect = nullptr;

using namespace facebook::jsi;

jobject jsPointToJobject(Runtime &rt, JNIEnv *env, Object jsPoint) {
    if (jsPoint.hasProperty(rt, "x") && jsPoint.hasProperty(rt, "y")) {
        int x = jsPoint.getProperty(rt, "x").asNumber();
        int y = jsPoint.getProperty(rt, "y").asNumber();
        jmethodID init = env->GetMethodID(jclzPoint, "<init>", "(II)V");
        jobject jPoint = env->NewObject(jclzPoint, init, x, y);
        return jPoint;
    }
    return nullptr;
}

jobject jsQuadToJobject(Runtime &rt, JNIEnv *env, Object &jsQuad) {
    if (jsQuad.hasProperty(rt, "points")) {
        auto jsPoints = jsQuad.getProperty(rt, "points").asObject(rt).asArray(rt);
        int count = jsPoints.length(rt);
        auto jPoints = env->NewObjectArray(count, jclzPoint, nullptr);
        for (int i = 0; i < count; ++i) {
            auto jPoint = jsPointToJobject(rt, env, jsPoints.getValueAtIndex(rt, i).asObject(rt));
            env->SetObjectArrayElement(jPoints, i, jPoint);
            env->DeleteLocalRef(jPoint);
        }
        auto initQuad = env->GetMethodID(jclzQuad, "<init>", "()V");
        auto fieldId_points = env->GetFieldID(jclzQuad, "points", "[Landroid/graphics/Point;");
        auto jQuad = env->NewObject(jclzQuad, initQuad);
        env->SetObjectField(jQuad, fieldId_points, jPoints);
        return jQuad;
    }
    return nullptr;
}

jobjectArray jsQuadArrayToJobjectArray(Runtime &rt, JNIEnv *env, Array &jsQuads) {
    int count = jsQuads.length(rt);
    jobjectArray jQuads = env->NewObjectArray(count, jclzQuad, nullptr);
    for (int i = 0; i < count; ++i) {
        auto jsQuad = jsQuads.getValueAtIndex(rt, i).asObject(rt);
        auto jQuad = jsQuadToJobject(rt, env, jsQuad);
        env->SetObjectArrayElement(jQuads, i, jQuad);
        env->DeleteLocalRef(jQuad);
    }
    return jQuads;
}

jobject jsDsRectToJobject(Runtime &rt, JNIEnv *env, Object &jsDSRect) {
    if (!jsDSRect.hasProperty(rt, "left") || !jsDSRect.hasProperty(rt, "top") || !jsDSRect.hasProperty(rt, "right") || !jsDSRect.hasProperty(rt, "bottom") ||
        !jsDSRect.hasProperty(rt, "measuredInPercentage")) {
        return nullptr;
    }
    float top = (float) jsDSRect.getProperty(rt, "top").asNumber();
    float left = (float) jsDSRect.getProperty(rt, "left").asNumber();
    float bottom = (float) jsDSRect.getProperty(rt, "bottom").asNumber();
    float right = (float) jsDSRect.getProperty(rt, "right").asNumber();
    bool measuredInPercentage = jsDSRect.getProperty(rt, "measuredInPercentage").asBool();
    jmethodID init = env->GetMethodID(jclzDSRect, "<init>", "(FFFFZ)V");
    jobject jDSRect = env->NewObject(jclzDSRect, init, left, top, right, bottom, measuredInPercentage);
    return jDSRect;
}


void saveImageDataHOToFile(Runtime &rt, ImageDataHostObject &imageDataHO, string filePath, bool overWrite) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "saveToFileCalledInJsi", "(Lcom/dynamsoft/core/basic_structures/ImageData;Ljava/lang/String;Z)V");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    env->CallVoidMethod(jImageManagerModule, methodId, jImageData, env->NewStringUTF(filePath.c_str()), overWrite);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
}

ImageDataHostObject readImageDataHOFromFile(Runtime &rt, string filePath) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "readFromFileCalledInJsi", "(Ljava/lang/String;)Lcom/dynamsoft/core/basic_structures/ImageData;");
    jstring jFilePath = env->NewStringUTF(filePath.c_str());
    auto jImageData = env->CallObjectMethod(jImageManagerModule, methodId, jFilePath);
    ImageDataHostObject imageHO;
    jsiConversion::core::getHOFromJImageData(env, jImageData, imageHO);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jFilePath);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHO;
}

ImageDataHostObject readImageDataHOFromMemory(Runtime &rt, unsigned char *buffer, int bufferSize) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "readFromMemoryCalledInJsi", "([B)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jBuffer = env->NewByteArray(bufferSize);
    env->SetByteArrayRegion(jBuffer, 0, bufferSize, (jbyte *) buffer);
    auto jImageData = env->CallObjectMethod(jImageManagerModule, methodId, jBuffer);
    ImageDataHostObject imageHO;
    jsiConversion::core::getHOFromJImageData(env, jImageData, imageHO);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jBuffer);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHO;
}

ArrayBuffer saveImageDataHOToMemory(Runtime &rt, ImageDataHostObject &imageDataHO, int fileFormat) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "saveToMemoryCalledInJsi", "(Lcom/dynamsoft/core/basic_structures/ImageData;I)[B");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jBuffer = (jbyteArray) env->CallObjectMethod(jImageManagerModule, methodId, jImageData, fileFormat);
    auto pBuffer = (uint8_t *) env->GetByteArrayElements(jBuffer, nullptr);
    int bufferSize = env->GetArrayLength(jBuffer);
    auto mutableBuffer = std::make_shared<MutableRawBuffer>(pBuffer, bufferSize, false);
    ArrayBuffer arrayBuffer(rt, mutableBuffer);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    return arrayBuffer;
}

ImageDataHostObject drawOnImage(Runtime &rt, ImageDataHostObject &imageDataHO, Array &jsQuads, int color, int thickness) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "drawOnImageCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;[Lcom/dynamsoft/core/basic_structures/Quadrilateral;II)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jQuads = jsQuadArrayToJobjectArray(rt, env, jsQuads);
    auto jImageDataAfterDraw = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, jQuads, color, thickness);
    ImageDataHostObject imageHOAfterDraw;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterDraw, imageHOAfterDraw);
    env->DeleteLocalRef(jQuads);
    env->DeleteLocalRef(jImageDataAfterDraw);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterDraw;
}

ImageDataHostObject cropImage(Runtime &rt, ImageDataHostObject &imageDataHO, Object &jsDSRect) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "cropImageCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;Lcom/dynamsoft/core/basic_structures/DSRect;)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jDSRect = jsDsRectToJobject(rt, env, jsDSRect);
    auto jImageDataAfterCrop = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, jDSRect);
    ImageDataHostObject imageHOAfterCrop;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterCrop, imageHOAfterCrop);
    env->DeleteLocalRef(jDSRect);
    env->DeleteLocalRef(jImageDataAfterCrop);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterCrop;
}

ImageDataHostObject cropAndDeskewImage(Runtime &rt, ImageDataHostObject &imageDataHO, Object &jsQuad, int dstWidth, int dstHeight, int padding) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "cropAndDeskewImageCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;Lcom/dynamsoft/core/basic_structures/Quadrilateral;III)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jQuad = jsQuadToJobject(rt, env, jsQuad);
    auto jImageDataAfterCropAndDeskew = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, jQuad, dstWidth, dstHeight, padding);
    ImageDataHostObject imageHOAfterCrop;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterCropAndDeskew, imageHOAfterCrop);
    env->DeleteLocalRef(jQuad);
    env->DeleteLocalRef(jImageDataAfterCropAndDeskew);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterCrop;
}


const int type_adjust_brightness = 0;
const int type_adjust_contrast = 1;
const int type_filter = 2;

ImageDataHostObject adjustOrFilter(Runtime &rt, ImageDataHostObject &imageDataHO, int value, int type) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId;
    switch (type) {
        case type_adjust_brightness:
            methodId = env->GetMethodID(clz, "adjustBrightnessCalledInJsi",
                                        "(Lcom/dynamsoft/core/basic_structures/ImageData;I)Lcom/dynamsoft/core/basic_structures/ImageData;");
            break;
        case type_adjust_contrast:
            methodId = env->GetMethodID(clz, "adjustContrastCalledInJsi",
                                        "(Lcom/dynamsoft/core/basic_structures/ImageData;I)Lcom/dynamsoft/core/basic_structures/ImageData;");
            break;
        case type_filter:
        default:
            methodId = env->GetMethodID(clz, "filterImageCalledInJsi",
                                        "(Lcom/dynamsoft/core/basic_structures/ImageData;I)Lcom/dynamsoft/core/basic_structures/ImageData;");
            break;
    }
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jImageDataAfter = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, value);
    ImageDataHostObject imageHOAfter;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfter, imageHOAfter);
    env->DeleteLocalRef(jImageDataAfter);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfter;
}

ImageDataHostObject convertToGray(Runtime &rt, ImageDataHostObject &imageDataHO, float r, float g, float b) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "convertToGrayCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;FFF)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jImageDataAfterConvert = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, r, g, b);
    ImageDataHostObject imageHOAfterConvert;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterConvert, imageHOAfterConvert);
    env->DeleteLocalRef(jImageDataAfterConvert);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterConvert;
}

ImageDataHostObject convertToBinaryGlobal(Runtime &rt, ImageDataHostObject &imageDataHO, int threshold, bool invert) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "convertToBinaryGlobalCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;IZ)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jImageDataAfterConvert = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, threshold, invert);
    ImageDataHostObject imageHOAfterConvert;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterConvert, imageHOAfterConvert);
    env->DeleteLocalRef(jImageDataAfterConvert);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterConvert;
}

ImageDataHostObject convertToBinaryLocal(Runtime &rt, ImageDataHostObject &imageDataHO, int blockSize, int compensation, bool invert) {
    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;
    }
    jclass clz = env->GetObjectClass(jImageManagerModule);
    jmethodID methodId = env->GetMethodID(clz, "convertToBinaryLocalCalledInJsi",
                                          "(Lcom/dynamsoft/core/basic_structures/ImageData;IIZ)Lcom/dynamsoft/core/basic_structures/ImageData;");
    auto jImageData = jsiConversion::core::getJImageDataFromHO(env, imageDataHO);
    auto jImageDataAfterConvert = env->CallObjectMethod(jImageManagerModule, methodId, jImageData, blockSize, compensation, invert);
    ImageDataHostObject imageHOAfterConvert;
    jsiConversion::core::getHOFromJImageData(env, jImageDataAfterConvert, imageHOAfterConvert);
    env->DeleteLocalRef(jImageDataAfterConvert);
    env->DeleteLocalRef(clz);
    env->DeleteLocalRef(jImageData);
    if (needDetach) {
        gJvm->DetachCurrentThread();
    }
    return imageHOAfterConvert;
}


#define JSI_FUNC [](Runtime &runtime, const Value &thisValue, const Value *arguments, size_t count) -> Value

namespace jsi_install::utility {
    void install_imageIO_saveToFile(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageIO_saveToFile");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto saveToFileFcn = Function::createFromHostFunction(jsiRuntime, nameID, 3, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto filePath = arguments[1].asString(runtime).utf8(runtime);
                bool overWrite = arguments[2].asBool();
                saveImageDataHOToFile(runtime, imageDataHostObject, filePath, overWrite);
                return {};
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(saveToFileFcn));
        }
    }

    void install_imageIO_readFromFile(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageIO_readFromFile");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto readFromFileFcn = Function::createFromHostFunction(jsiRuntime, nameID, 1, JSI_FUNC {
                auto filePath = arguments[0].asString(runtime).utf8(runtime);
                auto imageDataHostObject = readImageDataHOFromFile(runtime, filePath);
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageDataHostObject));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(readFromFileFcn));
        }
    }

    void install_imageIO_readFromMemory(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageIO_readFromMemory");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto readFromMemoryFcn = Function::createFromHostFunction(jsiRuntime, nameID, 1, JSI_FUNC {
                auto buffer = arguments[0].asObject(runtime).getArrayBuffer(runtime);
                auto pBuffer = (unsigned char *) buffer.data(runtime);
                auto len = (int) buffer.length(runtime);
                auto imageDataHostObject = readImageDataHOFromMemory(runtime, pBuffer, len);
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageDataHostObject));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(readFromMemoryFcn));
        }
    }

    void install_imageIO_saveToMemory(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageIO_saveToMemory");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto saveToMemoryFcn = Function::createFromHostFunction(jsiRuntime, nameID, 2, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                int fileFormat = arguments[1].asNumber();
                auto arrayBuffer = saveImageDataHOToMemory(runtime, imageDataHostObject, fileFormat);
                return arrayBuffer;
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(saveToMemoryFcn));
        }
    }

    void install_imageDrawer_drawOnImage(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageDrawer_drawOnImage");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto drawOnImageFcn = Function::createFromHostFunction(jsiRuntime, nameID, 4, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto jsQuads = arguments[1].asObject(runtime).asArray(runtime);
                auto color = arguments[2].asNumber();
                auto thickness = arguments[3].asNumber();
                ImageDataHostObject imageHOAfterDraw = drawOnImage(runtime, imageDataHostObject, jsQuads, (int) color, (int) thickness);
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfterDraw));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(drawOnImageFcn));
        }
    }

    void install_imageProcessor_cropImage(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageProcessor_cropImage");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto cropImageFcn = Function::createFromHostFunction(jsiRuntime, nameID, 2, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto jsDsRect = arguments[1].asObject(runtime);
                ImageDataHostObject imageHOAfterCrop = cropImage(runtime, imageDataHostObject, jsDsRect);
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfterCrop));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(cropImageFcn));
        }
    }

    void install_imageProcessor_cropAndDeskewImage(Runtime &jsiRuntime) {
        auto nameID = PropNameID::forAscii(jsiRuntime, "imageProcessor_cropAndDeskewImage");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameID)) {
            auto cropAndDeskewImageFcn = Function::createFromHostFunction(jsiRuntime, nameID, 5, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto jsQuads = arguments[1].asObject(runtime);
                int dstWidth = arguments[2].asNumber();
                int dstHeight = arguments[3].asNumber();
                int padding = arguments[4].asNumber();
                ImageDataHostObject imageHOAfterCrop = cropAndDeskewImage(runtime, imageDataHostObject, jsQuads, dstWidth, dstHeight, padding);
                if (imageHOAfterCrop.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfterCrop));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameID, std::move(cropAndDeskewImageFcn));
        }
    }

    void install_imageProcessor_adjustBrightness(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_adjustBrightness");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto adjustOrFilterFcn = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto value = arguments[1].asNumber();
                ImageDataHostObject imageHOAfter = adjustOrFilter(runtime, imageDataHostObject, (int) value, type_adjust_brightness);
                if (imageHOAfter.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfter));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(adjustOrFilterFcn));
        }
    }

    void install_imageProcessor_adjustContrast(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_adjustContrast");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto adjustOrFilterFcn = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto value = arguments[1].asNumber();
                ImageDataHostObject imageHOAfter = adjustOrFilter(runtime, imageDataHostObject, (int) value, type_adjust_contrast);
                if (imageHOAfter.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfter));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(adjustOrFilterFcn));
        }
    }

    void install_imageProcessor_filterImage(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_filterImage");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto adjustOrFilterFcn = Function::createFromHostFunction(jsiRuntime, nameId, 2, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                auto value = arguments[1].asNumber();
                ImageDataHostObject imageHOAfter = adjustOrFilter(runtime, imageDataHostObject, (int) value, type_filter);
                if (imageHOAfter.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(imageHOAfter));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(adjustOrFilterFcn));
        }
    }

    void install_imageProcessor_convertToGray(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_convertToGray");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto convertToGrayFcn = Function::createFromHostFunction(jsiRuntime, nameId, 4, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                float r = arguments[1].asNumber();
                float g = arguments[2].asNumber();
                float b = arguments[3].asNumber();
                ImageDataHostObject grayImageHO = convertToGray(runtime, imageDataHostObject, r, g, b);
                if (grayImageHO.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(grayImageHO));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(convertToGrayFcn));
        }
    }

    void install_imageProcessor_convertToBinaryGlobal(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_convertToBinaryGlobal");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto convertToBinaryGlobalFcn = Function::createFromHostFunction(jsiRuntime, nameId, 3, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                int threshold = arguments[1].asNumber();
                bool isInvert = arguments[2].asBool();
                ImageDataHostObject binaryImageHO = convertToBinaryGlobal(runtime, imageDataHostObject, threshold, isInvert);
                if (binaryImageHO.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(binaryImageHO));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(convertToBinaryGlobalFcn));
        }
    }

    void install_imageProcessor_convertToBinaryLocal(Runtime &jsiRuntime) {
        PropNameID nameId = PropNameID::forAscii(jsiRuntime, "imageProcessor_convertToBinaryLocal");
        if (!jsiRuntime.global().hasProperty(jsiRuntime, nameId)) {
            auto convertToBinaryLocalFcn = Function::createFromHostFunction(jsiRuntime, nameId, 4, JSI_FUNC {
                auto imageDataObj = arguments[0].asObject(runtime);
                auto imageDataHostObject = ImageDataHostObject::getHOFromObject(runtime, std::move(imageDataObj));
                int blockSize = arguments[1].asNumber();
                int compensation = arguments[2].asNumber();
                bool isInvert = arguments[3].asBool();
                ImageDataHostObject binaryImageHO = convertToBinaryLocal(runtime, imageDataHostObject, blockSize, compensation, isInvert);
                if (binaryImageHO.buffer == nullptr) {
                    return Value::undefined();
                }
                return Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(binaryImageHO));
            });
            jsiRuntime.global().setProperty(jsiRuntime, nameId, std::move(convertToBinaryLocalFcn));
        }
    }
} //namespace jsi_install::utility

void install_imageManager_methods(Runtime &jsiRuntime) {
    jsi_install::utility::install_imageIO_saveToFile(jsiRuntime);
    jsi_install::utility::install_imageIO_readFromFile(jsiRuntime);
    jsi_install::utility::install_imageIO_readFromMemory(jsiRuntime);
    jsi_install::utility::install_imageIO_saveToMemory(jsiRuntime);
    jsi_install::utility::install_imageDrawer_drawOnImage(jsiRuntime);
    jsi_install::utility::install_imageProcessor_cropImage(jsiRuntime);
    jsi_install::utility::install_imageProcessor_cropAndDeskewImage(jsiRuntime);
    jsi_install::utility::install_imageProcessor_adjustBrightness(jsiRuntime);
    jsi_install::utility::install_imageProcessor_adjustContrast(jsiRuntime);
    jsi_install::utility::install_imageProcessor_filterImage(jsiRuntime);
    jsi_install::utility::install_imageProcessor_convertToGray(jsiRuntime);
    jsi_install::utility::install_imageProcessor_convertToBinaryGlobal(jsiRuntime);
    jsi_install::utility::install_imageProcessor_convertToBinaryLocal(jsiRuntime);
}


extern "C"
JNIEXPORT void JNICALL
Java_com_dynamsoft_reactnativelib_utility_ImageManagerModuleImpl_nativeInstall(JNIEnv *env, jobject thiz, jlong jsiPtr) {
    if (!env->IsSameObject(jImageManagerModule, thiz)) {
        jImageManagerModule = env->NewWeakGlobalRef(thiz);
    }
    if (jclzQuad == nullptr) {
        jclzQuad = (jclass) env->NewWeakGlobalRef(env->FindClass("com/dynamsoft/core/basic_structures/Quadrilateral"));
    }
    if (jclzPoint == nullptr) {
        jclzPoint = (jclass) env->NewWeakGlobalRef(env->FindClass("android/graphics/Point"));
    }
    if (jclzDSRect == nullptr) {
        jclzDSRect = (jclass) env->NewWeakGlobalRef(env->FindClass("com/dynamsoft/core/basic_structures/DSRect"));
    }
    auto rt = reinterpret_cast<Runtime *>(jsiPtr);
    install_imageManager_methods(*rt);
}
