//
//  KMOCROperation.swift
//  PDF Reader Pro
//
//  Created by liujiajie on 2023/11/15.
//

import Foundation


let  KMOCR_API_URL = "https://api.convertio.co/convert"

#if VERSION_DMG
let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
#else
let KMOCR_API_KEY = "d1a0c1a4cc7fcac5eb08730570217f97"
#endif

let KMTIMER_MAXIMUM_CYCLE = 10

@objc(KMOCROperationDelegate)
protocol KMOCROperationDelegate: AnyObject {
    @objc optional func OCROperationCancel(_ op: KMOCROperation, atIndex: Int)
    @objc optional func OCROperationStart(_ op: KMOCROperation, atIndex: Int)
    @objc optional func OCROperationFinish(_ op: KMOCROperation, atIndex: Int, results: Array<Any>)
    @objc optional func OCROperationFail(_ op: KMOCROperation, atIndex: Int, err: Error?)
}

@objcMembers class KMOCROperation: Operation{
    var operationDelegate: KMOCROperationDelegate?
    var selectedLanguages: Array<String>?
    var fileType: String = ""
    var filePath: String = ""
    var timerCycleCount = 0
    var imageIndex = 0
    var task: URLSessionDataTask?
    var fileID: String?
    var fileName: String = ""
    var orcImage: NSImage?
    var fileURL:URL?
    override var isExecuting: Bool{
        set{
            self.willChangeValue(forKey: "isExecuting")
            didChangeValue(forKey: "isExecuting")
        }
        get{
            return super.isExecuting
        }
    }
    override var isFinished: Bool{
        set{
            self.willChangeValue(forKey: "isFinished")
            self.isFinished = newValue
            didChangeValue(forKey: "isFinished")
        }
        get{
            return super.isFinished
        }
    }
    override var isCancelled: Bool{
        set{
            self.willChangeValue(forKey: "isCancelled")
            self.isCancelled = newValue
            didChangeValue(forKey: "isCancelled")
        }
        get{
            return super.isCancelled
        }
    }
    init(recognitionImg:NSImage, imgIndex:Int) {
        super.init()
        self.timerCycleCount = 0
        self.imageIndex = imgIndex
        self.orcImage = recognitionImg
        self.fileName = fileNameWithDate()
        self.fileID = nil
        self.fileURL = nil
        self.queuePriority = .normal
        self.name = self.fileName
        self.isExecuting = false
        self.isFinished = false
    }
    func fileNameWithDate() -> String {
        let formatter = DateFormatter()
        formatter.dateFormat = "YYYY-MM-dd-hh-mm-ss-SSS"
        let dateString = formatter.string(from: Date())
        let fileName = "\(dateString) \(imageIndex)"
        return fileName
    }
    override func start() {
        if p_checkCancelled() { return }
        isExecuting = true
        
        Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
    }
    @objc override func main() {
        do {
            if p_checkCancelled() { return }
            recognitionImage(image: orcImage ?? NSImage())
            while isExecuting {
                if p_checkCancelled() {
                    return
                }
            }
        } catch let e {
            Swift.debugPrint("Exception %@", e)
        }
    }
    override func cancel() {
        super.cancel()
        if task != nil {
            task?.cancel()
            task = nil
        }
        
        self.operationDelegate?.OCROperationCancel?(self, atIndex: self.imageIndex)
        
        if isExecuting {
            isExecuting = false
            isFinished = true
        } else {
            isFinished = false
        }
        isCancelled = true
    }
    func p_done() {
        isExecuting = false
        isFinished = true
    }
    func p_checkCancelled() -> Bool {
        if isCancelled {
            isFinished = true
            return true
        }
        return false
    }
    
    func recognitionImage(image: NSImage) { 
        guard let binaryImageData = base64EncodeImage(image) else { return }
        let url = URL(string: KMOCR_API_URL)!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let paramsDictionary: [String: Any] = [
            "apikey": KMOCR_API_KEY,
            "input": "base64",
            "file": binaryImageData,
            "outputformat": fileType,
            "filename": "1.pdf",
            "options": [
                "ocr_enabled": true,
                "ocr_settings": [
                    "langs": selectedLanguages
                ]
            ]
        ]
        
        let requestData = try? JSONSerialization.data(withJSONObject: paramsDictionary, options: [])
        request.httpBody = requestData
        
        let URLSession = URLSession.shared
        let convertTask = URLSession.dataTask(with: request) { [self] (data, response, error) in
            guard let data = data else {
                return
            }
            
            if (error as NSError?)?.code == NSURLErrorCancelled {
                return
            }
            
            guard let dictionary = try? JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String: Any] else {
                return
            }
            
            guard let statusStr = dictionary["status"] as? String else {
                return
            }
            
            if statusStr == "ok" {
                let dic = (dictionary["data"] as? NSDictionary)
                self.fileID = dic?["id"] as? String
                DispatchQueue.main.asyncAfter(deadline: .now() + 15.0) {
                    self.requestQuery()
                }
            } else {
                self.cancel()
                operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
            }
        }
        convertTask.resume()
    }
    func errorWithContent(content: String) -> NSError {
        let domain = "com.MyCompany.MyApplication.ErrorDomain"
        let userInfo = [NSLocalizedDescriptionKey: content]
        let error = NSError(domain: domain, code: -101, userInfo: userInfo)
        return error
    }
    func requestQuery() { 
        let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/status"
        guard let url = URL(string: urlString) else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        let URLSession = URLSession.shared
        let task = URLSession.dataTask(with: request) { [self] (data, response, error) in
            if (error as NSError?)?.code == NSURLErrorCancelled {
                return
            }
            
            guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
                return
            }
            
            guard let statusStr = dictionary["status"] as? String else {
                return
            }
            
            let dic = (dictionary["data"] as? NSDictionary)
            self.fileID = dic?["id"] as? String
            
            if statusStr == "ok" {
                if let task = self.task {
                    task.cancel()
                    self.task = nil
                }
                
                if let fileData = dictionary["data"] as? [String: Any], let stepPercent = fileData["step_percent"] as? Int {
                    if stepPercent == 100 {
                        let dic = (fileData["output"] as? NSDictionary)
                        if let fileURLString = dic?["url"] as? String, let fileURL = URL(string: fileURLString) {
                            self.fileURL = fileURL
                            self.getResultFileContent()
                        }
                    } else {
                        if self.timerCycleCount >= KMTIMER_MAXIMUM_CYCLE {
                            self.cancel()
                            operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
                        } else {
                            self.timerCycleCount += 1
                            DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
                                self.requestQuery()
                            }
                        }
                    }
                }
            } else {
                self.cancel()
                operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
            }
        }
        task.resume()
    }
    
    func getResultFileContent() { 
        let urlString = "\(KMOCR_API_URL)/\(fileID ?? "")/dl"
        guard let url = URL(string: urlString) else { return }
        var request = URLRequest(url: url)
        request.httpMethod = "GET"
        
        let URLSession = URLSession.shared
        let fileContentTask = URLSession.dataTask(with: request) { (data, response, error) in
            if (error as NSError?)?.code == NSURLErrorCancelled {
                return
            }
            
            var fileData: Data?
            guard let dictionary = try? JSONSerialization.jsonObject(with: data!, options: .mutableLeaves) as? [String: Any] else {
                return
            }
            
            guard let statusStr = dictionary["status"] as? String else {
                return
            }
            
            if statusStr == "ok" {
                fileData = self.responseDataResults((dictionary as NSDictionary) as! [String : Any])
            } else {
                // err = self.errorWithContent(dictionary["error"])
            }
            
            DispatchQueue.main.async { [self] in
                if let error = error, fileData == nil {
                    self.cancel()
                    if let err = error as NSError? {
                        operationDelegate?.OCROperationFail?(self, atIndex: self.imageIndex, err: error)
                    }
                } else {
                    if let fileData = fileData {
                        let arr = [fileData]
                        if let delegate = operationDelegate {
                            delegate.OCROperationFinish?(self, atIndex: self.imageIndex, results: arr)
                        }
                        self.p_done()
                    }
                }
            }
        }
        fileContentTask.resume()
    }
    
    func base64EncodeImage(_ image: NSImage) -> String? {
        guard let data = image.tiffRepresentation else { return nil }
        let imageRep = NSBitmapImageRep(data: data)!
        imageRep.size = image.size
        let imageData = imageRep.representation(using: .png, properties: [:])
        // Resize the image if it exceeds the 4MB API limit
        if imageData?.count ?? 0 > 4194304 {
            let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
            if let data = compressedData {
                return data.base64EncodedString(options: .endLineWithCarriageReturn)
            }
        }
        
        if let data = imageData {
            if #available(macOS 10.9, *) {
                return data.base64EncodedString(options: .endLineWithCarriageReturn)
            } else {
                return data.base64EncodedString(options: [])
            }
        }
        
        return nil
    }
    
    func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
        var compression: CGFloat = 0.9
        let maxCompression: CGFloat = 0.1
        var compressImageData = imageData
        while compressImageData.count > maxFileSize && compression > maxCompression {
            compression -= 0.1
            let imageRep = NSBitmapImageRep(data: compressImageData)!
            compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
        }
        return compressImageData
    }
    func responseDataResults(_ dictionary: [String: Any]) -> Data? { 
        guard let responses = dictionary["data"] as? [String: Any] else { return nil }
        guard let stringBase64 = responses["content"] as? String else {
            return nil
        }
        
        guard let data = Data(base64Encoded: stringBase64, options: .ignoreUnknownCharacters) else {
            return nil
        }
        
        try? data.write(to: URL(fileURLWithPath: filePath), options: .atomic)
        return data
    }
}