//
//  KMBatchAddHeaderFooterOperation.swift
//  PDF Reader Pro
//
//  Created by liujiajie on 2023/11/7.
//

import Foundation

let supportDirectory = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true).last
let mainBundleIdentifier = Bundle.main.bundleIdentifier ?? ""
let kTempSavePath = supportDirectory?.stringByAppendingPathComponent(mainBundleIdentifier)

class KMBatchAddHeaderFooterOperation: KMBatchOperation{
    var headerFooter: KMHeaderFooterObject?
    var pdfDocument: CPDFDocument?
    var password: String?
    
    init(file: KMBatchOperateFile, headerFooter: KMHeaderFooterObject) {
        super.init(file: file)
        self.headerFooter = headerFooter
        
    }
    func currentParameter() -> KMBatchBaseParameter {
        if headerFooter!.isBates{
            return operateFile!.addBatesInfo
        }
        return operateFile!.addHeaderFooterInfo
    }
    override func start() {
        self.pdfDocument = CPDFDocument(url: URL(fileURLWithPath: self.operateFile?.filePath ?? ""))
        self.password = self.operateFile?.password
        if let data = self.pdfDocument?.isLocked, data {
            self.pdfDocument?.unlock(withPassword: self.operateFile?.password)
        }
        if !self.isCancelled {
            self.delegate?.fileBeginOperate?(self.operateFile!, info: self.currentParameter())
            willChangeValue(forKey: "isExecuting")
            self.hasExcuting = true
            didChangeValue(forKey: "isExecuting")
            if !FileManager.default.fileExists(atPath: self.operateFile!.filePath) {
                self.delegate?.fileOperateFailed?(self.operateFile!, error: self.errorWithMsg(KMLocalizedString("File Not Exist", nil)), info: self.operateFile!.removeBatesInfo)
                
                self.willChangeValue(forKey: "isFinished")
                self.hasFinished = true
                self.didChangeValue(forKey: "isFinished")
                return
            }
            
            if self.pdfDocument == nil {
                self.delegate?.fileOperateFailed?(self.operateFile!, error: self.errorWithMsg(KMLocalizedString("An error occurred while opening this document. The file is damaged and could not be repaired.", nil)), info: self.operateFile!.removeWatermarkInfo)
                self.willChangeValue(forKey: "isFinished")
                self.hasFinished = true
                self.didChangeValue(forKey: "isFinished")
                return
            }
            
            if !self.pdfDocument!.allowsPrinting || !self.pdfDocument!.allowsCopying {
                self.delegate?.fileOperateFailed?(self.operateFile!, error: self.errorWithMsg(KMLocalizedString("This is a secured document. Editing is not permitted.", nil)), info: self.operateFile!.removeWatermarkInfo)
                self.willChangeValue(forKey: "isFinished")
                self.hasFinished = true
                self.didChangeValue(forKey: "isFinished")
                return
            }
            self.saveAsPDFToPath((self.operateFile?.currentOperateInfo?.fetchDestinationFilepath())!)
            
        }else {
            willChangeValue(forKey: "isFinished")
            willChangeValue(forKey: "isExecuting")
            hasExcuting = false
            hasFinished = true
            didChangeValue(forKey: "isExecuting")
            didChangeValue(forKey: "isFinished")
        }
    }
    override func cancel() {
        //        super.cancel()
        if isExecuting {
            operateFile!.removeWatermarkInfo.status = .Waiting
            if FileManager.default.fileExists(atPath: self.currentParameter().outPutPath!) { try? FileManager.default.removeItem(atPath: self.currentParameter().outPutPath!)
            }
            self.delegate?.fileOperateCanceled?(self.operateFile!, info: self.currentParameter())
            
            willChangeValue(forKey: "isFinished")
            hasFinished = true
            didChangeValue(forKey: "isFinished")
        } else {
            willChangeValue(forKey: "isCancelled")
            hasCanceled = true
            didChangeValue(forKey: "isCancelled")
        }
    }
    
    func saveAsPDFToPath(_ path: String) {
        var filePath = self.pdfDocument?.documentURL?.path
        let password = self.password
        if filePath == nil {
            let str = String(format: "%@.pdf", KMLocalizedString("Untitled", nil))
            let writeSuccess = self.pdfDocument!.write(to: URL(fileURLWithPath: (kTempSavePath?.stringByAppendingPathComponent(str))!))
            if writeSuccess {
                let newDocument: CPDFDocument = CPDFDocument(url: URL(fileURLWithPath: (kTempSavePath?.stringByAppendingPathComponent(str))!))
                filePath = newDocument.documentURL?.path
            } else {
                NSSound.beep()
                return
            }
        }
        
        guard let document = CPDFDocument(url: URL(fileURLWithPath: filePath!)) else {
            return
        }
        
        if password?.count ?? 0 > 0 {
            document.unlock(withPassword: password)
        }
        
        let font = NSFont.boldSystemFont(ofSize: self.headerFooter?.getTextFontSize() ?? 16)
        let style = NSMutableParagraphStyle()
        style.alignment = .center
        style.lineBreakMode = .byCharWrapping
        var dictionary = [NSAttributedString.Key: Any]()
        dictionary[.paragraphStyle] = style
        dictionary[.font] = font
        let size = "text".boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT)), options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: dictionary).size
        
        if self.headerFooter!.isBates {
            var bates: CPDFHeaderFooter = document.bates()
            bates.margin = NSEdgeInsets(top: max(CGFloat(self.headerFooter!.topMargin) - size.height, 0), left: CGFloat(self.headerFooter!.leftMargin), bottom: max(CGFloat(self.headerFooter!.bottomMargin) - size.height, 0), right: CGFloat(self.headerFooter!.rightMargin))
            
            var arr = [NSNumber]()
            for i in 0..<(self.operateFile?.addBatesInfo.pagesArray?.count ?? 0) {
                var tmp = self.operateFile?.addBatesInfo.pagesArray?[i].intValue
                tmp! -= 1
                let number = NSNumber(value: tmp ?? 0)
                arr.append(number)
            }
            if arr.count < 1 {
                let error = NSError(domain: "LocalError", code: 0, userInfo: [NSLocalizedDescriptionKey: KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", nil)])
                self.delegate?.fileOperateFailed?(self.operateFile!, error: error, info: self.operateFile!.addBatesInfo)
                
                willChangeValue(forKey: "isFinished")
                self.hasFinished = true
                didChangeValue(forKey: "isFinished")
                return
            }
            let pagesString = arr.map{ "\($0)" }.joined(separator: ",")
            if pagesString.count > 0 {
                bates.pageString = pagesString
            } else {
                let pageString = String(format: "0-%ld", document.pageCount-1)
                bates.pageString = pageString
            }
            
            let topLeftString = self.headerFooter?.topLeftString
            let topCenterString = self.headerFooter?.topCenterString
            let topRightString = self.headerFooter?.topRightString
            let bottomLeftString = self.headerFooter?.bottomLeftString
            let bottomCenterString = self.headerFooter?.bottomCenterString
            let bottomRightString = self.headerFooter?.bottomRightString
            let items = [topLeftString, topCenterString, topRightString, bottomLeftString, bottomCenterString, bottomRightString]
            for i in 0..<items.count {
                let text = items[i]
                bates.setText(text, at: UInt(Int(i)))
                bates.setTextColor(self.headerFooter?.getTextColor(), at: UInt(Int(i)))
                bates.setFontSize(self.headerFooter?.getTextFontSize() ?? 16.0, at: UInt(Int(i)))
            }
            
            bates.update()
        } else {
            let headerFooterNew = document.headerFooter()
            headerFooterNew?.margin = NSEdgeInsets(top: max(CGFloat(self.headerFooter!.topMargin)-size.height, 0), left: CGFloat(self.headerFooter!.leftMargin), bottom: max(CGFloat(self.headerFooter!.bottomMargin)-size.height, 0), right: CGFloat(self.headerFooter!.rightMargin))
            
            var arr = [NSNumber]()
            for i in 0..<(self.operateFile?.addHeaderFooterInfo.pagesArray?.count ?? 0) {
                var tmp = self.operateFile?.addHeaderFooterInfo.pagesArray?[i].intValue
                tmp! -= 1
                let number = NSNumber(value: tmp ?? 0)
                arr.append(number)
            }
            if arr.count < 1 {
                let error = NSError(domain: "LocalError", code: 0, userInfo: [NSLocalizedDescriptionKey: KMLocalizedString("Invalid page range or the page number is out of range. Please try again.", nil)])
                self.delegate?.fileOperateFailed?(self.operateFile!, error: error, info: self.operateFile!.addHeaderFooterInfo)
                willChangeValue(forKey: "isFinished")
                self.hasFinished = true
                didChangeValue(forKey: "isFinished")
                return
            }
            let pagesString = arr.map{ "\($0)" }.joined(separator: ",")
            if pagesString.count > 0 {
                headerFooterNew?.pageString = pagesString
            } else {
                let pageString = String(format: "0-%ld", document.pageCount-1)
                headerFooterNew?.pageString = pageString
            }
            let num: Int = Int(self.headerFooter?.startString ?? "0") ?? 0
            let pageCount = Int(document.pageCount) + num - 1
            
            let topLeftString = convertPageFormat(oldString: self.headerFooter!.topLeftString, startPage: self.headerFooter!.startString,pageCount: "\(pageCount)")
            let topCenterString = convertPageFormat(oldString: self.headerFooter!.topCenterString, startPage: self.headerFooter!.startString,pageCount: "\(pageCount)")
            let topRightString = convertPageFormat(oldString: self.headerFooter!.topRightString, startPage: self.headerFooter!.startString,pageCount: "\(pageCount)")
            let bottomLeftString = convertPageFormat(oldString: self.headerFooter!.bottomLeftString, startPage: self.headerFooter!.startString, pageCount: "\(pageCount)")
            let bottomCenterString = convertPageFormat(oldString: self.headerFooter!.bottomCenterString, startPage: self.headerFooter!.startString,pageCount: "\(pageCount)")
            let bottomRightString = convertPageFormat(oldString: self.headerFooter!.bottomRightString, startPage: self.headerFooter!.startString,pageCount: "\(pageCount)")
            let items = [topLeftString, topCenterString, topRightString, bottomLeftString, bottomCenterString, bottomRightString]
            for i in 0..<items.count {
                let text = items[i]
                headerFooterNew?.setText(text, at: UInt(i))
                headerFooterNew?.setTextColor(self.headerFooter!.getTextColor(), at: UInt(Int(i)))
                headerFooterNew?.setFontSize(self.headerFooter!.getTextFontSize(), at: UInt(Int(i)))
            }
            
            headerFooterNew?.update()
        }
        
        let documentPath = NSTemporaryDirectory()
        let tempPath = documentPath.appending(filePath!.lastPathComponent)
        if FileManager.default.fileExists(atPath: tempPath) {
            try? FileManager.default.removeItem(atPath: tempPath)
        }
        
        let result = document.write(to: URL(fileURLWithPath: tempPath))
        if result {
            if FileManager.default.fileExists(atPath: path) {
                try? FileManager.default.removeItem(atPath: path)
            }
            try? FileManager.default.moveItem(atPath: tempPath, toPath: path)
        } else {
            try? FileManager.default.removeItem(atPath: tempPath)
        }
        if result {
            self.delegate?.fileOperateSuccessed?(self.operateFile!, info: self.currentParameter())
        } else {
            self.delegate?.fileOperateFailed?(self.operateFile!, error: self.defaultError(), info: self.currentParameter())
        }
        
        willChangeValue(forKey: "isFinished")
        self.hasFinished = true
        didChangeValue(forKey: "isFinished")
    }
    func defaultError() -> NSError {
        return errorWithMsg(NSLocalizedString("Failed", comment: ""))
    }
}

func convertPageFormat(oldString: String, startPage: String, pageCount: String) -> String {
    let pageFormatArray = ["1", "1 of n", "1/n", "Page 1", "Page 1 of n"]
    var newString = oldString
    for pageFormat in pageFormatArray {
        let format = "<<(pageFormat)>>"
        if newString.contains(format) {
            var tString: String? = nil
            if pageFormat == "1" {
                tString = "<<\(startPage)>>"
            } else if pageFormat == "1 of n" {
                tString = "\(startPage) of \(pageCount)"
            } else if pageFormat == "1/n" {
                tString = "\(startPage)/\(pageCount)"
            } else if pageFormat == "Page 1" {
                tString = "Page <<\(startPage)>>"
            } else if pageFormat == "Page 1 of n" {
                tString = "Page <<\(startPage)>> of \(pageCount)"
            }
            
            newString = newString.replacingOccurrences(of: format, with: tString ?? "")
        }
    }
    
    newString = convertDateFormat(oldString: newString)
    return newString
}
func convertDateFormat(oldString: String) -> String { 
    var newString = oldString
    for dateFormat in KMHeaderFooterManager.defaultManager.dateFormatArray {
        if newString.contains(dateFormat) {
            let formatString = dateFormat.replacingOccurrences(of: "m", with: "M")
            let replace = "<<\(dateFormat)>>"
            
            let date = Date()
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = formatString
            let dateString = dateFormatter.string(from: date)
            newString = newString.replacingOccurrences(of: replace, with: dateString)
        }
    }
    
    return newString
}