import Foundation
import Capacitor
import BRLMPrinterKit
import BRPtouchPrinterKit
import os

/**
 * Please read the Capacitor iOS Plugin Development Guide
 * here: https://capacitorjs.com/docs/plugins/ios
 */
@objc(BrotherPrint)
public class BrotherPrint: CAPPlugin {
    private var cancelRoutineWiFi: (() -> Void)?
    private var cancelRoutineBluetooth: (() -> Void)?

    @objc func printImage(_ call: CAPPluginCall) {
        let logger = Logger(subsystem: "Komia", category: "App")
        let encodedImage: String = call.getString("encodedImage", "")
        if encodedImage == "" {
            call.reject("Image data is not found", "5")
            return
        }

        let newImageData = Data(base64Encoded: encodedImage, options: [])

        let port: String = call.getString("port", "wifi")
        let channelInfo: String = call.getString("channelInfo", "")
        let modelName: String = call.getString("modelName", "not set")
        let labelName: String = call.getString("labelName", "not set")
        let printerModel = BrotherModel.getModelName(from: modelName)

        logger.debug("modelName \(modelName)")
        logger.debug("labelName \(labelName)")

        DispatchQueue.main.async {
            var channel: BRLMChannel
            
            switch port {
            case "wifi":
                channel = BRLMChannel(wifiIPAddress: channelInfo)
            case "bluetooth":
                channel = BRLMChannel(bluetoothSerialNumber: channelInfo)
            case "bluetoothLowEnergy":
                channel = BRLMChannel(bleLocalName: channelInfo)
            default:
                call.reject("\(port) is not supported", "17")
                return
            }

            let generateResult = BRLMPrinterDriverGenerator.open(channel)
            guard generateResult.error.code == BRLMOpenChannelErrorCode.noError,
                  let printerDriver = generateResult.driver else {
                let error: BrotherPrintError = BrotherPrintError(error: generateResult.error)
                self.notifyListeners(BrotherPrinterEvent.onPrintFailedCommunication.rawValue, data: error.toJSON())
                call.reject(error.message, error.code)
                return
            }

            guard
                let decodedByte = UIImage(data: newImageData! as Data)
            else {
                printerDriver.closeChannel()
                call.reject("Failed to decode image", "5")
                return
            }

            var printSettings: BRLMPrintSettingsProtocol

            if modelName.hasPrefix("QL") {
                guard
                    let _printSettings = BRLMQLPrintSettings(defaultPrintSettingsWith: printerModel)
                else {
                    printerDriver.closeChannel()
                    self.notifyListeners(BrotherPrinterEvent.onPrintFailedCommunication.rawValue, data: [
                        "code": 0,
                        "message": "Error - Create BRLMQLPrintSettings with " + modelName + " is failed."
                    ])
                    call.reject("Error - Create BRLMQLPrintSettings with " + modelName + " is failed.")
                    return
                }
                printSettings = PrinterSettingsModel.QLModelSettings(call, printSettings: _printSettings)
            } else if modelName == "TD_2120N" {
                guard
                    let _printSettings = BRLMTDPrintSettings(defaultPrintSettingsWith: printerModel)
                else {
                    printerDriver.closeChannel()
                    self.notifyListeners(BrotherPrinterEvent.onPrintFailedCommunication.rawValue, data: [
                        "code": 0,
                        "message": "Error - Create BRLMTDPrintSettings with " + modelName + " is failed."
                    ])
                    call.reject("Error - Create BRLMTDPrintSettings with " + modelName + " is failed.")
                    return
                }
                _printSettings.customPaperSize = BRLMCustomPaperSize.init(file: Bundle(for: BrotherPrint.self).url(forResource: "TD2120N-RD51_26mm", withExtension: "bin").unsafelyUnwrapped)
                printSettings = _printSettings
            } else if modelName.hasPrefix("TD") {
                call.reject("Wrong configuration")
                return
                guard
                    let _printSettings = BRLMTDPrintSettings(defaultPrintSettingsWith: printerModel)
                else {
                    printerDriver.closeChannel()
                    self.notifyListeners(BrotherPrinterEvent.onPrintFailedCommunication.rawValue, data: [
                        "code": 0,
                        "message": "Error - Create BRLMTDPrintSettings with " + modelName + " is failed."
                    ])
                    call.reject("Error - Create BRLMTDPrintSettings with " + modelName + " is failed.")
                    return
                }
                printSettings = PrinterSettingsModel.TDModelSettings(call, printSettings: _printSettings)

            } else {
                printerDriver.closeChannel()
                call.reject(modelName + " is not supported", "6")
                return
            }

            let printError = printerDriver.printImage(with: decodedByte.cgImage!, settings: printSettings)

            if printError.code != BRLMPrintErrorCode.noError {
                printerDriver.closeChannel()
                let error: BrotherPrintError = BrotherPrintError(error: printError)
                logger.debug("error with message \(error.message) and code \(error.code)")
                self.notifyListeners(BrotherPrinterEvent.onPrintError.rawValue, data: error.toJSON())
                call.reject(error.message, error.code)
                return
            }

            printerDriver.closeChannel()
            
            self.notifyListeners(BrotherPrinterEvent.onPrint.rawValue, data: [:])
            call.resolve()
        }
    }
    
    @objc func getPrinterStatus(_ call: CAPPluginCall) {
        let port: String = call.getString("port", "wifi")
        let channelInfo: String = call.getString("channelInfo", "")
        
        let logger = Logger(subsystem: "Komia", category: "App")
        
        DispatchQueue.global().async {
            var channel: BRLMChannel
            
            switch port {
            case "wifi":
                channel = BRLMChannel(wifiIPAddress: channelInfo)
            case "bluetooth":
                channel = BRLMChannel(bluetoothSerialNumber: channelInfo)
            case "bluetoothLowEnergy":
                channel = BRLMChannel(bleLocalName: channelInfo)
            default:
                DispatchQueue.main.async {
                    call.reject("\(port) is not supported", "17")
                }
                return
            }
            
            let generateResult = BRLMPrinterDriverGenerator.open(channel)
            guard generateResult.error.code == BRLMOpenChannelErrorCode.noError,
                  let printerDriver = generateResult.driver else {
                let error = BrotherPrintError(error: generateResult.error)
                DispatchQueue.main.async {
                    self.notifyListeners(BrotherPrinterEvent.onPrintFailedCommunication.rawValue, data: [
                        "message": error.message,
                        "code": error.code
                    ])
                    call.reject(error.message, error.code)
                }
                return
            }
            
            let statusResponse = printerDriver.getPrinterStatus()
            printerDriver.closeChannel()
            
            guard let status = statusResponse.status else {
                let error = BrotherPrintError(error: statusResponse.error)
                DispatchQueue.main.async {
                    call.reject(error.message, error.code)
                }
                return
            }
            
            var success: Bool = false
            if let value = status.mediaInfo?.getQLLabelSize(&success) {
                let labelSize: String = PrintErrorModel.fetchQLLabelSize(size: value)
                logger.trace("\(labelSize)")
                
                DispatchQueue.main.async {
                    self.notifyListeners(BrotherPrinterEvent.onPrinterStatus.rawValue, data: [
                        "mediaInfo": [
                            "labelSize": labelSize
                        ]
                    ])
                }
            }
            
            logger.error("Reported status: \(String(describing: status.debugDescription))")
            
            DispatchQueue.main.async {
                call.resolve()
            }
        }
    }

    @objc func search(_ call: CAPPluginCall) {
        switch call.getString("port", "wifi") {
        case "wifi":
            self.searchWiFiPrinter(call)
        case "bluetooth":
            self.checkBLEChannel(call)
        case "bluetoothLowEnergy":
            self.searchBLEPrinter(call)
        default:
            call.reject("port is not 'wifi' | 'bluetooth' | 'bluetoothLowEnergy'")
        }
    }

    private func searchWiFiPrinter(_ call: CAPPluginCall) {
        DispatchQueue.global().async {
            self.cancelRoutineWiFi = {
                BRLMPrinterSearcher.cancelNetworkSearch()
            }

            let option = BRLMNetworkSearchOption()
            option.printerList = PrinterModel.allCases.map { $0.searchModelName }
            option.searchDuration = TimeInterval(call.getInt("searchDuration", 15))

            NSLog("BRLMPrinterSearcher.startNetworkSearch")
            BRLMPrinterSearcher.startNetworkSearch(option) { channel in
                NSLog(channel.channelInfo)
                let printer = self.chanelToPrinter(port: "wifi", channel: channel)
                self.notifyListeners(BrotherPrinterEvent.onPrinterAvailable.rawValue, data: printer)
            }
            self.cancelRoutineWiFi = nil
            call.resolve()
        }
    }

    private func checkBLEChannel(_ call: CAPPluginCall) {
        DispatchQueue.global().async {
            let searcher = BRLMPrinterSearcher.startBluetoothSearch()
            if searcher.error.code != BRLMPrinterSearchErrorCode.noError {
                call.reject("Error - startBluetoothSearch: " + PrinterSearchErrorModel.fetchChannelErrorCode(error: searcher.error.code))
                return
            }
            for channel in searcher.channels {
                NSLog(channel.channelInfo)
                self.notifyListeners(BrotherPrinterEvent.onPrinterAvailable.rawValue, data: self.chanelToPrinter(port: "bluetooth", channel: channel))
            }
            call.resolve()
        }

        //        BRLMPrinterSearcher.startBluetoothAccessorySearch() { searcher in
        //            for channel in searcher.channels {
        //                self.notifyListeners(BrotherPrinterEvent.onPrinterAvailable.rawValue, data: self.chanelToPrinter(port: "bluetooth", channel: channel))
        //            }
        //            call.resolve()
        //        }
    }

    private func searchBLEPrinter(_ call: CAPPluginCall) {
        DispatchQueue.global().async {
            self.cancelRoutineBluetooth = {
                BRLMPrinterSearcher.cancelBLESearch()
            }
            let option = BRLMBLESearchOption()
            option.searchDuration = TimeInterval(call.getInt("searchDuration", 15))
            NSLog("BRLMPrinterSearcher.startBLESearch")
            BRLMPrinterSearcher.startBLESearch(option) { channel in
                self.notifyListeners(BrotherPrinterEvent.onPrinterAvailable.rawValue, data: self.chanelToPrinter(port: "bluetoothLowEnergy", channel: channel))
            }
            self.cancelRoutineBluetooth = nil
            call.resolve()
        }
    }

    private func chanelToPrinter(port: String, channel: BRLMChannel) -> JSObject {
        let modelName = channel.extraInfo?.value(forKey: BRLMChannelExtraInfoKeyModelName) as? String ?? ""
        let serialNumber = channel.extraInfo?.value(forKey: BRLMChannelExtraInfoKeySerialNumber) as? String ?? ""
        let macAddress = channel.extraInfo?.value(forKey: BRLMChannelExtraInfoKeyMacAddress) as? String ?? ""
        let nodeName = channel.extraInfo?.value(forKey: BRLMChannelExtraInfoKeyNodeName) as? String ?? ""
        let location = channel.extraInfo?.value(forKey: BRLMChannelExtraInfoKeyLocation) as? String ?? ""
        let ipaddress = channel.channelInfo

        return [
            "port": port,
            "modelName": modelName,
            "serialNumber": serialNumber,
            "macAddress": macAddress,
            "nodeName": nodeName,
            "location": location,
            "channelInfo": ipaddress
        ]
    }

    @objc func cancelSearchWiFiPrinter(_ call: CAPPluginCall) {
        DispatchQueue.global().async {
            self.cancelRoutineWiFi?()
            self.cancelRoutineWiFi = nil
        }
    }

    @objc func cancelSearchBluetoothPrinter(_ call: CAPPluginCall) {
        DispatchQueue.global().async {
            self.cancelRoutineBluetooth?()
            self.cancelRoutineBluetooth = nil
        }
    }
}
