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

#include "JsiDbr.h"

namespace jsiConversion::dbr {

    void getUpperAndLowerFromJRange(JNIEnv *env, jobject jRange, float &lower, float &upper) {
        env->PushLocalFrame(16);
        if (jRange == nullptr) {
            lower = 0;
            upper = 0;
            return;
        }
        jclass clsRange = env->FindClass("android/util/Range");
        jmethodID getLower = env->GetMethodID(clsRange, "getLower", "()Ljava/lang/Comparable;");
        jmethodID getUpper = env->GetMethodID(clsRange, "getUpper", "()Ljava/lang/Comparable;");
        jobject jLowerObj = env->CallObjectMethod(jRange, getLower);
        jobject jUpperObj = env->CallObjectMethod(jRange, getUpper);
        jclass clsFloat = env->FindClass("java/lang/Float");
        jmethodID floatValue = env->GetMethodID(clsFloat, "floatValue", "()F");
        lower = env->CallFloatMethod(jLowerObj, floatValue);
        upper = env->CallFloatMethod(jUpperObj, floatValue);
        env->PopLocalFrame(nullptr);
    }


    void jBarcodeResultItemToJsObject(Runtime &rt, JNIEnv *env, jobject jItem, Object &jsItem) {
        env->PushLocalFrame(128);
        jclass clsItem = env->FindClass("com/dynamsoft/dbr/BarcodeResultItem");
        jmethodID getFormat = env->GetMethodID(clsItem, "getFormat", "()J");
        jsItem.setProperty(rt, "format", BigInt::fromInt64(rt, env->CallLongMethod(jItem, getFormat)));

        SET_STRING_PROPERTY_BY_METHOD(clsItem, jItem, jsItem, formatString, getFormatString)

        jmethodID getText = env->GetMethodID(clsItem, "getText", "()Ljava/lang/String;");
        jstring jtext = (jstring) env->CallObjectMethod(jItem, getText);
        if (jtext != nullptr) {
            const jchar *ptext = env->GetStringChars(jtext, nullptr);
            if (ptext != nullptr) {
                jsize textLength = env->GetStringLength(jtext);
                jsItem.setProperty(rt, "text", facebook::jsi::String::createFromUtf16(rt, reinterpret_cast<const char16_t *>(ptext), static_cast<size_t>(textLength)));
                env->ReleaseStringChars(jtext, ptext);
            } else {
                jsItem.setProperty(rt, "text", "");
            }
        } else {
            jsItem.setProperty(rt, "text", "");
        }

        jmethodID getLocation = env->GetMethodID(clsItem, "getLocation", "()Lcom/dynamsoft/core/basic_structures/Quadrilateral;");
        Object jsQuad(rt);
        jsiConversion::core::jQuadToJsObject(rt, env, env->CallObjectMethod(jItem, getLocation), jsQuad);
        jsItem.setProperty(rt, "location", jsQuad);

        jmethodID getConfidence = env->GetMethodID(clsItem, "getConfidence", "()I");
        jsItem.setProperty(rt, "confidence", env->CallIntMethod(jItem, getConfidence));

        jmethodID getAngle = env->GetMethodID(clsItem, "getAngle", "()I");
        jsItem.setProperty(rt, "angle", env->CallIntMethod(jItem, getAngle));

        jmethodID getModuleSize = env->GetMethodID(clsItem, "getModuleSize", "()I");
        jsItem.setProperty(rt, "moduleSize", env->CallIntMethod(jItem, getModuleSize));

        jmethodID isMirrored = env->GetMethodID(clsItem, "isMirrored", "()Z");
        jsItem.setProperty(rt, "isMirrored", (bool) env->CallBooleanMethod(jItem, isMirrored));

        jmethodID isDPM = env->GetMethodID(clsItem, "isDPM", "()Z");
        jsItem.setProperty(rt, "isDPM", (bool) env->CallBooleanMethod(jItem, isDPM));

        jmethodID getBytes = env->GetMethodID(clsItem, "getBytes", "()[B");
        jbyteArray jBytes = (jbyteArray) env->CallObjectMethod(jItem, getBytes);
        if (jBytes != nullptr) {
            jclass clsBase64 = env->FindClass("android/util/Base64");
            jmethodID encodeMethod = env->GetStaticMethodID(clsBase64, "encodeToString", "([BI)Ljava/lang/String;");
            jstring base64Str = (jstring) env->CallStaticObjectMethod(clsBase64, encodeMethod, jBytes, 2);
            if (base64Str) {
                const char *b64 = env->GetStringUTFChars(base64Str, nullptr);
                if (b64) {
                    jsItem.setProperty(rt, "bytes", String::createFromUtf8(rt, b64));
                    env->ReleaseStringUTFChars(base64Str, b64);
                }
                env->DeleteLocalRef(base64Str);
            }
            env->DeleteLocalRef(clsBase64);
        }

        jmethodID getECISegments = env->GetMethodID(clsItem, "getECISegments", "()[Lcom/dynamsoft/dbr/ECISegment;");
        jobjectArray jEciSegments = (jobjectArray) env->CallObjectMethod(jItem, getECISegments);
        if (jEciSegments != nullptr) {
            int eciSegments_len = env->GetArrayLength(jEciSegments);
            Array jsEciSegments(rt, eciSegments_len);
            jclass clsEciSegment = env->FindClass("com/dynamsoft/dbr/ECISegment");
            jfieldID fi_eciValue = env->GetFieldID(clsEciSegment, "eciValue", "I");
            jfieldID fi_startIndex = env->GetFieldID(clsEciSegment, "startIndex", "I");
            jfieldID fi_length = env->GetFieldID(clsEciSegment, "length", "I");
            jfieldID fi_charsetEncoding = env->GetFieldID(clsEciSegment, "charsetEncoding", "Ljava/lang/String;");
            for (int i = 0; i < eciSegments_len; ++i) {
                Object jsEciSegment(rt);
                jobject jEciSegment = env->GetObjectArrayElement(jEciSegments, i);
                jsEciSegment.setProperty(rt, "eciValue", env->GetIntField(jEciSegment, fi_eciValue));
                jsEciSegment.setProperty(rt, "startIndex", env->GetIntField(jEciSegment, fi_startIndex));
                jsEciSegment.setProperty(rt, "length", env->GetIntField(jEciSegment, fi_length));
                auto jCharsetEncoding = (jstring) env->GetObjectField(jEciSegment, fi_charsetEncoding);
                auto pCharsetEncoding = jCharsetEncoding ? env->GetStringUTFChars(jCharsetEncoding, nullptr) : "";
                jsEciSegment.setProperty(rt, "charsetEncoding", pCharsetEncoding);
                if (jCharsetEncoding) {
                    env->ReleaseStringUTFChars(jCharsetEncoding, pCharsetEncoding);
                    env->DeleteLocalRef(jCharsetEncoding);
                }
                jsEciSegments.setValueAtIndex(rt, i, std::move(jsEciSegment));
                env->DeleteLocalRef(jEciSegment);
            }
            jsItem.setProperty(rt, "eciSegments", jsEciSegments);
        }

        jmethodID getDetails = env->GetMethodID(clsItem, "getDetails", "()Lcom/dynamsoft/dbr/BarcodeDetails;");
        jobject jDetails = env->CallObjectMethod(jItem, getDetails);
        if (jDetails != nullptr) {
            jclass clsOneDCodeDetails = env->FindClass("com/dynamsoft/dbr/OneDCodeDetails");
            jclass clsDataMatrixDetails = env->FindClass("com/dynamsoft/dbr/DataMatrixDetails");
            jclass clsAztecDetails = env->FindClass("com/dynamsoft/dbr/AztecDetails");
            jclass clsPDF417Details = env->FindClass("com/dynamsoft/dbr/PDF417Details");
            jclass clsQRCodeDetails = env->FindClass("com/dynamsoft/dbr/QRCodeDetails");
            if (env->IsInstanceOf(jDetails, clsOneDCodeDetails)) {
                Object jsOneDCodeDetails(rt);

                jmethodID getStartPatternRange = env->GetMethodID(clsOneDCodeDetails, "getStartPatternRange", "()Landroid/util/Range;");
                jobject jStartPatternRange = env->CallObjectMethod(jDetails, getStartPatternRange);
                float startPatternRange_lower = 0, startPatternRange_upper = 0;
                getUpperAndLowerFromJRange(env, jStartPatternRange, startPatternRange_lower, startPatternRange_upper);
                jsOneDCodeDetails.setProperty(rt, "startPatternRange_lower", startPatternRange_lower);
                jsOneDCodeDetails.setProperty(rt, "startPatternRange_upper", startPatternRange_upper);

                jmethodID getMiddlePatternRange = env->GetMethodID(clsOneDCodeDetails, "getMiddlePatternRange", "()Landroid/util/Range;");
                jobject jMiddlePatternRange = env->CallObjectMethod(jDetails, getMiddlePatternRange);
                float middlePatternRange_lower = 0, middlePatternRange_upper = 0;
                getUpperAndLowerFromJRange(env, jMiddlePatternRange, middlePatternRange_lower, middlePatternRange_upper);
                jsOneDCodeDetails.setProperty(rt, "middlePatternRange_lower", middlePatternRange_lower);
                jsOneDCodeDetails.setProperty(rt, "middlePatternRange_upper", middlePatternRange_upper);

                jmethodID getEndPatternRange = env->GetMethodID(clsOneDCodeDetails, "getEndPatternRange", "()Landroid/util/Range;");
                jobject jEndPatternRange = env->CallObjectMethod(jDetails, getEndPatternRange);
                float endPatternRange_lower = 0, endPatternRange_upper = 0;
                getUpperAndLowerFromJRange(env, jEndPatternRange, endPatternRange_lower, endPatternRange_upper);
                jsOneDCodeDetails.setProperty(rt, "endPatternRange_lower", endPatternRange_lower);
                jsOneDCodeDetails.setProperty(rt, "endPatternRange_upper", endPatternRange_upper);


                jmethodID getStartCharsBytes = env->GetMethodID(clsOneDCodeDetails, "getStartCharsBytes", "()[B");
                jobject jStartCharsBytes = env->CallObjectMethod(jDetails, getStartCharsBytes);
                if (jStartCharsBytes != nullptr) {
                    int startCharsBytes_len = env->GetArrayLength((jbyteArray) jStartCharsBytes);
                    Array jsStartCharsBytes(rt, startCharsBytes_len);
                    jbyte *jStartCharsByte = env->GetByteArrayElements((jbyteArray) jStartCharsBytes, nullptr);
                    for (int i = 0; i < startCharsBytes_len; i++) {
                        jsStartCharsBytes.setValueAtIndex(rt, i, Value((unsigned char) jStartCharsByte[i]));
                    }
                    env->ReleaseByteArrayElements((jbyteArray) jStartCharsBytes, jStartCharsByte, 0);
                    jsOneDCodeDetails.setProperty(rt, "startCharsBytes", jsStartCharsBytes);
                }

                jmethodID getStopCharsBytes = env->GetMethodID(clsOneDCodeDetails, "getStopCharsBytes", "()[B");
                jobject jStopCharsBytes = env->CallObjectMethod(jDetails, getStopCharsBytes);
                if (jStopCharsBytes != nullptr) {
                    int stopCharsBytes_len = env->GetArrayLength((jbyteArray) jStopCharsBytes);
                    Array jsStopCharsBytes(rt, stopCharsBytes_len);
                    jbyte *jStopCharsByte = env->GetByteArrayElements((jbyteArray) jStopCharsBytes, nullptr);
                    for (int i = 0; i < stopCharsBytes_len; i++) {
                        jsStopCharsBytes.setValueAtIndex(rt, i, Value((unsigned char) jStopCharsByte[i]));
                    }
                    env->ReleaseByteArrayElements((jbyteArray) jStopCharsBytes, jStopCharsByte, 0);
                    jsOneDCodeDetails.setProperty(rt, "stopCharsBytes", jsStopCharsBytes);
                }

                jmethodID getCheckDigitBytes = env->GetMethodID(clsOneDCodeDetails, "getCheckDigitBytes", "()[B");
                jobject jCheckDigitBytes = env->CallObjectMethod(jDetails, getCheckDigitBytes);
                if (jCheckDigitBytes != nullptr) {
                    int checkDigitBytes_len = env->GetArrayLength((jbyteArray) jCheckDigitBytes);
                    Array jsCheckDigitBytes(rt, checkDigitBytes_len);
                    jbyte *jCheckDigitByte = env->GetByteArrayElements((jbyteArray) jCheckDigitBytes, nullptr);
                    for (int i = 0; i < checkDigitBytes_len; i++) {
                        jsCheckDigitBytes.setValueAtIndex(rt, i, Value((unsigned char) jCheckDigitByte[i]));
                    }
                    env->ReleaseByteArrayElements((jbyteArray) jCheckDigitBytes, jCheckDigitByte, 0);
                    jsOneDCodeDetails.setProperty(rt, "checkDigitBytes", jsCheckDigitBytes);
                }

                jsItem.setProperty(rt, "oneDCodeDetails", jsOneDCodeDetails);
            } else if (env->IsInstanceOf(jDetails, clsQRCodeDetails)) {
                Object jsQRCodeDetails(rt);
                jmethodID getRows = env->GetMethodID(clsQRCodeDetails, "getRows", "()I");
                jsQRCodeDetails.setProperty(rt, "rows", env->CallIntMethod(jDetails, getRows));
                jmethodID getColumns = env->GetMethodID(clsQRCodeDetails, "getColumns", "()I");
                jsQRCodeDetails.setProperty(rt, "columns", env->CallIntMethod(jDetails, getColumns));
                jmethodID getErrorCorrectionLevel = env->GetMethodID(clsQRCodeDetails, "getErrorCorrectionLevel", "()I");
                jsQRCodeDetails.setProperty(rt, "errorCorrectionLevel", env->CallIntMethod(jDetails, getErrorCorrectionLevel));
                jmethodID getVersion = env->GetMethodID(clsQRCodeDetails, "getVersion", "()I");
                jsQRCodeDetails.setProperty(rt, "version", env->CallIntMethod(jDetails, getVersion));
                jmethodID getModel = env->GetMethodID(clsQRCodeDetails, "getModel", "()I");
                jsQRCodeDetails.setProperty(rt, "model", env->CallIntMethod(jDetails, getModel));
                jmethodID getMode = env->GetMethodID(clsQRCodeDetails, "getMode", "()I");
                jsQRCodeDetails.setProperty(rt, "mode", env->CallIntMethod(jDetails, getMode));
                jmethodID getPage = env->GetMethodID(clsQRCodeDetails, "getPage", "()I");
                jsQRCodeDetails.setProperty(rt, "page", env->CallIntMethod(jDetails, getPage));
                jmethodID getTotalPage = env->GetMethodID(clsQRCodeDetails, "getTotalPage", "()I");
                jsQRCodeDetails.setProperty(rt, "totalPage", env->CallIntMethod(jDetails, getTotalPage));
                jmethodID getParityData = env->GetMethodID(clsQRCodeDetails, "getParityData", "()B");
                jsQRCodeDetails.setProperty(rt, "parityData", (int) env->CallByteMethod(jDetails, getParityData));
                jmethodID getDataMaskPattern = env->GetMethodID(clsQRCodeDetails, "getDataMaskPattern", "()I");
                jsQRCodeDetails.setProperty(rt, "dataMaskPattern", env->CallIntMethod(jDetails, getDataMaskPattern));
                jmethodID getCodewords = env->GetMethodID(clsQRCodeDetails, "getCodewords", "()[B");
                jbyteArray jCodewords = (jbyteArray) env->CallObjectMethod(jDetails, getCodewords);

                if (jCodewords != nullptr) {
                    int codewords_len = env->GetArrayLength(jCodewords);
                    Array jsCodewords(rt, codewords_len);
                    jbyte *pCodewords = env->GetByteArrayElements(jCodewords, nullptr);
                    for (int i = 0; i < codewords_len; i++) {
                        jsCodewords.setValueAtIndex(rt, i, Value(pCodewords[i]));
                    }
                    env->ReleaseByteArrayElements(jCodewords, pCodewords, 0);
                    jsQRCodeDetails.setProperty(rt, "codewords", jsCodewords);
                }

                jsItem.setProperty(rt, "qrCodeDetails", jsQRCodeDetails);
            } else if (env->IsInstanceOf(jDetails, clsAztecDetails)) {
                Object jsAztecDetails(rt);
                jmethodID getRows = env->GetMethodID(clsAztecDetails, "getRows", "()I");
                jsAztecDetails.setProperty(rt, "rows", env->CallIntMethod(jDetails, getRows));
                jmethodID getColumns = env->GetMethodID(clsAztecDetails, "getColumns", "()I");
                jsAztecDetails.setProperty(rt, "columns", env->CallIntMethod(jDetails, getColumns));
                jmethodID getLayerNumber = env->GetMethodID(clsAztecDetails, "getLayerNumber", "()I");
                jsAztecDetails.setProperty(rt, "layerNumber", env->CallIntMethod(jDetails, getLayerNumber));

                jsItem.setProperty(rt, "aztecDetails", jsAztecDetails);
            } else if (env->IsInstanceOf(jDetails, clsPDF417Details)) {
                Object jsPDF417Details(rt);
                jmethodID getRows = env->GetMethodID(clsPDF417Details, "getRows", "()I");
                jsPDF417Details.setProperty(rt, "rows", env->CallIntMethod(jDetails, getRows));
                jmethodID getColumns = env->GetMethodID(clsPDF417Details, "getColumns", "()I");
                jsPDF417Details.setProperty(rt, "columns", env->CallIntMethod(jDetails, getColumns));
                jmethodID getErrorCorrectionLevel = env->GetMethodID(clsPDF417Details, "getErrorCorrectionLevel", "()I");
                jsPDF417Details.setProperty(rt, "errorCorrectionLevel", env->CallIntMethod(jDetails, getErrorCorrectionLevel));
                jmethodID hasLeftRowIndicator = env->GetMethodID(clsPDF417Details, "hasLeftRowIndicator", "()Z");
                jsPDF417Details.setProperty(rt, "hasLeftRowIndicator", (bool) env->CallBooleanMethod(jDetails, hasLeftRowIndicator));
                jmethodID hasRightRowIndicator = env->GetMethodID(clsPDF417Details, "hasRightRowIndicator", "()Z");
                jsPDF417Details.setProperty(rt, "hasRightRowIndicator", (bool) env->CallBooleanMethod(jDetails, hasRightRowIndicator));

                jmethodID getCodewords = env->GetMethodID(clsPDF417Details, "getCodewords", "()[I");
                jintArray jCodewords = (jintArray) env->CallObjectMethod(jDetails, getCodewords);

                if (jCodewords != nullptr) {
                    int codewords_len = env->GetArrayLength(jCodewords);
                    Array jsCodewords(rt, codewords_len);
                    jint *pCodewords = env->GetIntArrayElements(jCodewords, nullptr);
                    for (int i = 0; i < codewords_len; i++) {
                        jsCodewords.setValueAtIndex(rt, i, Value(pCodewords[i]));
                    }
                    env->ReleaseIntArrayElements(jCodewords, pCodewords, 0);
                    jsPDF417Details.setProperty(rt, "codewords", jsCodewords);
                }

                jsItem.setProperty(rt, "pdf417Details", jsPDF417Details);
            } else if (env->IsInstanceOf(jDetails, clsDataMatrixDetails)) {
                Object jsDataMatrixDetails(rt);
                jmethodID getRows = env->GetMethodID(clsDataMatrixDetails, "getRows", "()I");
                jsDataMatrixDetails.setProperty(rt, "rows", env->CallIntMethod(jDetails, getRows));
                jmethodID getColumns = env->GetMethodID(clsDataMatrixDetails, "getColumns", "()I");
                jsDataMatrixDetails.setProperty(rt, "columns", env->CallIntMethod(jDetails, getColumns));
                jmethodID getDataRegionRows = env->GetMethodID(clsDataMatrixDetails, "getDataRegionRows", "()I");
                jsDataMatrixDetails.setProperty(rt, "dataRegionRows", env->CallIntMethod(jDetails, getDataRegionRows));
                jmethodID getDataRegionColumns = env->GetMethodID(clsDataMatrixDetails, "getDataRegionColumns", "()I");
                jsDataMatrixDetails.setProperty(rt, "dataRegionColumns", env->CallIntMethod(jDetails, getDataRegionColumns));
                jmethodID getDataRegionNumber = env->GetMethodID(clsDataMatrixDetails, "getDataRegionNumber", "()I");
                jsDataMatrixDetails.setProperty(rt, "dataRegionNumber", env->CallIntMethod(jDetails, getDataRegionNumber));
                jsItem.setProperty(rt, "dataMatrixDetails", jsDataMatrixDetails);
            }
        }
        env->PopLocalFrame(nullptr);
    }
}//namespace jsiConversion::dbr
