/**
 *   Docutain SDK Capacitor
 *   Copyright (c) INFOSOFT Informations- und Dokumentationssysteme GmbH. All rights reserved.
 *
 *   Docutain SDK Capacitor is a commercial product and requires a license.
 *   Details found in the LICENSE file in the root directory of this source tree.
 */

import Foundation
import Capacitor
import DocutainSdk
import typealias DocutainSdk.Logger
import typealias DocutainSdk.Document
import typealias DocutainSdk.Source
import typealias DocutainSdk.ScanFilter

@objc(DocutainSDKPlugin)
public class DocutainSDKPlugin: CAPPlugin, ScanDelegate, PhotoPaymentDelegate {

    private var scanCall: CAPPluginCall?
    private var scanCallStartDocumentScanner: CAPPluginCall?
    private var photoPaymentCall: CAPPluginCall?
    private let canceledState = "CANCELED"

    @objc func initSDK(_ call: CAPPluginCall) {
        guard let licenseKey = call.getString("licenseKey") else {
            call.reject("no license key provided")
            return
        }
        if !DocutainSDK.initSDK(licenseKey: licenseKey) {
            call.reject(DocutainSDK.getLastError())
        } else {
            call.resolve()
        }
    }

    @objc func setAnalyzeConfiguration(_ call: CAPPluginCall) {
        // only available for iOS >= 13
        if #available(iOS 13, *) {
            let analyzeConfig = AnalyzeConfiguration()
            guard let data = call.getObject("config") else {
                call.reject("config not provided")
                return
            }
            mapAnalyzeConfiguration(data: data, analyzeConfig: analyzeConfig)
            if !DocumentDataReader.setAnalyzeConfiguration(analyzeConfiguration: analyzeConfig) {
                call.reject(DocutainSDK.getLastError())
            } else {
                call.resolve()
            }
        } else {
            call.unavailable("Not available in iOS 12 or earlier.")
        }
    }

    @objc func scanDocument(_ call: CAPPluginCall) {
        let scanConfig = DocumentScannerConfiguration()
        guard let config = call.getObject("config") else {
            call.reject("config not provided")
            return
        }
        if !mapDocumentScannerConfiguration(config: config, scanConfig: scanConfig, needToMigrate: true) {
            call.reject("DocumentScannerConfiguration not valid")
            return
        }
        scanCall = call
        // capacitor runs async, so we need to dispatch on ui thread
        DispatchQueue.main.async {
            UI.scanDocument(scanDelegate: self, scanConfig: scanConfig)
        }
    }

    @objc func loadFile(_ call: CAPPluginCall) {
        // only available for iOS >= 13
        if #available(iOS 13, *) {
            guard let path = call.getString("filepath") else {
                call.reject("no filepath provided")
                return
            }
            if let url = URL(string: path) {
                if !DocumentDataReader.loadFile(fileUrl: url as URL) {
                    call.reject(DocutainSDK.getLastError())
                } else {
                    call.resolve()
                }
            } else {
                call.reject("invalid path provided")
            }
        } else {
            call.unavailable("Not available in iOS 12 or earlier.")
        }
    }

    @objc func writePDF(_ call: CAPPluginCall) {
        let pageFormat = call.getString("pageFormat")
        var fileUri = call.getString("fileUri")
        var overWrite = call.getBool("overWrite", true)
        
        if(fileUri == nil || fileUri!.isEmpty){
            overWrite = true
            let appDir = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
            fileUri = appDir[0] + "/Docutain/Temp/Docutain.pdf";
        }

        let maxSizeKB = call.getInt("maxSizeKB") ?? 0

        if let url = URL(string: fileUri!) {
            if let returnURL = Document.writePDF(fileUrl: url.deletingLastPathComponent(), fileName: url.lastPathComponent, overwrite: overWrite, pageFormat: getPDFPageFormatFromString(pageFormat: pageFormat), maxSizeKB: UInt32(maxSizeKB)) {
                call.resolve([
                    "fileUri": returnURL.absoluteString
                ])
            } else {
                call.reject(DocutainSDK.getLastError())
            }
        } else {
            call.reject("invalid path provided")
        }
    }

    @objc func writeImage(_ call: CAPPluginCall) {
        guard let fileUri = call.getString("fileUri") else {
            call.reject("no fileUri provided")
            return
        }
        guard let pageNumber = call.getInt("pageNumber") else {
            call.reject("pageNumber not provided")
            return
        }
        if let url = URL(string: fileUri) {
            if let returnURL = Document.writeImage(page: pageNumber, fileUrl: url) {
                call.resolve([
                    "fileUri": returnURL.absoluteString
                ])
            } else {
                call.reject(DocutainSDK.getLastError())
            }
        } else {
            call.reject("invalid path provided")
        }
    }

    @objc func getImageBytes(_ call: CAPPluginCall) {
        guard let pageNumber = call.getInt("pageNumber") else {
            call.reject("pageNumber not provided")
            return
        }
        let pageSourceType = call.getString("pageSourceType")
        if let data = Document.getImage(page: pageNumber, pageSourceType: getPageSourceTypeFromString(pageSourceType: pageSourceType)) {
            call.resolve([
                "bytes": data.base64EncodedString()
            ])
        } else {
            call.reject(DocutainSDK.getLastError())
        }
    }

    @objc func getText(_ call: CAPPluginCall) {
        // only available for iOS >= 13
        if #available(iOS 13, *) {
            call.resolve([
                "text": DocumentDataReader.getText()
            ])
        } else {
            call.unavailable("Not available in iOS 12 or earlier.")
        }
    }

    @objc func getTextPage(_ call: CAPPluginCall) {
        // only available for iOS >= 13
        if #available(iOS 13, *) {
            guard let pageNumber = call.getInt("pageNumber") else {
                call.reject("pageNumber not provided")
                return
            }
            call.resolve([
                "text": DocumentDataReader.getText(pageNumber: Int32(pageNumber))
            ])
        } else {
            call.unavailable("Not available in iOS 12 or earlier.")
        }
    }

    @objc func analyze(_ call: CAPPluginCall) {
        // only available for iOS >= 13
        if #available(iOS 13, *) {
            call.resolve([
                "data": DocumentDataReader.analyze()
            ])
        } else {
            call.unavailable("Not available in iOS 12 or earlier.")
        }
    }

    @objc func setLogLevel(_ call: CAPPluginCall) {
        guard let logLevel = call.getString("logLevel") else {
            call.reject("logLevel not provided")
            return
        }
        Logger.setLogLevel(level: getLogLevelFromString(loglevel: logLevel))
        call.resolve()
    }

    @objc func getTraceFile(_ call: CAPPluginCall) {
        call.resolve([
            "fileUri": URL(fileURLWithPath: Logger.getTraceFile()).absoluteString
        ])
    }

    @objc func deleteTempFiles(_ call: CAPPluginCall) {
        let deleteTraceFileContent = call.getBool("deleteTraceFileContent") ?? false
        if DocutainSDK.deleteTempFiles(deleteTraceFileContent: deleteTraceFileContent) {
            call.resolve()
        } else {
            call.reject(DocutainSDK.getLastError())
        }
    }

    @objc func pageCount(_ call: CAPPluginCall) {
        call.resolve([
            "count": Document.pageCount()
        ])
    }

    private func mapAnalyzeConfiguration(data: [String: Any], analyzeConfig: AnalyzeConfiguration) {
        if let readBIC = data["readBIC"] as? Bool {
            analyzeConfig.readBIC = readBIC
        }
        if let readPaymentState = data["readPaymentState"] as? Bool {
            analyzeConfig.readPaymentState = readPaymentState
        }
        if let readSEPACreditor = data["readSEPACreditor"] as? Bool {
            analyzeConfig.readSEPACreditor = readSEPACreditor
        }
    }

    private func setButtonConfig(docutainButton: DocutainButton, docutainButtonEntry: [String: Any]) {
        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
    }

    private func mapPhotoPaymentConfiguration(call: CAPPluginCall, photoPaymentConfig: PhotoPaymentConfiguration) -> Bool {
        if !mapDocumentScannerConfiguration(config: call.options as! [String: Any], scanConfig: photoPaymentConfig) {
            return false
        }
        if let optionsAnalyzeConfig = call.getObject("analyzeConfig") {
            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 = call.getObject("emptyResultScreen") {
            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 call.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
            }
        }
    }

    public func didFinishScan(withResult result: Bool) {
        // alte variante
        if let call = scanCall {
            if result {
                call.resolve([
                    "status": "SUCCESS"
                ])
            } else {
                call.resolve([
                    "status": "CANCELED"
                ])
            }
        }
        // neue variante
        if let call = scanCallStartDocumentScanner {
            if result {
                call.resolve()
            } else {
                call.reject("user canceled", canceledState)
            }
        }
        // reset
        scanCall = nil
        scanCallStartDocumentScanner = nil
    }

    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 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 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
        }
    }

    @objc func startPhotoPayment(_ call: CAPPluginCall) {
        let nativePhotoPaymentConfig = PhotoPaymentConfiguration()
        if !mapPhotoPaymentConfiguration(call: call, photoPaymentConfig: nativePhotoPaymentConfig) {
            call.reject("StartPhotoPaymentOptions not valid")
            return
        }
        photoPaymentCall = call
        // capacitor runs async, so we need to dispatch on ui thread
        DispatchQueue.main.async {
            UI.startPhotoPayment(delegate: self, config: nativePhotoPaymentConfig)
        }
    }

    @objc func resetOnboarding(_ call: CAPPluginCall) {
        let onboarding = call.getBool("onboarding")
        let scanHintPopup = call.getBool("scanHintPopup")
        Onboarding().reset(onboarding: onboarding ?? true, scanHintPopup: scanHintPopup ?? true)
        call.resolve()
    }

    @objc func onboardingDefaultItems(_ call: CAPPluginCall) {
        call.resolve([
            "items": defaultItemsToJSONArray(defaultItems: Onboarding.defaultItems)
        ])
    }

    @objc func emptyResultScreenDefaultItems(_ call: CAPPluginCall) {
        call.resolve([
            "items": defaultItemsToJSONArray(defaultItems: EmptyResultScreen.defaultItems)
        ])
    }

    @objc func scanTipsDefaultItems(_ call: CAPPluginCall) {
        call.resolve([
            "items": defaultItemsToJSONArray(defaultItems: ScanTips.defaultItems)
        ])
    }

    @objc func setAnalyzeConfigurationDocScan(_ call: CAPPluginCall) {
        let analyzeConfig = AnalyzeConfiguration()
        mapAnalyzeConfiguration(data: call.options as! [String: Any], analyzeConfig: analyzeConfig)
        if !DocumentDataReader.setAnalyzeConfiguration(analyzeConfiguration: analyzeConfig) {
            call.reject(DocutainSDK.getLastError())
        } else {
            call.resolve()
        }
    }

    @objc func startDocumentScanner(_ call: CAPPluginCall) {
        let nativeScanConfig = DocumentScannerConfiguration()
        if !mapDocumentScannerConfiguration(config: call.options as! [String: Any], scanConfig: nativeScanConfig) {
            call.reject("StartDocumentScannerOptions not valid")
            return
        }
        scanCallStartDocumentScanner = call
        // capacitor runs async, so we need to dispatch on ui thread
        DispatchQueue.main.async {
            UI.scanDocument(scanDelegate: self, scanConfig: nativeScanConfig)
        }
    }

    private func getImageSource(entry: [String: Any]) -> String? {
        if let imageSource = entry["imageSource"] as? String {
            return imageSource
        }
        return nil
    }

    

    
    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
    }

    public func didFinishPhotoPayment(paymentData: String?) {
        if let call = photoPaymentCall {
            if let data = paymentData {
                call.resolve([
                    "data": data
                ])
            } else {
                call.reject("user canceled", canceledState)
            }
        }
        // reset
        photoPaymentCall = 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])
    }
}
