/*
 * Copyright © Dynamsoft Corporation. All rights reserved.
 */

#import "RNDynamsoft+JSI.h"
#import "RNDynamsoft+Json.h"
#import "DSImageData+HostObject.h"
#import "YeetJSIUtils.h"
#import <DynamsoftCaptureVisionBundle/DynamsoftCaptureVisionBundle.h>

NSArray<NSNumber *> *CGAffineTransformToArray(CGAffineTransform transform) {
    return @[@(transform.a), @(transform.b), @(transform.c), @(transform.d), @(transform.tx), @(transform.ty)];
}

@implementation NSData (DSNumber)

- (nullable NSArray<NSNumber *> *)toNumberArray {
    if (!self) return nil;
    
    const uint8_t *bytes = (const uint8_t *)self.bytes;
    NSUInteger length = self.length;
    
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:length];
    for (NSUInteger i = 0; i < length; i++) {
        [result addObject:@(bytes[i])];
    }
    
    return result;
}

@end

@implementation DSECISegment (JSI)

- (nullable NSDictionary *)toJson {
    if (!self) return nil;
    return @{
        @"eciValue" : @(self.eciValue),
        @"charsetEncoding" : self.charsetEncoding,
        @"startIndex" : @(self.startIndex),
        @"length" : @(self.length)
    };
}

@end

@implementation DSCapturedResultItem (JSI)

- (Object)convertToJSIObject:(Runtime& )runtime {
    Object obj = Object(runtime);
    obj.setProperty(runtime, "type", convertNSNumberToJSINumber(runtime, @(self.type)));
    obj.setProperty(runtime, "targetROIDefName", convertNSStringToJSIString(runtime, self.targetROIDefName));
    obj.setProperty(runtime, "taskName", convertNSStringToJSIString(runtime, self.taskName));
    switch (self.type) {
        case DSCapturedResultItemTypeOriginalImage:
            {
                
            }
            break;
        case DSCapturedResultItemTypeBarcode:
            {
                DSBarcodeResultItem *item = (DSBarcodeResultItem *)self;
                obj.setProperty(runtime, "format", BigInt::fromUint64(runtime, item.format));
                obj.setProperty(runtime, "_formatNumberString", convertNSStringToJSIString(runtime, [NSNumber numberWithUnsignedInteger:item.format].stringValue));
                obj.setProperty(runtime, "formatString", convertNSStringToJSIString(runtime, item.formatString));
                obj.setProperty(runtime, "text", convertNSStringToJSIString(runtime, item.text));
                obj.setProperty(runtime, "bytes", convertNSStringToJSIString(runtime, [item.bytes base64EncodedStringWithOptions:0]));
                obj.setProperty(runtime, "location", convertNSDictionaryToJSIObject(runtime, [item.location toJson]));
                obj.setProperty(runtime, "confidence", convertNSNumberToJSINumber(runtime, @(item.confidence)));
                obj.setProperty(runtime, "angle", convertNSNumberToJSINumber(runtime, @(item.angle)));
                obj.setProperty(runtime, "moduleSize", convertNSNumberToJSINumber(runtime, @(item.moduleSize)));
                obj.setProperty(runtime, "isDPM", convertNSNumberToJSIBoolean(runtime, @(item.isDPM)));
                obj.setProperty(runtime, "isMirrored", convertNSNumberToJSIBoolean(runtime, @(item.isMirrored)));
                if (item.details) {
                    if (item.format == DSBarcodeFormatQRCode) {
                        DSQRCodeDetails *qrDetails = (DSQRCodeDetails *)item.details;
                        NSMutableDictionary *qrDict = [NSMutableDictionary dictionaryWithDictionary:@{
                            @"rows": @(qrDetails.rows),
                            @"columns": @(qrDetails.columns),
                            @"errorCorrectionLevel": @(qrDetails.errorCorrectionLevel),
                            @"version": @(qrDetails.version),
                            @"model": @(qrDetails.model),
                            @"mode": @(qrDetails.mode),
                            @"page": @(qrDetails.page),
                            @"totalPage": @(qrDetails.totalPage),
                            @"parityData": @(qrDetails.parityData),
                            @"dataMaskPattern": @(qrDetails.dataMaskPattern)
                        }];
                        
                        if (qrDetails.codewords) {
                            qrDict[@"codewords"] = [qrDetails.codewords toNumberArray];
                        }
                        obj.setProperty(runtime, "qrCodeDetails", convertNSDictionaryToJSIObject(runtime, [qrDict copy]));
                    } else if (item.format == DSBarcodeFormatAztec) {
                        DSAztecDetails *aztecDetails = (DSAztecDetails *)item.details;
                        NSDictionary *aztecDict = @{
                            @"rows": @(aztecDetails.rows),
                            @"columns": @(aztecDetails.columns),
                            @"layerNumber": @(aztecDetails.layerNumber)
                        };
                        obj.setProperty(runtime, "aztecDetails", convertNSDictionaryToJSIObject(runtime, [aztecDict copy]));
                    } else if (item.format == DSBarcodeFormatDataMatrix) {
                        DSDataMatrixDetails *dmDetails = (DSDataMatrixDetails *)item.details;
                        NSDictionary *dmDict = @{
                            @"rows": @(dmDetails.rows),
                            @"columns": @(dmDetails.columns),
                            @"dataRegionRows": @(dmDetails.dataRegionRows),
                            @"dataRegionColumns": @(dmDetails.dataRegionColumns),
                            @"dataRegionNumber": @(dmDetails.dataRegionNumber)
                        };
                        obj.setProperty(runtime, "dataMatrixDetails", convertNSDictionaryToJSIObject(runtime, [dmDict copy]));
                    } else if ((item.format & DSBarcodeFormatOneD) != 0) {
                        DSOneDCodeDetails *onedDetails = (DSOneDCodeDetails *)item.details;
                        NSMutableDictionary *onedDict = [NSMutableDictionary dictionary];
                        
                        if (onedDetails.startCharsBytes) {
                            onedDict[@"startCharsBytes"] = [onedDetails.startCharsBytes toNumberArray];
                        }
                        if (onedDetails.stopCharsBytes) {
                            onedDict[@"stopCharsBytes"] = [onedDetails.stopCharsBytes toNumberArray];
                        }
                        if (onedDetails.checkDigitBytes) {
                            onedDict[@"checkDigitBytes"] = [onedDetails.checkDigitBytes toNumberArray];
                        }
                        
                        onedDict[@"startPatternRange_lower"] = @(onedDetails.startPatternRange.minimum);
                        onedDict[@"middlePatternRange_lower"] = @(onedDetails.middlePatternRange.minimum);
                        onedDict[@"endPatternRange_lower"] = @(onedDetails.endPatternRange.minimum);
                        onedDict[@"startPatternRange_upper"] = @(onedDetails.startPatternRange.maximum);
                        onedDict[@"middlePatternRange_upper"] = @(onedDetails.middlePatternRange.maximum);
                        onedDict[@"endPatternRange_upper"] = @(onedDetails.endPatternRange.maximum);
                        
                        obj.setProperty(runtime, "oneDCodeDetails", convertNSDictionaryToJSIObject(runtime, [onedDict copy]));
                    } else if (item.format == DSBarcodeFormatPDF417) {
                        DSPDF417Details *pdf417Details = (DSPDF417Details *)item.details;
                        NSMutableDictionary *pdf417Dict = [NSMutableDictionary dictionaryWithDictionary:@{
                            @"rows": @(pdf417Details.rows),
                            @"columns": @(pdf417Details.columns),
                            @"errorCorrectionLevel": @(pdf417Details.errorCorrectionLevel),
                            @"hasLeftRowIndicator": @(pdf417Details.hasLeftRowIndicator),
                            @"hasRightRowIndicator": @(pdf417Details.hasRightRowIndicator)
                        }];
                        
                        if (pdf417Details.codewords) {
                            pdf417Dict[@"codewords"] = pdf417Details.codewords;
                        }
                        
                        obj.setProperty(runtime, "pdf417Details", convertNSDictionaryToJSIObject(runtime, [pdf417Dict copy]));
                    }
                }
                NSArray<DSECISegment *> *eciSegments = item.eciSegments;
                if (eciSegments && eciSegments.count > 0) {
                    NSMutableArray<NSDictionary *> *mutableArray = [NSMutableArray arrayWithCapacity:eciSegments.count];
                    for (DSECISegment *eci in eciSegments) {
                        [mutableArray addObject:[eci toJson]];
                    }
                    obj.setProperty(runtime, "eciSegments", convertNSArrayToJSIArray(runtime, [mutableArray copy]));
                } else {
                    obj.setProperty(runtime, "eciSegments", jsi::Value::null());
                }
            }
            break;
        case DSCapturedResultItemTypeTextLine:
            {
                DSTextLineResultItem *item = (DSTextLineResultItem *)self;
                obj.setProperty(runtime, "text", convertNSStringToJSIString(runtime, item.text));
                obj.setProperty(runtime, "rawText", convertNSStringToJSIString(runtime, item.rawText));
                obj.setProperty(runtime, "location", convertNSDictionaryToJSIObject(runtime, [item.location toJson]));
                obj.setProperty(runtime, "confidence",convertNSNumberToJSINumber(runtime, @(item.confidence)));
                NSUInteger count = item.charResult.count;
                if (count > 0) {
                    jsi::Array array = jsi::Array(runtime, count);
                    for (size_t i = 0; i < count; i++) {
                        array.setValueAtIndex(runtime, i, convertNSDictionaryToJSIObject(runtime, [[item.charResult objectAtIndex:i] toJson]));
                    }
                    obj.setProperty(runtime, "characterResults", array);
                } else {
                    obj.setProperty(runtime, "characterResults", jsi::Value::null());
                }
                obj.setProperty(runtime, "specificationName", convertNSStringToJSIString(runtime, item.specificationName));
            }
            break;
        case DSCapturedResultItemTypeDetectedQuad:
            {
                DSDetectedQuadResultItem *item = (DSDetectedQuadResultItem *)self;
                obj.setProperty(runtime, "location", convertNSDictionaryToJSIObject(runtime, [item.location toJson]));
                obj.setProperty(runtime, "confidenceAsDocumentBoundary",convertNSNumberToJSINumber(runtime, @(item.confidenceAsDocumentBoundary)));
                obj.setProperty(runtime, "crossVerificationStatus", convertNSNumberToJSINumber(runtime, @(item.crossVerificationStatus)));
            }
            break;
        case DSCapturedResultItemTypeDeskewedImage:
            {
                DSDeskewedImageResultItem *item = (DSDeskewedImageResultItem *)self;
                if (item.imageData) {
                    ImageDataHostObject hostObject = [item.imageData hostObject];
                    Object object = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(hostObject));
                    obj.setProperty(runtime, "imageData", object);
                } else {
                    obj.setProperty(runtime, "imageData", jsi::Value::null());
                }
                obj.setProperty(runtime, "sourceDeskewQuad", convertNSDictionaryToJSIObject(runtime, [item.sourceDeskewQuad toJson]));
                obj.setProperty(runtime, "crossVerificationStatus", convertNSNumberToJSINumber(runtime, @(item.crossVerificationStatus)));
                obj.setProperty(runtime, "originalToLocalMatrix", convertNSArrayToJSIArray(runtime, CGAffineTransformToArray(item.originalToLocalMatrix)));
            }
            break;
        case DSCapturedResultItemTypeEnhancedImage:
        {
            DSEnhancedImageResultItem *item = (DSEnhancedImageResultItem *)self;
            if (item.imageData) {
                ImageDataHostObject hostObject = [item.imageData hostObject];
                Object object = Object::createFromHostObject(runtime, std::make_shared<ImageDataHostObject>(hostObject));
                obj.setProperty(runtime, "imageData", object);
            } else {
                obj.setProperty(runtime, "imageData", jsi::Value::null());
            }
            obj.setProperty(runtime, "originalToLocalMatrix", convertNSArrayToJSIArray(runtime, CGAffineTransformToArray(item.originalToLocalMatrix)));
            break;
        }
        case DSCapturedResultItemTypeParsedResult:
            {
                DSParsedResultItem *item = (DSParsedResultItem *)self;
                NSDictionary<NSString *, NSString *> *parsedFields = item.parsedFields;
                NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:parsedFields];
                for (NSString *key in [parsedFields allKeys]) {
                    [mutableDictionary setValue:@{ @"value":[parsedFields valueForKey:key],
                                                   @"mappingStatus":@([item getFieldMappingStatus:key]),
                                                   @"validationStatus":@([item getFieldValidationStatus:key]) } forKey:key];
                }
                obj.setProperty(runtime, "parsedFields", convertNSDictionaryToJSIObject(runtime, [mutableDictionary copy]));
                obj.setProperty(runtime, "jsonString", convertNSStringToJSIString(runtime, item.jsonString));
                obj.setProperty(runtime, "codeType", convertNSStringToJSIString(runtime, item.codeType));
            }
            break;
    }
    return obj;
}

@end

@implementation DSCapturedResult (JSI)

- (Object)convertToJSIObject:(Runtime& )runtime {
    Object obj = Object(runtime);
    obj.setProperty(runtime, "originalImageHashId", convertNSStringToJSIString(runtime, self.originalImageHashId));
    obj.setProperty(runtime, "errorMessage", convertNSStringToJSIString(runtime, self.errorMessage));
    obj.setProperty(runtime, "errorCode", convertNSNumberToJSINumber(runtime, @(self.errorCode)));
    obj.setProperty(runtime, "rotationTransformMatrix", convertNSArrayToJSIArray(runtime, CGAffineTransformToArray(self.rotationTransformMatrix)));
    int count = (int)self.items.count;
    if (count > 0) {
        jsi::Array array = jsi::Array(runtime, count);
        for (size_t i = 0; i < count; i++) {
            array.setValueAtIndex(runtime, i, [[self.items objectAtIndex:i] convertToJSIObject:runtime]);
        }
        obj.setProperty(runtime, "items", array);
    } else {
        obj.setProperty(runtime, "items", jsi::Value::null());
    }
    return obj;
}

@end

DSQuadrilateral * convertJSIValueToDSQuadrilateral(
                               jsi::Runtime &runtime,
                               const jsi::Object &value)
{
    if (value.hasProperty(runtime, "points") && value.getProperty(runtime, "points").isObject() && value.getProperty(runtime, "points").getObject(runtime).isArray(runtime)) {
        auto jsiPoints = value.getProperty(runtime, "points").getObject(runtime).getArray(runtime);
        size_t size = jsiPoints.size(runtime);
        NSMutableArray *points = [NSMutableArray array];
        for (size_t i = 0; i < size; i++) {
            auto jsiPoint =  jsiPoints.getValueAtIndex(runtime, i).getObject(runtime);
            if (jsiPoint.hasProperty(runtime, "x") && jsiPoint.hasProperty(runtime, "y")) {
                double x = jsiPoint.getProperty(runtime, "x").getNumber();
                double y = jsiPoint.getProperty(runtime, "y").getNumber();
                CGPoint point = CGPointMake(x, y);
                [points addObject:[NSValue valueWithCGPoint:point]];
            }
        }
        if (points.count == 4) {
            return [[DSQuadrilateral alloc] initWithPointArray:[points copy]];
        }
    }
    return nil;
}

DSRect * convertJSIValueToDSRect(
                                 jsi::Runtime &runtime,
                                 const jsi::Object &value)
{
    if (value.hasProperty(runtime, "top") &&
        value.hasProperty(runtime, "bottom") &&
        value.hasProperty(runtime, "right") &&
        value.hasProperty(runtime, "left") &&
        value.hasProperty(runtime, "measuredInPercentage")) {
        double top = value.getProperty(runtime, "top").getNumber();
        double bottom = value.getProperty(runtime, "bottom").getNumber();
        double right = value.getProperty(runtime, "right").getNumber();
        double left = value.getProperty(runtime, "left").getNumber();
        BOOL measuredInPercentage = value.getProperty(runtime, "measuredInPercentage").getBool();
        return [[DSRect alloc] initWithLeft:left top:top right:right bottom:bottom measuredInPercentage:measuredInPercentage];
    }
    return nil;
}
