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

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

@objc(DocutainSdk)
class DocutainSdk: NSObject, ScanDelegate {

private var scanResolve : RCTPromiseResolveBlock? = nil

  @objc(multiply:withB:withResolver:withRejecter:)
  func multiply(a: Float, b: Float, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void {
    resolve(a*b)
  }

  @objc(initSDK:withResolver:withRejecter:)
  func initSDK(licenseKey: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
    if(!DocutainSDK.initSDK(licenseKey: licenseKey)){
      resolve(false)
    } else{
      resolve(true)
    }
  }

  @objc(getLastError:withRejecter:)
  func getLastError(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
    resolve(DocutainSDK.getLastError())
  }

  @objc(setAnalyzeConfiguration:withResolver:withRejecter:)
  func setAnalyzeConfiguration(config: [String: Any], resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
    //only available for iOS >= 13
    if #available(iOS 13, *){
      let analyzeConfig = AnalyzeConfiguration()
      mapAnalyzeConfiguration(config: config, analyzeConfig: analyzeConfig)
      if(!DocumentDataReader.setAnalyzeConfiguration(analyzeConfiguration: analyzeConfig)){
        resolve(false)
      } else{
        resolve(true)
      }
    } else{
      resolve(false)
    }
  }

  @objc(scanDocument:withResolver:withRejecter:)
  func scanDocument(config: [String: Any], resolve:@escaping RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
    let scanConfig = DocumentScannerConfiguration()
    mapDocumentScannerConfiguration(config: config, scanConfig: scanConfig)
    scanResolve = resolve
    //react runs async, so we need to dispatch on ui thread
    DispatchQueue.main.async {
        UI.scanDocument(scanDelegate: self, scanConfig: scanConfig)
    }
  }

  @objc(loadFile:withResolver:withRejecter:)
  func loadFile(path: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      //only available for iOS >= 13
      if #available(iOS 13, *){
          if let url = URL(string: path){
              if(!DocumentDataReader.loadFile(fileUrl: url as URL)){
                resolve(false)
              } else{
                resolve(true)
              }
          } else{
              resolve(false)
          }
      } else{
          resolve(false)
      }
  }

    @objc(writePDF:withOverwrite:withPageFormat:withResolver:withRejecter:)
  func writePDF(filepath: String, overWrite: Bool, pageFormat: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      if let url = URL(string: filepath){
          if let returnURL = Document.writePDF(fileUrl: url.deletingLastPathComponent(), fileName: url.lastPathComponent, overwrite: overWrite, pageFormat: getPDFPageFormatFromString(pageFormat: pageFormat)){
          resolve(returnURL.path)
        } else{
          resolve("")
        }
      } else{
        resolve("")
      }
  }

  @objc(writeImage:withPath:withResolver:withRejecter:)
  func writeImage(pageNumber: Int, filepath: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      if let url = URL(string: filepath){
          if let returnURL = Document.writeImage(page: pageNumber, fileUrl: url){
          resolve(returnURL.path)
        } else{
          resolve("")
        }
      } else{
        resolve("")
      }
  }
    
  @objc(getImageBytes:withPageSourceType:withResolver:withRejecter:)
  func getImageBytes(pageNumber: Int, pageSourceType: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
    if let data = Document.getImage(page: pageNumber, pageSourceType: getPageSourceTypeFromString(pageSourceType: pageSourceType)){
        resolve(data.base64EncodedString())
    } else{
        resolve("")
      }
  }

  @objc(getText:withRejecter:)
  func getText(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      //only available for iOS >= 13
      if #available(iOS 13, *){
          resolve(DocumentDataReader.getText())
      } else{
          resolve("")
      }
  }

  @objc(getTextPage:withResolver:withRejecter:)
  func getTextPage(pageNumber: Int, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      //only available for iOS >= 13
      if #available(iOS 13, *){
          resolve(DocumentDataReader.getText(pageNumber: Int32(pageNumber)))
      } else{
          resolve("")
      }
  }

  @objc(analyze:withRejecter:)
  func analyze(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock) -> Void{
      //only available for iOS >= 13
      if #available(iOS 13, *){
          resolve(DocumentDataReader.analyze())
      } else{
          resolve("")
      }
  }

  @objc(setLogLevel:withResolver:withRejecter:)
  func setLogLevel(logLevel: String, resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock)-> Void{
      Logger.setLogLevel(level: getLogLevelFromString(loglevel: logLevel))
      resolve(true)
  }

  @objc(getTraceFile:withRejecter:)
  func getTraceFile(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock)-> Void{
    resolve(Logger.getTraceFile())
  }

  @objc(deleteTempFiles:withResolver:withRejecter:)
  func deleteTempFiles(deleteTraceFileContent:Bool,resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock)-> Void{
    resolve(DocutainSDK.deleteTempFiles(deleteTraceFileContent:deleteTraceFileContent))
  }

    @objc(pageCount:withRejecter:)
  func pageCount(resolve:RCTPromiseResolveBlock,reject:RCTPromiseRejectBlock)-> Void{
    resolve(Document.pageCount())
  }

  internal func didFinishScan(withResult result: Bool) {
    if let resolve = scanResolve{
      resolve(result)
    } 
    //reset
    scanResolve = nil
  }

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

  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: DocumentScannerConfiguration) -> Void{
      if let onboardingImageSource = config["onboardingImageSource"] as? String{
          scanConfig.onboardingImageSource = onboardingImageSource
      }
      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{
              images.append(URL(fileURLWithPath:image))
          }
          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 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{
                      switch(key.uppercased()){
                      case "COLORPRIMARY" :
                          scanConfig.colorConfig.setColorPrimary(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORSECONDARY" :
                          scanConfig.colorConfig.setColorSecondary(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORONSECONDARY" :
                          scanConfig.colorConfig.setColorOnSecondary(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORSCANBUTTONSLAYOUTBACKGROUND" :
                          scanConfig.colorConfig.setColorScanButtonsLayoutBackground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORSCANBUTTONSFOREGROUND" :
                          scanConfig.colorConfig.setColorScanButtonsForeground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORSCANPOLYGON" :
                          scanConfig.colorConfig.setColorScanPolygon(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORBOTTOMBARBACKGROUND" :
                          scanConfig.colorConfig.setColorBottomBarBackground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORBOTTOMBARFOREGROUND" :
                          scanConfig.colorConfig.setColorBottomBarForeground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORTOPBARBACKGROUND" :
                          scanConfig.colorConfig.setColorTopBarBackground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      case "COLORTOPBARFOREGROUND" :
                          scanConfig.colorConfig.setColorTopBarForeground(light: UIColor.init(hexaString: colorLight), dark: UIColor.init(hexaString: colorDark))
                      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 textOnboardingTitle  = textConfig["textOnboardingTitle"] as? String {
              scanConfig.textConfig.textOnboardingTitle = textOnboardingTitle
          }
          if let textOnboardingMessage  = textConfig["textOnboardingMessage"] as? String {
              scanConfig.textConfig.textOnboardingMessage = textOnboardingMessage
          }
          if let textOnboardingCloseButton  = textConfig["textOnboardingCloseButton"] as? String {
              scanConfig.textConfig.textOnboardingCloseButton = textOnboardingCloseButton
          }
          if let textSizeOnboardingTitle = textConfig["textSizeOnboardingTitle"] as? Float {
              scanConfig.textConfig.textSizeOnboardingTitle = (textSizeOnboardingTitle) as NSNumber
          }
          if let textSizeOnboardingMessage = textConfig["textSizeOnboardingMessage"] as? Float {
              scanConfig.textConfig.textSizeOnboardingMessage = (textSizeOnboardingMessage) as NSNumber
          }
          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 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)
                  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
      }
  }
        

  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
      }
      default:
        return Source.camera
    }
  }

  private func getPDFPageFormatFromString(pageFormat: String) -> Document.PDFPageFormat {
    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{
        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 extension UIColor {
    convenience init(hexaString: String, alpha: CGFloat = 1) {
        let chars = Array(hexaString.dropFirst())
        self.init(red:   .init(strtoul(String(chars[0...1]),nil,16))/255,
                  green: .init(strtoul(String(chars[2...3]),nil,16))/255,
                  blue:  .init(strtoul(String(chars[4...5]),nil,16))/255,
                  alpha: alpha)}
}
