//
//  Scanbot SDK ReactNative Module
//
//  Copyright (c) 2017 doo GmbH. All rights reserved.
//

#import "RNScanbotSDK.h"
#import "React/RCTLog.h"

#import "SharedConfiguration.h"
#import "InternalScanbotStorageUtils.h"
#import "OCRUtils.h"
#import "HashUtils.h"
#import "SBSDKPolygon+JSON.h"
#import "JSONOptionsUtils.h"
#import "JSONMappings.h"
#import "ObjectMapper.h"
#import "ScanbotSDKConfiguration.h"
#import "OCRCompletionAdapter.h"
#import "BarcodeMapping.h"
#import "RNProxyDelegate.h"

@import ScanbotSDK;
@import ScanbotSDKNativeWrapper;

static BOOL rejectIfUninitialized(RCTPromiseRejectBlock reject) {
    if (![SharedConfiguration isSDKInitialized]) {
        reject(@"error", @"ScanbotSDK is not initialized", nil);
        return TRUE;
    }
    return FALSE;
}

@implementation RNScanbotSDK

RCT_EXPORT_MODULE(ScanbotSDK);

#pragma mark - ReactNative exported interfaces

RCT_EXPORT_METHOD(initializeSDK:(NSDictionary *)options
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    NSDictionary *enums = @{
        @"storageImageFormat": @{
            @"JPG": @(SBSDKImageFileFormatJPEG),
            @"PNG": @(SBSDKImageFileFormatPNG)
        },
        @"cameraPreviewMode": @{
            @"FILL_IN": @(SBSDKVideoContentModeFillIn),
            @"FIT_IN": @(SBSDKVideoContentModeFitIn),
        },
        @"documentDetectorMode": @{
            @"EDGE_BASED": @(SBSDKDocumentDetectorModeStandard),
            @"ML_BASED": @(SBSDKDocumentDetectorModeMachineLearning)
        },
        @"fileEncryptionMode": @{
            @"AES128": @(SBSDKAESEncrypterModeAES128),
            @"AES256": @(SBSDKAESEncrypterModeAES256)
        },
        @"engineMode": @{
            @"NEXT_GEN": @(SBSDKBarcodeEngineModeNextGen),
            @"LEGACY": @(SBSDKBarcodeEngineModeLegacy),
        },
        @"textFilterStrategy": @{
            @"DOCUMENT": @(SBSDKTextFilterStrategyDocument),
            @"LC_DOT_MATRIX_DISPLAY": @(SBSDKTextFilterStrategyLCDotMatrixDisplay)
        },
    };
    
    [ObjectMapper setEnumerationMapping: enums];
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations initializeSDKWithOptions:options delegate:delegate];
    
    // Check if the App has OCR data (ScanbotSDKOCRData.bundle) as resources provided:
    if ([SBSDKResourcesManager canInstallDefaultOCRResources]) {
        // if so, install those:
        [SBSDKResourcesManager installRequiredResourcesOfTypeIfNeeded:SBSDKResourceTypeOCR forceInstall:NO];
    }
    
    [SharedConfiguration defaultConfiguration].isSDKInitialized = YES;
}

RCT_EXPORT_METHOD(getLicenseInfo:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RCTLogInfo(@"Validating ScanbotSDK license...");
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations getLicenseInfoWithDelegate:delegate];
}

RCT_EXPORT_METHOD(applyImageFilter:(NSString *)imageFileUri
                  filter:(NSString *)filterType
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations applyImageFilterWithImageFileUri:imageFileUri filter:filterType delegate:delegate];
}

RCT_EXPORT_METHOD(estimateBlur:(NSDictionary*)args
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations estimateBlurWithArgs:args delegate:delegate];
}

RCT_EXPORT_METHOD(detectDocument:(nonnull NSString *)imageFileUri
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    NSMutableDictionary* configuration = [NSMutableDictionary dictionary];
    [configuration setObject:imageFileUri forKey:@"imageFileUri"];
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations detectDocumentWithConfiguration:configuration delegate:delegate];
}

RCT_EXPORT_METHOD(getOCRConfigs:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations getOCRConfigsWithDelegate:delegate];
}

RCT_EXPORT_METHOD(performOCR:(nonnull NSArray<NSString *> *)imageFileUris
                  languages:(nonnull NSArray<NSString *> *)languages
                  options:(nonnull NSDictionary *)options
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    JSONOptionsUtils *optionsUtils = [[JSONOptionsUtils alloc] initWithOptions:options];
    NSString *outputFormat = [optionsUtils stringValueForOption:@"outputFormat"
                                                   defaultValue:@"PLAIN_TEXT"];
    
    NSDictionary* configuration = @{
        @"imageFileUris": imageFileUris,
        @"languages": languages,
        @"outputFormat": outputFormat
    };
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    
    [SBNWOperations performOCRWithConfiguration: configuration delegate: delegate];
}

RCT_EXPORT_METHOD(createPDF:(NSArray *)imageFileUris
                  pageSize:(NSString *)pageSize
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations createPDFWithImageFileUris:imageFileUris pageSize:pageSize delegate:delegate];
}

RCT_EXPORT_METHOD(writeTIFF:(NSArray *)imageFileUris
                  options:(NSDictionary *)options
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    
    NSMutableDictionary* configuration = [[NSMutableDictionary alloc] initWithDictionary: options];
    [configuration setObject:imageFileUris forKey:@"imageFileUris"];
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations writeTIFFWithConfiguration:configuration delegate:delegate];
}

- (NSArray<UIImage*>*)decryptedImages: (NSArray<NSURL*>*) urls {
    
    NSMutableArray *resultedImages = [[NSMutableArray alloc] init];
    
    SBSDKAESEncrypter* encrypter = [ScanbotSDKUI defaultPDFEncrypter];
    if (encrypter == NULL) {
        return resultedImages;
    }
    
    NSData* tmpData = NULL;
    NSData* decryptedData = NULL;
    UIImage* image = NULL;
    
    for (NSURL* url in urls) {
        tmpData = [[NSData alloc] initWithContentsOfURL:url];
        if (tmpData == NULL) { continue; }
        
        decryptedData = [encrypter decryptData:tmpData];
        if (decryptedData == NULL) { continue; }
        
        image = [[UIImage alloc] initWithData:decryptedData];
        if (image == NULL) { continue; }
        
        [resultedImages addObject:image];
    }
    
    return resultedImages;
}

RCT_EXPORT_METHOD(rotateImage:(NSString *)imageFileUri
                  degrees:(nonnull NSNumber *)degrees
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations rotateImageWithImageFileUri:imageFileUri
                                        degrees:[degrees doubleValue]
                                        quality:SharedConfiguration.defaultConfiguration.sdkConfiguration.storageImageQuality
                                       delegate:delegate];
}


RCT_EXPORT_METHOD(createPage:(NSString*)imageFileUri
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations createPageWithImageFileUri:imageFileUri delegate: delegate];
}

RCT_EXPORT_METHOD(detectDocumentOnPage:(NSDictionary*)pageDict
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations detectDocumentOnPageWithPageDict:pageDict delegate: delegate];
}

RCT_EXPORT_METHOD(applyImageFilterOnPage:(NSDictionary*)pageDict
                  filter:(NSString *)filterType
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations applyImageFilterOnPageWithPageDict:pageDict filter:filterType delegate:delegate];
}

RCT_EXPORT_METHOD(rotatePage:(NSDictionary*)pageDict
                  times:(nonnull NSNumber *)times
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations rotatePageWithPageDict:pageDict
                                     times:[times intValue]
                                  delegate:delegate];
}

RCT_EXPORT_METHOD(getFilteredDocumentPreviewUri:(NSDictionary*)pageDict
                  filter:(NSString *)filterType
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations getFilteredDocumentPreviewUriWithPageDict:pageDict filter:filterType delegate:delegate];
}

RCT_EXPORT_METHOD(removePage:(NSDictionary*)pageDict
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations removePageWithPageDict:pageDict delegate:delegate];
}

RCT_EXPORT_METHOD(setDocumentImage:(NSDictionary*)pageDict
                  imageFileUri:(NSString*)imageFileUri
                  withResolver:(RCTPromiseResolveBlock)resolve
                  withRejecter:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations setDocumentImageWithConfiguration:@{
        @"page": pageDict,
        @"imageFileUri": imageFileUri
    } delegate:delegate];
}

RCT_EXPORT_METHOD(cleanup:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations cleanupWithDelegate:delegate];
}

RCT_EXPORT_METHOD(recognizeMrz:(nonnull NSString *)imageFileUri
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations recognizeCheckOnImageWithConfiguration:@{
        @"imageFileUri": imageFileUri
    } delegate:delegate];
}

- (SBSDKIndexedImageStorage *)tempImageStorageFromFilesList:(NSArray<NSURL *> *)imageFileURLs {
    NSURL *tempStorageDirUrl = [self getRandomTempStorageUrl];
    SBSDKStorageLocation *location = [[SBSDKStorageLocation alloc] initWithBaseURL:tempStorageDirUrl];
    
    // optional image encryptor, can be nil (see SDK initialization)
    SBSDKAESEncrypter *imageEncrypter = [ScanbotSDKUI defaultImageStoreEncrypter];
    
    return [[SBSDKIndexedImageStorage alloc] initWithStorageLocation:location
                                                          fileFormat:SBSDKImageFileFormatJPEG
                                                       withEncrypter:imageEncrypter
                                                 encryptedImagesURLs:imageFileURLs];
}

- (NSURL *)getRandomTempStorageUrl {
    NSString* tempSubFolderName = [[[NSUUID UUID] UUIDString] lowercaseString];
    NSString* pluginStorageDirectoryPath = [InternalScanbotStorageUtils pluginStorageDirectoryPath];
    NSURL *tempStorageDirUrl = [[NSURL alloc] initFileURLWithPath: [NSString stringWithFormat:@"%@/%@", pluginStorageDirectoryPath, tempSubFolderName]];
    return tempStorageDirUrl;
}

- (NSURL *)createRandomTempStorageDirectory {
    NSURL* tempStorageUrl = [self getRandomTempStorageUrl];
    NSFileManager* fileManager = [[NSFileManager alloc] init];
    [fileManager createDirectoryAtPath:[tempStorageUrl path]
           withIntermediateDirectories:true
                            attributes:NULL
                                 error:NULL];
    return tempStorageUrl;
}

- (NSArray*) tryExtractImagesFromPdfOrReject:(NSString*)pdfFilePath
                                 withOptions:(NSDictionary*)options
                              rejectDelegate:(RCTPromiseRejectBlock)rejectDelegate {
    
    const CGFloat kScalingDefaultValue = 2.0;
    const CGFloat kQualityDefaultValue = 90;
    
    if (rejectIfUninitialized(rejectDelegate)) {
        return NULL;
    }
    
    if(!pdfFilePath) {
        [self reportErrorWithMessage:@"PDF could not be loaded, the file path is invalid" reject:rejectDelegate];
        return NULL;
    }
    
    NSURL* pdfFileUrl = [[NSURL alloc] initWithString:pdfFilePath];
    if(!pdfFilePath) {
        [self reportErrorWithMessage:@"Invalid URL for input PDF." reject:rejectDelegate];
        return NULL;
    }
    
    const SBSDKPDFPagesExtractor* extractor = [[SBSDKPDFPagesExtractor alloc] init];
    
    NSURL* outputDirectoryUrl = [self createRandomTempStorageDirectory];
    SBSDKAESEncrypter *pdfEncrypter = [ScanbotSDKUI defaultPDFEncrypter];
    
    NSArray* outputUrls = NULL;
    
    id scalingObj = options ? [options objectForKey:@"scaling"] : NULL;
    id qualityObj = options ? [options objectForKey:@"quality"] : NULL;
    
    if (scalingObj || pdfEncrypter) {
        CGFloat scaling = scalingObj ? [scalingObj floatValue] : kScalingDefaultValue;
        float quality = qualityObj ? ([qualityObj floatValue] / 100.0) : kQualityDefaultValue;
        
        outputUrls = [extractor imageURLsFromPDF:pdfFileUrl
                                         scaling:scaling
                                     compression:quality
                                       encrypter:pdfEncrypter
                                 outputDirectory:outputDirectoryUrl];
    } else if (qualityObj) {
        outputUrls = [extractor imageURLsFromPDF:pdfFileUrl
                                     compression:[qualityObj floatValue] / 100.0
                                 outputDirectory:outputDirectoryUrl];
    } else {
        outputUrls = [extractor imageURLsFromPDF:pdfFileUrl
                                 outputDirectory:outputDirectoryUrl];
    }
    
    if([outputUrls count] == 0) {
        [self reportErrorWithMessage:@"Something went wrong while extracting images from given PDF."
                              reject:rejectDelegate];
        return  NULL;
    }
    
    return outputUrls;
}

RCT_EXPORT_METHOD(detectBarcodesOnImages:(NSDictionary*)configuration
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    NSArray* imageUris = [configuration objectForKey:@"imageFileUris"];
    NSDictionary* nwConfiguration = @{
        @"imageFileUris": imageUris,
    };
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations detectBarcodesOnImagesWithConfiguration:nwConfiguration delegate:delegate];
}

RCT_EXPORT_METHOD(detectBarcodesOnImage:(NSDictionary *)configuration
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    NSString* imageUri = [configuration objectForKey:@"imageFileUri"];
    NSDictionary* nwConfiguration = @{
        @"imageFileUri": imageUri,
    };
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations detectBarcodesOnImageWithConfiguration:nwConfiguration delegate:delegate];
}

RCT_EXPORT_METHOD(extractPagesFromPdf:(NSDictionary*)options
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    NSString* pdfFilePath = [options objectForKey:@"pdfFilePath"];

    NSDictionary* configuration = @{
        @"pdfFilePath": pdfFilePath
    };
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations extractPagesFromPdfWithConfiguration:configuration delegate:delegate];
}

RCT_EXPORT_METHOD(extractImagesFromPdf:(NSDictionary*)options
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    NSString* pdfFilePath = [options objectForKey:@"pdfFilePath"];
    
    NSDictionary* configuration = @{
        @"pdfFilePath": pdfFilePath
    };
    
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations extractImagesFromPdfWithConfiguration:configuration delegate:delegate];
}

RCT_EXPORT_METHOD(refreshImageUris:(NSDictionary *)configuration
                  resolve:(RCTPromiseResolveBlock)resolve
                  reject:(RCTPromiseRejectBlock)reject)
{
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations refreshImageUrisWithConfiguration:configuration delegate:delegate];
}

RCT_EXPORT_METHOD(getImageData:(NSString *)imageFileUri
                  success:(RCTPromiseResolveBlock)resolve
                  failure:(RCTPromiseRejectBlock)reject) {
    RNProxyDelegate* delegate = [[RNProxyDelegate alloc] initWithResolveBlock:resolve andRejectBlock:reject];
    [SBNWOperations getImageDataWithImageFileUri:imageFileUri delegate:delegate];
}

- (void)reportSuccessWithMessage:(NSString *)message resolve:(RCTPromiseResolveBlock)resolve {
    resolve(@{
        @"status": @"OK",
        @"message": message
    });
}

- (void)reportErrorWithMessage:(NSString *)message reject:(RCTPromiseRejectBlock)reject {
    reject(nil, message, nil);
}

void runInBackground(void (^function)(void)) {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), function);
}

@end
