import Foundation
import DocutainSdk

@objc(DocutainSDKCordova)
class DocutainSDKCordova : CDVPlugin{

    private var scanCommand : CDVInvokedUrlCommand? = nil
    private var photoPaymentCommand : CDVInvokedUrlCommand? = nil
    
    @objc func initSDK(_ command: CDVInvokedUrlCommand){
        guard let licenseKey = command.arguments[0] as? String else{
            sendResultError(command, errorMessage: "No license key provided.")
            return
        }
        if(!DocutainSDK.initSDK(licenseKey: licenseKey)){
            sendResultError(command, errorMessage: DocutainSDK.getLastError())
        } else{
            sendResultOK(command)
        }
    }

    @objc func setAnalyzeConfiguration(_ command: CDVInvokedUrlCommand) {
        //only available for iOS >= 13
        if #available(iOS 13, *){
            let analyzeConfig = AnalyzeConfiguration()
            mapAnalyzeConfiguration(command: command, analyzeConfig: analyzeConfig)
            if(!DocumentDataReader.setAnalyzeConfiguration(analyzeConfiguration: analyzeConfig)){
                sendResultError(command, errorMessage: DocutainSDK.getLastError())
            } else{
                sendResultOK(command)
            }
        } else{
            sendResultError(command, errorMessage: "Not available in iOS 12 or earlier.")
        }
    }

    @objc func scanDocument(_ command: CDVInvokedUrlCommand) {
        let scanConfig = DocumentScannerConfiguration()
        if !mapDocumentScannerConfiguration(config: command.arguments[0] as! [String:Any], scanConfig: scanConfig, needToMigrate: true){
            cancelDocumentScanDueToInvalidConfig(result: command)
            return
        }
        scanCommand = command
        //cordova runs async, so we need to dispatch on ui thread
        DispatchQueue.main.async {
            UI.scanDocument(scanDelegate: self, scanConfig: scanConfig)
        }
    }

    @objc func loadFile(_ command: CDVInvokedUrlCommand) {
        //only available for iOS >= 13
        if #available(iOS 13, *){
            guard let path = command.arguments[0] as? String else{
                sendResultError(command, errorMessage: "No filepath provided.")
                return
            }
            if let url = URL(string: path){
                if(!DocumentDataReader.loadFile(fileUrl: url as URL)){
                    sendResultError(command, errorMessage: DocutainSDK.getLastError())
                } else{
                    sendResultOK(command)
                }
            } else{
                sendResultError(command, errorMessage: "Invalid filepath provided.")
            }
        } else{
            sendResultError(command, errorMessage: "Not available in iOS 12 or earlier.")
        }
    }
    
    @objc func writePDF(_ command: CDVInvokedUrlCommand){
        let pageFormat = command.arguments[0] as? String
        var fileUri = command.arguments[1] as? String
        var overWrite = command.arguments[2] as? Bool ?? true
        
        if(fileUri == nil || fileUri!.isEmpty){
            overWrite = true
            let appDir = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
            fileUri = appDir[0] + "/Docutain/Temp/Docutain.pdf";
        }

        if let url = URL(string: fileUri!){
            if let returnURL = Document.writePDF(fileUrl: url.deletingLastPathComponent(), fileName: url.lastPathComponent, overwrite: overWrite, pageFormat: getPDFPageFormatFromString(pageFormat: pageFormat)){
                sendResultOK(command, message: returnURL.absoluteString)
            } else{
                sendResultError(command, errorMessage: DocutainSDK.getLastError())
            }
        } else{
            sendResultError(command, errorMessage: "invalid path provided")
        }
    }
    
    @objc func writeImage(_ command: CDVInvokedUrlCommand){
        guard let pageNumber = command.arguments[0] as? Int else {
            sendResultError(command, errorMessage: "pageNumber not provided")
            return
        }
        var fileUri = command.arguments[1] as? String
        if(fileUri == nil || fileUri!.isEmpty){
            let appDir = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
            fileUri = appDir[0] + "/Docutain/Temp/Docutain_\(pageNumber).jpg"
        }
        if let url = URL(string: fileUri!){
            if let returnURL = Document.writeImage(page: pageNumber, fileUrl: url){
                sendResultOK(command, message: returnURL.absoluteString)
            } else{
                sendResultError(command, errorMessage: DocutainSDK.getLastError())
            }
        } else{
            sendResultError(command, errorMessage: "invalid path provided")
        }
    }
    
    @objc func getImageBytes(_ command: CDVInvokedUrlCommand){
        guard let pageNumber = command.arguments[0] as? Int else {
            sendResultError(command, errorMessage: "pageNumber not provided")
            return
        }
        let pageSourceType = command.arguments[0] as? String
        if let data = Document.getImage(page: pageNumber, pageSourceType: getPageSourceTypeFromString(pageSourceType: pageSourceType)){
            sendResultOK(command, message: data.base64EncodedString())
        } else{
            sendResultError(command, errorMessage: DocutainSDK.getLastError())
        }
    }

    @objc func getText(_ command: CDVInvokedUrlCommand){
        //only available for iOS >= 13
        if #available(iOS 13, *){
            sendResultOK(command, message: DocumentDataReader.getText())
        } else{
            sendResultError(command, errorMessage: "Not available in iOS 12 or earlier.")
        }
    }

    @objc func getTextPage(_ command: CDVInvokedUrlCommand){
        //only available for iOS >= 13
        if #available(iOS 13, *){
            guard let pageNumber = command.arguments[0] as? Int else {
                sendResultError(command, errorMessage: "pageNumber not provided")
                return
            }
            sendResultOK(command, message: DocumentDataReader.getText(pageNumber: Int32(pageNumber)))
        } else{
            sendResultError(command, errorMessage: "Not available in iOS 12 or earlier.")
        }
    }

    @objc func analyze(_ command: CDVInvokedUrlCommand){
        //only available for iOS >= 13
        if #available(iOS 13, *){
            sendResultOK(command, message: DocumentDataReader.analyze())
        } else{
            sendResultError(command, errorMessage: "Not available in iOS 12 or earlier.")
        }
    }

    @objc func setLogLevel(_ command: CDVInvokedUrlCommand){
        guard let logLevel = command.arguments[0] as? String else {
            sendResultError(command, errorMessage: "logLevel not provided")
            return
        }
        Logger.setLogLevel(level: getLogLevelFromString(loglevel: logLevel))
        sendResultOK(command)
    }

    @objc func getTraceFile(_ command: CDVInvokedUrlCommand){
        sendResultOK(command, message: URL(fileURLWithPath: Logger.getTraceFile()).absoluteString)
    }

    @objc func deleteTempFiles(_ command: CDVInvokedUrlCommand){
        let deleteTraceFileContent = command.arguments[0] as? Bool ?? false
        if(DocutainSDK.deleteTempFiles(deleteTraceFileContent:deleteTraceFileContent)){
            sendResultOK(command)
        }
        else{
            sendResultError(command, errorMessage: DocutainSDK.getLastError())
        }
    }

    @objc func pageCount(_ command: CDVInvokedUrlCommand){
        sendResultOK(command, message: Document.pageCount())
    }

    private func mapAnalyzeConfiguration(command: CDVInvokedUrlCommand, analyzeConfig: AnalyzeConfiguration) -> Void{
        guard let data = command.arguments[0] as? NSObject else{
            sendResultError(command, errorMessage: "Config not provided.")
            return
        }
        if let readBIC = data.value(forKey:"readBIC") as? Bool{
            analyzeConfig.readBIC = readBIC
        }
        if let readPaymentState = data.value(forKey:"readPaymentState") as? Bool{
            analyzeConfig.readPaymentState = readPaymentState
        }
        if let readSEPACreditor = data.value(forKey:"readSEPACreditor") as? Bool{
            analyzeConfig.readSEPACreditor = readSEPACreditor
        }
    }

    private func setButtonConfig(docutainButton: DocutainButton, docutainButtonEntry: [String: Any]) -> Void{
        if let buttonTitle = docutainButtonEntry["title"] as? String{
            docutainButton.title = buttonTitle
        }
        if let buttonIcon = docutainButtonEntry["icon"] as? String{
            docutainButton.icon = buttonIcon
        }
    }


    private func mapDocumentScannerConfiguration(config: [String: Any], scanConfig: BaseScannerConfiguration, needToMigrate: Bool = false) -> Bool {
        if let allowCaptureModeSetting = config["allowCaptureModeSetting"] as? Bool {
            scanConfig.allowCaptureModeSetting = allowCaptureModeSetting
        }
        if let autoCapture = config["autoCapture"] as? Bool {
            scanConfig.autoCapture = autoCapture
        }
        if let defaultScanFilter = config["defaultScanFilter"] as? String {
            scanConfig.defaultScanFilter = getScanFilterFromString(
                scanFilter: defaultScanFilter
            )
        }
        if let source = config["source"] as? String {
            scanConfig.source = getScanSourceFromString(scanSource: source)
        }
        if let sourceImages = config["sourceImages"] as? [String] {
            var images: [URL] = []
            for image in sourceImages {
                // needs to be an absolut path to the file
                let path = image.replacingOccurrences(of: "file://", with: "")
                images.append(URL(fileURLWithPath: path))
            }
            scanConfig.sourceImages = images
        }
        if let autoCrop = config["autoCrop"] as? Bool {
            scanConfig.autoCrop = autoCrop
        }
        if let multiPage = config["multiPage"] as? Bool {
            scanConfig.multiPage = multiPage
        }
        if let pageEditConfig = config["pageEditConfig"] as? [String: Any] {
            if let allowPageFilter = pageEditConfig["allowPageFilter"] as? Bool {
                scanConfig.pageEditConfig.allowPageFilter = allowPageFilter
            }
            if let allowPageRotation = pageEditConfig["allowPageRotation"] as? Bool {
                scanConfig.pageEditConfig.allowPageRotation = allowPageRotation
            }
            if let allowPageArrangement = pageEditConfig["allowPageArrangement"] as? Bool {
                scanConfig.pageEditConfig.allowPageArrangement = allowPageArrangement
            }
            if let allowPageCropping = pageEditConfig["allowPageCropping"] as? Bool {
                scanConfig.pageEditConfig.allowPageCropping = allowPageCropping
            }
            if let allowPageRetake = pageEditConfig["allowPageRetake"] as? Bool {
                scanConfig.pageEditConfig.allowPageRetake = allowPageRetake
            }
            if let allowPageAdd = pageEditConfig["allowPageAdd"] as? Bool {
                scanConfig.pageEditConfig.allowPageAdd = allowPageAdd
            }
            if let allowPageDeletion = pageEditConfig["allowPageDeletion"] as? Bool {
                scanConfig.pageEditConfig.allowPageDeletion = allowPageDeletion
            }
            if let pageArrangementShowDeleteButton = pageEditConfig["pageArrangementShowDeleteButton"] as? Bool {
                scanConfig.pageEditConfig.pageArrangementShowDeleteButton = pageArrangementShowDeleteButton
            }
            if let pageArrangementShowPageNumber = pageEditConfig["pageArrangementShowPageNumber"] as? Bool {
                scanConfig.pageEditConfig.pageArrangementShowPageNumber = pageArrangementShowPageNumber
            }
        }
        if let colorConfig = config["ColorConfig"] as? [String: Any] ?? config["colorConfig"] as? [String: Any]{
            for (key, value) in colorConfig {
                if let color = value as? [String: Any] {
                    if let colorLight = color["Light"] as? String, let colorDark = color["Dark"] as? String {
                        let lightColor = UIColor.init(hexaString: colorLight)
                        let darkColor = UIColor.init(hexaString: colorDark)
                        switch key.uppercased() {
                        case "COLORPRIMARY":
                            scanConfig.colorConfig.setColorPrimary(light: lightColor, dark: darkColor)
                        case "COLORONPRIMARY":
                            scanConfig.colorConfig.setColorOnPrimary(light: lightColor, dark: darkColor)
                        case "COLORSECONDARY":
                            scanConfig.colorConfig.setColorSecondary(light: lightColor, dark: darkColor)
                        case "COLORONSECONDARY":
                            scanConfig.colorConfig.setColorOnSecondary(light: lightColor, dark: darkColor)
                        case "COLORSCANBUTTONSLAYOUTBACKGROUND":
                            scanConfig.colorConfig.setColorScanButtonsLayoutBackground(light: lightColor, dark: darkColor)
                        case "COLORSCANBUTTONSFOREGROUND":
                            scanConfig.colorConfig.setColorScanButtonsForeground(light: lightColor, dark: darkColor)
                        case "COLORSCANPOLYGON":
                            scanConfig.colorConfig.setColorScanPolygon(light: lightColor, dark: darkColor)
                        case "COLORBOTTOMBARBACKGROUND":
                            scanConfig.colorConfig.setColorBottomBarBackground(light: lightColor, dark: darkColor)
                        case "COLORBOTTOMBARFOREGROUND":
                            scanConfig.colorConfig.setColorBottomBarForeground(light: lightColor, dark: darkColor)
                        case "COLORTOPBARBACKGROUND":
                            scanConfig.colorConfig.setColorTopBarBackground(light: lightColor, dark: darkColor)
                        case "COLORTOPBARFOREGROUND":
                            scanConfig.colorConfig.setColorTopBarForeground(light: lightColor, dark: darkColor)
                        case "COLORTOPBARTITLE":
                            scanConfig.colorConfig.setColorTopBarTitle(light: lightColor, dark: darkColor)
                        default:
                            print("mapDocumentScannerConfiguration color not valid " + key)
                        }
                    }
                }
            }
        }

        if let textConfig = config["textConfig"] as? [String: Any] {
            if let textSizeBottomToolbar = textConfig["textSizeBottomToolbar"] as? Float {
                scanConfig.textConfig.textSizeBottomToolbar = (
                    textSizeBottomToolbar
                ) as NSNumber
            }
            if let textSizeTopToolbar = textConfig["textSizeTopToolbar"] as? Float {
                scanConfig.textConfig.textSizeTopToolbar = (
                    textSizeTopToolbar
                ) as NSNumber
            }
            if let textSizeScanButtons = textConfig["textSizeScanButtons"] as? Float {
                scanConfig.textConfig.textSizeScanButtons = (
                    textSizeScanButtons
                ) as NSNumber
            }
            if let textSizeTitle = textConfig["textSizeTitle"] as? Float {
                scanConfig.textConfig.textSizeTitle = (
                    textSizeTitle
                ) as NSNumber
            }
            if let textTitleScanPage = textConfig["textTitleScanPage"] as? String {
                scanConfig.textConfig.textTitleScanPage = textTitleScanPage
            }
            if let textTitleEditPage = textConfig["textTitleEditPage"] as? String {
                scanConfig.textConfig.textTitleEditPage = textTitleEditPage
            }
            if let textTitleFilterPage = textConfig["textTitleFilterPage"] as? String {
                scanConfig.textConfig.textTitleFilterPage = textTitleFilterPage
            }
            if let textTitleCroppingPage = textConfig["textTitleCroppingPage"] as? String {
                scanConfig.textConfig.textTitleCroppingPage = textTitleCroppingPage
            }
            if let textTitleArrangementPage = textConfig["textTitleArrangementPage"] as? String {
                scanConfig.textConfig.textTitleArrangementPage = textTitleArrangementPage
            }
            if let textTitleConfirmationPage = textConfig["textTitleConfirmationPage"] as? String {
                scanConfig.textConfig.textTitleConfirmationPage = textTitleConfirmationPage
            }
            if let textDocumentTitle = textConfig["textDocumentTitle"] as? String {
                scanConfig.textConfig.textDocumentTitle = textDocumentTitle
            }
            if let textFocusHint  = textConfig["textFocusHint"] as? String {
                scanConfig.textConfig.textFocusHint = textFocusHint
            }
            if let textFirstPageHint  = textConfig["textFirstPageHint"] as? String {
                scanConfig.textConfig.textFirstPageHint = textFirstPageHint
            }
            if let textLastPageHint  = textConfig["textLastPageHint"] as? String {
                scanConfig.textConfig.textLastPageHint = textLastPageHint
            }
            if let textOnePageHint  = textConfig["textOnePageHint"] as? String {
                scanConfig.textConfig.textOnePageHint = textOnePageHint
            }
            if let textScanProgress  = textConfig["textScanProgress"] as? String {
                scanConfig.textConfig.textScanProgress = textScanProgress
            }
            if let textDeleteDialogCurrentPage  = textConfig["textDeleteDialogCurrentPage"] as? String {
                scanConfig.textConfig.textDeleteDialogCurrentPage = textDeleteDialogCurrentPage
            }
            if let textDeleteDialogAllPages  = textConfig["textDeleteDialogAllPages"] as? String {
                scanConfig.textConfig.textDeleteDialogAllPages = textDeleteDialogAllPages
            }
            if let textDeleteDialogCancel  = textConfig["textDeleteDialogCancel"] as? String {
                scanConfig.textConfig.textDeleteDialogCancel = textDeleteDialogCancel
            }
            if let textTitleScanTipsPage  = textConfig["textTitleScanTipsPage"] as? String {
                scanConfig.textConfig.textTitleScanTipsPage = textTitleScanTipsPage
            }
        }

        if let buttonConfig = config["buttonConfig"] as? [String: Any] {
            for (key, value) in buttonConfig {
                if let docutainButtonEntry = value as? [String: Any] {
                    switch key.uppercased() {
                    case "BUTTONEDITROTATE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditRotate,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITCROP": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditCrop,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITFILTER": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditFilter,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITARRANGE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditArrange,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITRETAKE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditRetake,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITDELETE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditDelete,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITFINISH": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditFinish,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONCROPEXPAND": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonCropExpand,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONCROPSNAP": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonCropSnap,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONCROPFINISH": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonCropFinish,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANAUTOCAPTUREON": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanAutoCaptureOn,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANAUTOCAPTUREOFF": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanAutoCaptureOff,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANTORCH": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanTorch,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANCAPTURE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanCapture,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANFINISH": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanFinish,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONCONFIRMATIONFINISH": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonConfirmationFinish,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONEDITADDPAGE": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonEditAddPage,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANIMPORT": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanImport,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    case "BUTTONSCANCANCEL": setButtonConfig(
                        docutainButton: scanConfig.buttonConfig.buttonScanCancel,
                        docutainButtonEntry: docutainButtonEntry
                    )
                    default:
                        print(
                            "mapDocumentScannerConfiguration button not valid " + key
                        )
                    }
                }
            }
        }
        if let confirmPages = config["confirmPages"] as? Bool {
            scanConfig.confirmPages = confirmPages
        }

        if let allowPageEditing = config["allowPageEditing"] as? Bool {
            scanConfig.allowPageEditing = allowPageEditing
        }

        if let vibrateOnCapture = config["vibrateOnCapture"] as? Bool {
            scanConfig.vibrateOnCapture = vibrateOnCapture
        }

        if let optionsOnboarding = config["onboarding"] as? [String: Any] {
            var onboarding: Onboarding? = scanConfig.onboarding
            if onboarding == nil {
                onboarding = Onboarding()
                scanConfig.onboarding = onboarding
            }
            readButtonConfig(
                key: "buttonNext",
                docutainButton: onboarding!.buttonNext,
                entry: optionsOnboarding
            )
            readButtonConfig(
                key: "buttonFinish",
                docutainButton: onboarding!.buttonFinish,
                entry: optionsOnboarding
            )
            readButtonConfig(
                key: "buttonSkip",
                docutainButton: onboarding!.buttonSkip,
                entry: optionsOnboarding
            )
            if let buttonBack = optionsOnboarding["buttonBack"] {
                onboarding!.buttonBack = DocutainButton()
                readButtonConfig(
                    key: "buttonBack",
                    docutainButton: onboarding!.buttonBack!,
                    entry: optionsOnboarding
                )
            }
            if let optionsScanHintPopup = optionsOnboarding["scanHintPopup"] as? [String: Any] {
                var scanHintPopup: ScanHintPopup? = onboarding!.scanHintPopup
                if scanHintPopup == nil {
                    scanHintPopup = ScanHintPopup()
                    onboarding!.scanHintPopup = scanHintPopup
                }
                if let title = optionsScanHintPopup["title"] as? String {
                    scanHintPopup!.title = title
                }
                if let message = optionsScanHintPopup["message"] as? String {
                    scanHintPopup!.message = message
                }
                if let closeButton = optionsScanHintPopup["closeButton"] as? String {
                    scanHintPopup!.closeButton = closeButton
                }

                if let imageSource = getImageSource(
                    entry: optionsScanHintPopup
                ) {
                    scanHintPopup!.imageSource = imageSource
                }
            } else {
                // If "scanHintPopup" does not include valid dictionary but a value
                // it is the nil value (NSNull). In that case user wants to disable
                // the scanHintPopup.
                if optionsOnboarding["scanHintPopup"] != nil {
                    onboarding!.scanHintPopup = nil
                }
            }
            if let optionsOnboardingItems = optionsOnboarding["items"] as? [[String: Any]] {
                var items = [DocutainListItem]()
                for item in optionsOnboardingItems {
                    items.append(readDocutainListItemConfig(entry: item))
                }
                if items.isEmpty {
                    onboarding!.items = nil
                } else {
                    onboarding!.items = items
                }
            }
        } else {
            // If "onboarding" does not include valid dictionary but a value
            // it is the nil value (NSNull). In that case user wants to disable
            // the onboarding.
            if config["onboarding"] != nil {
                scanConfig.onboarding = nil
            }
        }

        if let optionsScanTips = config["scanTips"] as? [String: Any] {
            var scanTips: ScanTips? = scanConfig.scanTips
            if scanTips == nil {
                scanTips = ScanTips()
                scanConfig.scanTips = scanTips
            }
            readButtonConfig(
                key: "toolbarItem",
                docutainButton: scanTips!.toolbarItem,
                entry: optionsScanTips,
                toolbarButton: true
            )

            if let optionsScanTipsItems = optionsScanTips["items"] as? [[String: Any]] {
                var items = [DocutainListItem]()
                for item in optionsScanTipsItems {
                    items.append(readDocutainListItemConfig(entry: item))
                }
                if items.isEmpty {
                    scanTips!.items = ScanTips.defaultItems
                } else {
                    scanTips!.items = items
                }
            }
        } else {
            // If "scanTips" does not include valid dictionary but a value
            // it is the nil value (NSNull). In that case user wants to disable
            // the scanTips.
            if config["scanTips"] != nil {
                scanConfig.scanTips = nil
            }
        }

        if needToMigrate && scanConfig.onboarding != nil && scanConfig.onboarding!.scanHintPopup != nil {
            let scanHintPopup =  scanConfig.onboarding!.scanHintPopup!
            // CheckMigration
            let optionsOnboarding = config["onboarding"] as? [String: Any]
            var optionsScanHintPopup: [String: Any]?
            if optionsOnboarding != nil {
                optionsScanHintPopup = optionsOnboarding!["scanHintPopup"] as? [String: Any]
            }
            if let onboardingImageSource = config["onboardingImageSource"] as? String {
                let imageSource = optionsScanHintPopup?["imageSource"]
                if optionsScanHintPopup == nil || imageSource == nil {
                    scanHintPopup.imageSource = onboardingImageSource
                }
            }
            if let optionsTextConfig = config["textConfig"] as? [String: Any] {
                if let textOnboardingTitle = optionsTextConfig["textOnboardingTitle"] as? String {
                    let title = optionsScanHintPopup?["title"]
                    if optionsScanHintPopup == nil || title == nil {
                        scanHintPopup.title = textOnboardingTitle
                    }
                }
                if let textOnboardingMessage = optionsTextConfig["textOnboardingMessage"] as? String {
                    let message = optionsScanHintPopup?["message"]
                    if optionsScanHintPopup == nil || message == nil {
                        scanHintPopup.message = textOnboardingMessage
                    }
                }
                if let textOnboardingCloseButton = optionsTextConfig["textOnboardingCloseButton"] as? String {
                    let closeButton = optionsScanHintPopup?["closeButton"]
                    if optionsScanHintPopup == nil || closeButton == nil {
                        scanHintPopup.closeButton = textOnboardingCloseButton
                    }
                }
            }
        }
        return true
    }
    
    @available(iOS 13.0, *)
    private func mapPhotoPaymentConfiguration(options: [String: Any], photoPaymentConfig: PhotoPaymentConfiguration) -> Bool {
        if !mapDocumentScannerConfiguration(
            config: options,
            scanConfig: photoPaymentConfig
        ) {
            return false
        }
        if let optionsAnalyzeConfig = options["analyzeConfig"] as? [String: Any] {
            if let readBIC = optionsAnalyzeConfig["readBIC"] as? Bool {
                photoPaymentConfig.analyzeConfig.readBIC = readBIC
            }
            if let readPaymentState = optionsAnalyzeConfig["readPaymentState"] as? Bool {
                photoPaymentConfig.analyzeConfig.readPaymentState = readPaymentState
            }
            if let readSEPACreditor = optionsAnalyzeConfig["readSEPACreditor"] as? Bool {
                photoPaymentConfig.analyzeConfig.readSEPACreditor = readSEPACreditor
            }
        }
        if let optionsEmptyResultScreen = options["emptyResultScreen"]  as? [String: Any]  {
            var emptyResultScreen: EmptyResultScreen? = photoPaymentConfig.emptyResultScreen
            if emptyResultScreen == nil {
                emptyResultScreen = EmptyResultScreen()
                photoPaymentConfig.emptyResultScreen = emptyResultScreen
            }
            if let title = optionsEmptyResultScreen["title"] as? String {
                emptyResultScreen!.title = title
            }
            readButtonConfig(
                key: "repeatButton",
                docutainButton: emptyResultScreen!.repeatButton,
                entry: optionsEmptyResultScreen
            )
            if let optionsEmptyResultScreenItems = optionsEmptyResultScreen["items"] as? [[String: Any]] {
                var items = [DocutainListItem]()
                for item in optionsEmptyResultScreenItems {
                    items.append(readDocutainListItemConfig(entry: item))
                }
                if items.isEmpty {
                    emptyResultScreen!.items = nil
                } else {
                    emptyResultScreen!.items = items
                }
            }
        } else {
            // If "emptyResultScreen" does not include valid dictionary but a value
            // it is the nil value (NSNull). In that case user wants to disable
            // the emptyResultScreen.
            if options["emptyResultScreen"] != nil {
                photoPaymentConfig.emptyResultScreen = nil
            }
        }
        return true
    }
    
    private func readDocutainListItemConfig(entry: [String: Any]) -> DocutainListItem {
        let image = entry["image"] as? String ?? ""
        let title = entry["title"] as? String ?? ""
        let message = entry["message"] as? String ?? ""
        return DocutainListItem(image: image, title: title, message: message)
    }
    
    private func readButtonConfig(
        key: String,
        docutainButton: DocutainButton,
        entry: [String: Any],
        toolbarButton: Bool = false
    ) {
        if let docutainButtonEntry = entry[key] as? [String: Any] {
            if let title = docutainButtonEntry["title"] as? String {
                docutainButton.title = title
                if toolbarButton {
                    docutainButton.icon = nil
                    return
                }
            }
            if let icon = docutainButtonEntry["icon"] as? String {
                docutainButton.icon = icon
            }
        }
    }
    
    private func getScanFilterFromString(scanFilter: String) -> ScanFilter {
        switch scanFilter.uppercased() {
        case "AUTO":
            return ScanFilter.auto
        case "GRAY":
            return ScanFilter.gray
        case "BLACKWHITE":
            return ScanFilter.blackWhite
        case "ORIGINAL":
            return ScanFilter.original
        case "TEXT":
            return ScanFilter.text
        case "AUTO2":
            return ScanFilter.auto2
        case "ILLUSTRATION":
            return ScanFilter.illustration
        default:
            return ScanFilter.illustration
        }
    }

    private func getScanSourceFromString(scanSource: String) -> Source {
        switch scanSource.uppercased() {
        case "CAMERA":
            return Source.camera
        case "IMAGE":
            return Source.image
        case "GALLERY":
            return Source.gallery
        case "GALLERY_MULTIPLE":
            if #available(iOS 14, *) {
                return Source.galleryMultiple
            } else {
                // fallback to single image picking
                return Source.gallery
            }
        case "CAMERA_IMPORT":
            if #available(iOS 14, *) {
                return Source.cameraImport
            } else {
                return Source.camera
            }
        default:
            return Source.camera
        }
    }
    
    private func getImageSource(entry: [String: Any]) -> String? {
        if let imageSource = entry["imageSource"] as? String {
            return imageSource
        }
        return nil
    }
    
    private func cancelDocumentScanDueToInvalidConfig(result: CDVInvokedUrlCommand, details: String? = nil){
        sendResultError(result, errorMessage: "DocumentScannerConfiguration not valid")
    }
    
    private func cancelPhotoPaymentDueToInvalidConfig(result: CDVInvokedUrlCommand, details: String? = nil){
        sendResultError(result, errorMessage: "StartPhotoPaymentOptions not valid")
    }
    
    @objc func startPhotoPayment(_ command: CDVInvokedUrlCommand) {
        if #available(iOS 13.0, *) {
            let nativePhotoPaymentConfig = PhotoPaymentConfiguration()
            if !mapPhotoPaymentConfiguration(options: command.arguments[0] as! [String:Any], photoPaymentConfig: nativePhotoPaymentConfig) {
                cancelPhotoPaymentDueToInvalidConfig(result: command)
                return
            }
            photoPaymentCommand = command
            //Cordova runs async, so we need to dispatch on ui thread
            DispatchQueue.main.async {
                UI.startPhotoPayment(delegate: self, config: nativePhotoPaymentConfig)
            }
        } else {
            // Fallback on earlier versions
            sendResultError(command, errorMessage: "PhotoPayment is available from iOS 13 or higher.")
        }
    }
    
    @objc func resetOnboarding(_ command: CDVInvokedUrlCommand) {
        let onboarding = command.arguments[0] as? Bool ?? true
        let scanHintPopup = command.arguments[1] as? Bool ?? true
        Onboarding()
            .reset(
                onboarding: onboarding,
                scanHintPopup: scanHintPopup
            )
        sendResultOK(command)
    }
    
    @objc func onboardingDefaultItems(_ command: CDVInvokedUrlCommand) {
        sendResultOK(command, data: defaultItemsToJSONArray(defaultItems: Onboarding.defaultItems))
    }

    @objc func emptyResultScreenDefaultItems(_ command: CDVInvokedUrlCommand) {
        sendResultOK(command, data: defaultItemsToJSONArray(defaultItems: EmptyResultScreen.defaultItems))
    }

    @objc func scanTipsDefaultItems(_ command: CDVInvokedUrlCommand) {
        sendResultOK(command, data: defaultItemsToJSONArray(defaultItems: ScanTips.defaultItems))
    }
    
    private func defaultItemsToJSONArray(defaultItems: [DocutainListItem]) -> [[String: Any]] {
        var array = [[String: Any]]()
        defaultItems.forEach {
            var tempDicData = [String: Any]()
            tempDicData["image"] = $0.image
            tempDicData["title"] = $0.title
            tempDicData["message"] = $0.message
            array.append(tempDicData)
        }
        return array
    }

    private func getLogLevelFromString(loglevel: String) -> Logger.Level {
        switch (loglevel.uppercased()) {
        case "DISABLE":
            return Logger.Level.disable
        case "ASSERT":
            return Logger.Level.assert
        case "ERROR":
            return Logger.Level.error
        case "WARNING":
            return Logger.Level.warning
        case "INFO":
            return Logger.Level.info
        case "DEBUG":
            return Logger.Level.debug
        case "VERBOSE":
            return Logger.Level.verbose
        default:
            return Logger.Level.error
        }
    }

    private func getPDFPageFormatFromString(pageFormat: String?) -> Document.PDFPageFormat {
        if(pageFormat == nil || pageFormat!.isEmpty){
            return Document.PDFPageFormat.A4
        }
        switch (pageFormat!.uppercased()) {
        case "FIT_TO_PAGES":
            return Document.PDFPageFormat.FitToPages
        case "A4":
            return Document.PDFPageFormat.A4
        case "A4_LANDSCAPE":
            return Document.PDFPageFormat.A4Landscape
        case "A5":
            return Document.PDFPageFormat.A5
        case "A5_LANDSCAPE":
            return Document.PDFPageFormat.A5Landscape
        case "LETTER":
            return Document.PDFPageFormat.Letter
        case "LETTER_LANDSCAPE":
            return Document.PDFPageFormat.LetterLandscape
        case "LEGAL":
            return Document.PDFPageFormat.Legal
        case "LEGAL_LANDSCAPE":
            return Document.PDFPageFormat.LegalLandscape
        default:
            return Document.PDFPageFormat.A4
        }
    }
    
    private func getPageSourceTypeFromString(pageSourceType: String?) -> Document.PageSourceType{
        if(pageSourceType == nil || pageSourceType!.isEmpty){
            return Document.PageSourceType.cutFilter
        }
        switch (pageSourceType!.uppercased()) {
          case "ORIGINAL":
            return Document.PageSourceType.original
          case "CUT_FILTER":
            return Document.PageSourceType.cutFilter
          case "CUT_ONLY":
            return Document.PageSourceType.cutOnly
          default:
            return Document.PageSourceType.cutFilter
        }
    }

    private func sendResultOK(_ command: CDVInvokedUrlCommand) -> Void{
        let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK)
        self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
    }
    
    private func sendResultOK(_ command: CDVInvokedUrlCommand, message: String) -> Void{
        let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: message)
        self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
    }
    
    private func sendResultOK(_ command: CDVInvokedUrlCommand, message: Int) -> Void{
        let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: message)
        self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
    }
    
    private func sendResultOK(_ command: CDVInvokedUrlCommand, data: [[String: Any]]) -> Void{
        let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: data)
        self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
    }

    private func sendResultError(_ command: CDVInvokedUrlCommand, errorMessage: String) -> Void{
        let pluginResult = CDVPluginResult(status: CDVCommandStatus_ERROR, messageAs: errorMessage)
        self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
    }
}

extension DocutainSDKCordova: ScanDelegate {
    public func didFinishScan(withResult result: Bool) {
        if let command = scanCommand{
            let result = ["status": result ? "SUCCESS" : "CANCELED"]
            let pluginResult = CDVPluginResult(status: CDVCommandStatus_OK, messageAs: result)
            self.commandDelegate.send(pluginResult, callbackId: command.callbackId)
        }
        //reset
        scanCommand = nil
    }
}

extension DocutainSDKCordova: PhotoPaymentDelegate {
    public func didFinishPhotoPayment(paymentData: String?) {
        if let command = photoPaymentCommand{
            if let data = paymentData{
                sendResultOK(command, message: data)
            } else{
                sendResultError(command, errorMessage: "CANCELED")
            }
        }
        //reset
        photoPaymentCommand = nil
    }
}

private extension UIColor {
    convenience init(hexaString: String) {
        // input is #AARRGGBB or #RRGGBB
        var colorString = String(hexaString.dropFirst())
        var alpha: UInt = 255
        if(colorString.count == 8){
            // alpha channel is defined
            alpha = strtoul(colorString.substring(to: 2), nil, 16)
            colorString = colorString.substring(from: 2)
        }
        var ulColor = strtoul(colorString, nil, 16)
        let b = ulColor % 256
        ulColor = ulColor >> 8
        let g = ulColor % 256
        ulColor = ulColor >> 8
        let r = ulColor % 256
        self.init(red: .init(r)/255,
                  green: .init(g)/255,
                  blue: .init(b)/255,
                  alpha: .init(alpha)/255)}
}

private extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }
}
