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

import UIKit
import React
import DynamsoftCaptureVisionBundle

@objc(RNDynamsoftCaptureVisionRouter)
public class RNDynamsoftCaptureVisionRouter: RCTEventEmitter {

    @objc public var cvr: CaptureVisionRouter?

    @objc public private(set) var deskewedImageDataArray: [ImageData] = []
    @objc public private(set) var enhancedImageDataArray: [ImageData] = []

    private var receiveDictionary: [String: Bool] = [:]
    private var hasListeners = false
    private var semaphore = DispatchSemaphore(value: 0)

    @objc(createInstance)
    func createInstance() -> NSNumber {
        cvr = CaptureVisionRouter()
        return true
    }

    @objc(destroyInstance)
    func destroyInstance() -> NSNumber {
        cvr = nil
        return true
    }

    @objc func setGlobalIntraOpNumThreads(_ intraOpNumThreads: NSInteger) {
        CaptureVisionRouter.setGlobalIntraOpNumThreads(intraOpNumThreads)
    }

    @objc func clearDLModelBuffers() {
        CaptureVisionRouter.clearDLModelBuffers()
    }

    @objc(switchCapturingTemplate:resolve:reject:)
    func switchCapturingTemplate(_ templateName: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            try cvr?.switchCapturingTemplate(templateName)
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(startCapturing:resolve:reject:)
    func startCapturing(_ templateName: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        guard let cvr = cvr else {
            reject("CVR_NOT_INITIALIZED", "CaptureVisionRouter has not been initialized", nil)
            return
        }

        cvr.startCapturing(templateName, completionHandler: { [weak self] isSuccess, error in
            guard let self = self else { return }
            if !isSuccess, let error = error {
                let nsError = error as NSError
                reject(String(nsError.code), nsError.localizedDescription, nsError)
            } else if !isSuccess {
                reject("CVR_START_FAILED", "CaptureVisionRouter failed to start capturing", nil)
            } else {
                self.semaphore = DispatchSemaphore(value: 0)
                resolve(nil)
            }
        })
    }

    @objc(stopCapturing:reject:)
    func stopCapturing(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        guard let cvr = cvr else {
            resolve(nil)
            return
        }
        continueCRR()
        cvr.stopCapturing()
        resolve(nil)
    }

    @objc func setInput(_ key: String) {
        guard let cvr = cvr else {
            return
        }
        guard let isa = DSISAStore.shared.map[key] as? ImageSourceAdapter else {
            return
        }
        do {
            try cvr.setInput(isa)
        } catch {
            let nsError = error as NSError
            RNDynamsoftLogError("CaptureVisionRouter setInput failed: \(nsError.localizedDescription)")
        }
    }

    @objc(getSimplifiedSettings:resolve:reject:)
    func getSimplifiedSettings(_ name: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            let settings = try cvr?.getSimplifiedSettings(name)
            resolve(settings?.toJson())
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(updateSettings:templateName:resolve:reject:)
    func updateSettings(_ dictionary: NSDictionary, templateName: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        let settings = try? cvr?.getSimplifiedSettings(templateName)
        settings?.update(withDicitionary: dictionary as? [AnyHashable: Any] ?? [:])
        do {
            if let settings = settings {
                try cvr?.updateSettings(templateName, settings: settings)
            }
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(resetSettings:reject:)
    func resetSettings(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            try cvr?.resetSettings()
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(initSettings:resolve:reject:)
    func initSettings(_ content: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            try cvr?.initSettings(content)
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(initSettingsFromFile:resolve:reject:)
    func initSettingsFromFile(_ path: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            try cvr?.initSettingsFromFile(path)
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(outputSettings:includeDefaultValues:resolve:reject:)
    func outputSettings(_ name: String, includeDefaultValues: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            let setting = try cvr?.outputSettings(name, includeDefaultValues: includeDefaultValues)
            resolve(setting)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    @objc(outputSettingsToFile:file:includeDefaultValues:resolve:reject:)
    func outputSettingsToFile(_ name: String, file path: String, includeDefaultValues: Bool, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
        do {
            try cvr?.outputSettingsToFile(name, filePath: path, includeDefaultValues: includeDefaultValues)
            resolve(nil)
        } catch {
            let nsError = error as NSError
            reject(String(nsError.code), nsError.localizedDescription, error)
        }
    }

    // MARK: - Listener

    @objc func continueCRR() {
        semaphore.signal()
    }

    @objc func addResultReceiver(_ event: String) {
        receiveDictionary[event] = true
    }

    @objc func removeResultReceiver(_ event: String) {
        receiveDictionary.removeValue(forKey: event)
    }

    @objc func removeAllResultListeners() {
        cvr?.removeResultReceiver(self)
    }

    @objc func addFilter(_ key: String) {
        if let filter = DSFilterStore.shared.map[key] {
            cvr?.addResultFilter(filter)
        }
    }

    @objc func removeFilter(_ key: String) {
        if let filter = DSFilterStore.shared.map[key] {
            cvr?.removeResultFilter(filter)
        }
    }

    public override func supportedEvents() -> [String]! {
        return ["onCapturedResultReceived",
                "onDecodedBarcodesReceived",
                "onProcessedDocumentResultReceived",
                "onRecognizedTextLinesReceived",
                "onParsedResultsReceived"]
    }

    public override func startObserving() {
        hasListeners = true
        cvr?.addResultReceiver(self)
    }

    public override func stopObserving() {
        hasListeners = false
        cvr?.removeResultReceiver(self)
    }

    // MARK: - Private

    // Invoked from the JSI installer (RNDynamsoftCaptureVisionRouterJSI.mm).
    @objc(getOriginalImage:)
    public func getOriginalImage(_ imageHashId: String) -> ImageData? {
        return cvr?.getIntermediateResultManager().getOriginalImage(imageHashId)
    }

    private func waitForContinueCRR() {
        _ = semaphore.wait(timeout: .now() + 10)
    }
}

extension RNDynamsoftCaptureVisionRouter: CapturedResultReceiver {
    public func onCapturedResultReceived(_ result: CapturedResult) {
        guard hasListeners, receiveDictionary["onCapturedResultReceived"] == true else { return }

        let imageResult = result.processedDocumentResult
        deskewedImageDataArray = imageResult?.deskewedImageResultItems?.compactMap { $0.imageData } ?? []
        enhancedImageDataArray = imageResult?.enhancedImageResultItems?.compactMap { $0.imageData } ?? []

        sendEvent(withName: "onCapturedResultReceived", body: result.toJson())
        waitForContinueCRR()
    }

    public func onDecodedBarcodesReceived(_ result: DecodedBarcodesResult) {
        guard hasListeners, receiveDictionary["onDecodedBarcodesReceived"] == true else { return }
        sendEvent(withName: "onDecodedBarcodesReceived", body: result.toJson())
        waitForContinueCRR()
    }

    public func onProcessedDocumentResultReceived(_ result: ProcessedDocumentResult) {
        guard hasListeners, receiveDictionary["onProcessedDocumentResultReceived"] == true else { return }

        deskewedImageDataArray = result.deskewedImageResultItems?.compactMap { $0.imageData } ?? []
        enhancedImageDataArray = result.enhancedImageResultItems?.compactMap { $0.imageData } ?? []

        sendEvent(withName: "onProcessedDocumentResultReceived", body: result.toJson())
        waitForContinueCRR()
    }

    public func onRecognizedTextLinesReceived(_ result: RecognizedTextLinesResult) {
        guard hasListeners, receiveDictionary["onRecognizedTextLinesReceived"] == true else { return }
        sendEvent(withName: "onRecognizedTextLinesReceived", body: result.toJson())
        waitForContinueCRR()
    }

    public func onParsedResultsReceived(_ result: ParsedResult) {
        guard hasListeners, receiveDictionary["onParsedResultsReceived"] == true else { return }
        sendEvent(withName: "onParsedResultsReceived", body: result.toJson())
        waitForContinueCRR()
    }
}
