//
//  AutoTest.swift
//  KdanAuto
//
//  Created by 朱东勇 on 2022/11/25.
//

import Foundation
import AppKit

var cacheObjects = NSMutableDictionary()

class AutoTest : NSObject, AutoTestProtocal {
    var reportString : NSMutableAttributedString? = nil
    
    public var _status :  AutoTestStatus = .Normal
    
    var _fileType : String = "RTF"
    var _type : String = "Others"
    var _extention : String = "rtf"
    
    var _degree : Double = 0.0
    var _fileDegreeInfo : [String:Double] = [:]
    
    class func autoTestFor(_ fileType:NSString ,type:NSString) -> AutoTest? {
        let key = String(fileType) + "." + String(type)
        
        if cacheObjects.value(forKey: key) != nil {
            let object = cacheObjects.value(forKey: key)
            
            return object as? AutoTest
        }
        
//        if  let cacheObject as AutoTest {
//            return cacheObject
//        }
        
        let fileTypes = testTypeInfo[fileType] as! NSArray
       
        let clsname = "KdanAuto"//Bundle.main.infoDictionary! ["CFBundleExecutable"]
    
        for item in fileTypes {
            let cItem = item as! NSDictionary
            
            let cType = cItem["Type"] as! NSString
            let cExtention = cItem["Extention"] as! NSString
            if (cType.isEqual(to: type)) {
                let className = String((clsname )+"."+(cItem["Class"] as! String)) as! String
                let cl = NSClassFromString(className) as! AutoTest.Type
                
                let object = cl.shared()
                object?._fileType = fileType as! String
                object?._type = String(cType)
                object?._extention = String(cExtention)
                cacheObjects.setValue(object, forKey: key)
                
                return object
            }
        }
        
        let object = AutoTest.shared()
        
        object?._fileType = fileType as String
        object?._type = String(cType)
        object?._extention = ""
        cacheObjects.setValue(object, forKey: key)
        
        return object
    }
    
    class func shared() -> AutoTest? {
        return AutoTest()
    }
    
    func fileType() -> String {
        return _fileType
    }
    
    func type() -> String {
        return _type
    }
    
    func name() -> String {
        return "未指定类型对照测试"
    }
    
    func keys() -> NSArray {
        return ["快照"]
    }
    
    func selectedKeys() -> NSArray {
        let userDefaults = UserDefaults.standard
        
        let key = self.fileType() + "." + self.type() + ".selectedKeys"
        
        if userDefaults.value(forKey: key) != nil {
            return userDefaults.value(forKey: key) as! NSArray
        }
        
        self.setSelectedKeys(self.keys())
        
        return self.keys()
    }
    
    func setSelectedKeys(_ keys: NSArray) {
        let userDefaults = UserDefaults.standard
        
        let key = self.fileType() + "." + self.type() + ".selectedKeys"
        
        userDefaults.setValue(keys, forKey: key)
        userDefaults.synchronize()
    }
    
    func status() -> AutoTestStatus {
        return _status
    }
    
    func setStatus(_ status:AutoTestStatus) {
        _status = status
    }
    
    // Auto Test
    func autoTest() {
        clearCacheFiles()
        
        if hasOriginFile() {
            let needCompare = self.selectedKeys().contains("快照")
            
            if !needCompare {
                _status = .Finished
                return
            }
            
            _status = .Process
            reportString = NSMutableAttributedString.init(string: "\n【\(String(self.fileType())) - \(self.name())】快照比对开始!\n",
                                                          attributes:[.foregroundColor : NSColor.blue])
            let files = DataModel.shared.originFilesFor(_fileType, type: _type) as [String]
            
            let checkDirectory = self.checkFileDirectory()
            let originDirectory = self.originFileDirectory()
            let resultDirectory = self.resultFileDirectory()
            
            var tDegree = Double(0);
            var tCount = Int(0)
            
            for fileName in files {
                let fName = NSString(string: fileName).deletingPathExtension
                let originPath = NSString(string: originDirectory).appendingPathComponent(fName+".pdf")
                let resultPath = NSString(string: resultDirectory).appendingPathComponent(fName+"."+_extention)
                let checkPath = NSString(string: checkDirectory).appendingPathComponent(fName+"."+_extention)
                
                
                reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】开始转换文件 \"\(fName)\"\n",
                                                                    attributes:[.foregroundColor : NSColor.blue]))
                // ...
                // 执行转换过程
                let convertSemaphore = DispatchSemaphore(value: 0)
                var success = false
                let convertQueue = DispatchQueue.global()
                convertQueue.async {
                    success = FileConverter.shared().converter(originPath, inDesPath: resultPath)
                    
                    convertSemaphore.signal()
                }
                convertSemaphore.wait()
                
                
                var isDirectory = ObjCBool(false)
                if FileManager.default.fileExists(atPath: resultPath, isDirectory:&isDirectory) && success {
                    // compare screenshoot between result file with check file
                    if needCompare {
                        let items = NSMutableArray()
                        if (isDirectory.boolValue) {
                            let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultPath)
                            for item in NSArray(array: searchItems) {
                                let ext = NSString(string: item as! String).pathExtension.lowercased()
                                if NSArray(array: [_extention]).contains(ext) {
                                    let fileName = NSString(string: fName+"."+_extention+"/\(item as! String)").deletingPathExtension
                                    items.add(fileName)
                                }
                            }
                        }else {
                            items.add(fName)
                        }
                        
                        var subDegree = Double(0);
                        var subCount = Int(0)
                        
                        for item in items {
                            let subFileName = item as! String
                            let subResultPath = NSString(string: resultDirectory).appendingPathComponent(subFileName+"."+_extention)
                            
                            reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subFileName+".\(_extention)")\"快照生成中\n",
                                                                                attributes:[.foregroundColor : NSColor.black]))
                            let rComparePath = NSString(string: resultDirectory).appendingPathComponent(subFileName+".jpg")
                            let cComparePath = NSString(string: checkDirectory).appendingPathComponent(subFileName+".jpg")
                            
                            
                            let processThumbSemaphore = DispatchSemaphore(value: 0)
                            var processSuccess = false
                            let thumbnailQueue = DispatchQueue.global()
                            thumbnailQueue.async {
                                processSuccess = ProcessThumbnal.process(subResultPath, desPath: rComparePath, outputSize: CGSize.init(width: 2048, height: 2048))
                                
                                if ( processSuccess &&
                                     FileManager.default.fileExists(atPath: rComparePath)) {
                                    
                                    let degree = self.compareJPEG(rComparePath, checkPath: cComparePath)
                                    
                                    if degree == -1 {
                                        self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照对比失败，生成快照失败或无比对文件\n",
                                                                                            attributes:[.foregroundColor : NSColor.red]))
                                    }else {
                                        var color = NSColor.black
                                        if fabs(degree-100.0) >= 0.01 {
                                            color = NSColor.red
                                        }
                                        subDegree += degree
                                        subCount += 1
                                        
                                        self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照对比完成，图像相似度 \(degree)%\n",
                                                                                            attributes:[.foregroundColor : color]))
                                    }
                                }else {
                                    self.reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(subResultPath)\"快照生成失败\n",
                                                                                        attributes:[.foregroundColor : NSColor.red]))
                                }
                                
                                processThumbSemaphore.signal()
                            }
                            processThumbSemaphore.wait()
                            
                        }
                        
                        if subCount != 0 {
                            subDegree = subDegree/Double(subCount)
                        }else {
                            subDegree = 0.0
                        }
                        
                        _fileDegreeInfo[fileName] = subDegree
                        tDegree += subDegree;
                        tCount += 1
                    }
                }else {
                    reportString?.append(NSMutableAttributedString.init(string: "【\(String(self.fileType())) - \(self.name())】文件 \"\(fName)\"转档失败!\n",
                                                                        attributes:[.foregroundColor : NSColor.red]))
                }
            }
            
            if tCount != 0 {
                _degree = tDegree/Double(tCount)
            }else {
                _degree = 0.0
            }
            
            _status = .Finished
        }else {
            _status = .Normal
        }
    }
    
    
    func testReport() -> NSAttributedString? {
        return reportString
    }
    
    func degree() -> Double {
        return _degree
    }
    
//    func testReportOfFile(_ fileName:String) -> NSAttributedString? {
//        
//    }
    
    func degreeOfFile(_ fileName:String) -> Double {
        if _fileDegreeInfo[fileName] != nil {
            return _fileDegreeInfo[fileName]!
        }else {
            return 0
        }
    }
    
    //  Image compare
    func compareJPEG(_ resultPath:String, checkPath:String) -> Double {
        if !FileManager.default.fileExists(atPath: resultPath) || !FileManager.default.fileExists(atPath: checkPath) {
            return -1
        }
        
        let rImage = NSImage.init(contentsOfFile: resultPath) ?? nil
        let cImage = NSImage.init(contentsOfFile: checkPath) ?? nil
        
        if nil == rImage || nil == cImage {
            return -1
        }
        
        let resultImage = rImage as! NSImage
        let checkImage = cImage as! NSImage
        
        let resultImageRep = NSBitmapImageRep.init(cgImage: resultImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!)
        let checkImageRep = NSBitmapImageRep.init(cgImage: checkImage.cgImage(forProposedRect: nil, context: nil, hints: nil)!)
        
        let rWidth = resultImageRep.pixelsWide
        let rHeight = resultImageRep.pixelsHigh
        let rBitPerPixel = resultImageRep.bitsPerPixel / 8
        let rBytePerRow = resultImageRep.bytesPerRow
        
        let cWidth = checkImageRep.pixelsWide
        let cHeight = checkImageRep.pixelsHigh
        let cBitPerPixel = checkImageRep.bitsPerPixel / 8
        let cBytePerRow = checkImageRep.bytesPerRow
        
        let maxWidth = min(rWidth, cWidth) - 1
        let maxHeight = min(rHeight, cHeight) - 1
        
        // check background color
        // 挑选图片 对角斜线 上的相素进行识别
        var markInfo = NSMutableDictionary.init()
        for w in 0...maxWidth {
            let x = Int(w)
            for mark in 0...1 {
                let color = checkImageRep.colorAt(x: min(x, cWidth-1), y: min(mark == 0 ? x : (maxWidth - x), (cHeight-1))) as! NSColor
                
                let r = Int(color.redComponent*255)
                let g = Int(color.greenComponent*255)
                let b = Int(color.blueComponent*255)
                
                if (markInfo[color] != nil) {
                    let count = (markInfo[color] as? NSNumber)!.intValue ?? 0
                    
                    markInfo[color] = NSNumber.init(value: count + 1)
                }else {
                    markInfo[color] = NSNumber.init(value: 1)
                }
            }
        }
        
        var maxCount = Int(0);
        var bgColor : NSColor? = nil
        for color in markInfo.allKeys {
            let count = (markInfo[color] as? NSNumber)!.intValue ?? 0
                         
            if count > maxCount {
                maxCount = count
                bgColor = color as! NSColor
            }
        }
        
        if nil != bgColor {
            NSLog(String("识别到背景色\(bgColor)"))
        }
        
        // Compare
        var equalCount = 0 as Double
        var bgCount = 0 as Double
        for w in 0...maxWidth {
            let x = Int(w)
            
            for h in 0...maxHeight {
                let y = Int(h)
                
                let cColor = checkImageRep.colorAt(x: x, y: y) as! NSColor
                let rColor = resultImageRep.colorAt(x: x, y: y) as! NSColor
                
                if (cColor.isEqual(to: rColor)) {
                    equalCount = equalCount + 1
                    
                    if bgColor != nil &&
                        cColor.isEqual(to: bgColor) {
                        bgCount += 1
                    }
                }
            }
        }
        
        NSLog(String("过滤点数目\(bgCount)"))
        
        return Double(max(equalCount-bgCount, 1)/(max(Double(cWidth) * Double(cHeight)-bgCount, 1)) * 100.0)
    }
    
    // Update Refrence image
    func canUpdateRefImage() -> Bool {
        let files = DataModel.shared.originFilesFor(_fileType, type: _type) as [String]
        let checkDirectory = self.checkFileDirectory()
        let originDirectory = self.originFileDirectory()
        let resultDirectory = self.resultFileDirectory()
        
        for fileName in files {
            let fName = NSString(string: fileName).deletingPathExtension
            let originPath = NSString(string: originDirectory).appendingPathComponent(fName+".pdf")
            let resultPath = NSString(string: resultDirectory).appendingPathComponent(fName+"."+_extention)
            let checkPath = NSString(string: checkDirectory).appendingPathComponent(fName+"."+_extention)
            
            
            var isDirectory = ObjCBool(false)
            if FileManager.default.fileExists(atPath: resultPath, isDirectory:&isDirectory) {
                // compare screenshoot between result file with check file
                let items = NSMutableArray()
                if (isDirectory.boolValue) {
                    let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultPath)
                    for item in NSArray(array: searchItems) {
                        let ext = NSString(string: item as! String).pathExtension.lowercased()
                        if NSArray(array: [_extention]).contains(ext) {
                            let fileName = NSString(string: fName+"."+_extention+"/\(item as! String)").deletingPathExtension
                            items.add(fileName)
                        }
                    }
                }else {
                    items.add(fName)
                }
                
                for item in items {
                    let subFileName = item as! String
                    let rComparePath = NSString(string: resultDirectory).appendingPathComponent(subFileName+".jpg")
                    let cComparePath = NSString(string: checkDirectory).appendingPathComponent(subFileName+".jpg")
                    
                    if FileManager.default.fileExists(atPath: rComparePath) {
                        if !FileManager.default.fileExists(atPath: cComparePath) {
                            return true
                        }
                        
                        let rfs = try! FileManager.default.attributesOfItem(atPath: rComparePath)[FileAttributeKey.size] as! NSNumber
                        let cfs = try! FileManager.default.attributesOfItem(atPath: cComparePath)[FileAttributeKey.size] as! NSNumber
                        if  rfs.int64Value != cfs.int64Value {
                            return true
                        }
                    }
                }
            }
        }
        
        return false
    }
    
    func updateRefImage() {
        let files = DataModel.shared.originFilesFor(_fileType, type: _type) as [String]
        
        for fileName in files {
            updateRefImage(fileName)
        }
    }
    
    func canUpdateRefImage(_ fileName:String) -> Bool {
        let checkDirectory = self.checkFileDirectory()
        let originDirectory = self.originFileDirectory()
        let resultDirectory = self.resultFileDirectory()
        
        let fName = NSString(string: fileName).deletingPathExtension
        let originPath = NSString(string: originDirectory).appendingPathComponent(fName+".pdf")
        let resultPath = NSString(string: resultDirectory).appendingPathComponent(fName+"."+_extention)
        let checkPath = NSString(string: checkDirectory).appendingPathComponent(fName+"."+_extention)
        
        
        var isDirectory = ObjCBool(false)
        if FileManager.default.fileExists(atPath: resultPath, isDirectory:&isDirectory) {
            // compare screenshoot between result file with check file
            let items = NSMutableArray()
            if (isDirectory.boolValue) {
                let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultPath)
                for item in NSArray(array: searchItems) {
                    let ext = NSString(string: item as! String).pathExtension.lowercased()
                    if NSArray(array: [_extention]).contains(ext) {
                        let fileName = NSString(string: fName+"."+_extention+"/\(item as! String)").deletingPathExtension
                        items.add(fileName)
                    }
                }
            }else {
                items.add(fName)
            }
            
            for item in items {
                let subFileName = item as! String
                let rComparePath = NSString(string: resultDirectory).appendingPathComponent(subFileName+".jpg")
                let cComparePath = NSString(string: checkDirectory).appendingPathComponent(subFileName+".jpg")
                
                if FileManager.default.fileExists(atPath: rComparePath) {
                    if !FileManager.default.fileExists(atPath: cComparePath) {
                        return true
                    }
                    
                    let rfs = try! FileManager.default.attributesOfItem(atPath: rComparePath)[FileAttributeKey.size] as! NSNumber
                    let cfs = try! FileManager.default.attributesOfItem(atPath: cComparePath)[FileAttributeKey.size] as! NSNumber
                    if  rfs.int64Value != cfs.int64Value {
                        return true
                    }
                }
            }
        }
        
        return false
    }
    
    func updateRefImage(_ fileName:String) {
        let checkDirectory = self.checkFileDirectory()
        let originDirectory = self.originFileDirectory()
        let resultDirectory = self.resultFileDirectory()
        
        let fName = NSString(string: fileName).deletingPathExtension
        let originPath = NSString(string: originDirectory).appendingPathComponent(fName+".pdf")
        let resultPath = NSString(string: resultDirectory).appendingPathComponent(fName+"."+_extention)
        let checkPath = NSString(string: checkDirectory).appendingPathComponent(fName+"."+_extention)
        
        
        var isDirectory = ObjCBool(false)
        if FileManager.default.fileExists(atPath: resultPath, isDirectory:&isDirectory) {
            // compare screenshoot between result file with check file
            let items = NSMutableArray()
            if (isDirectory.boolValue) {
                let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultPath)
                for item in NSArray(array: searchItems) {
                    let ext = NSString(string: item as! String).pathExtension.lowercased()
                    if NSArray(array: [_extention]).contains(ext) {
                        let fileName = NSString(string: fName+"."+_extention+"/\(item as! String)").deletingPathExtension
                        items.add(fileName)
                    }
                }
            }else {
                items.add(fName)
            }
            
            for item in items {
                let subFileName = item as! String
                let rComparePath = NSString(string: resultDirectory).appendingPathComponent(subFileName+".jpg")
                let cComparePath = NSString(string: checkDirectory).appendingPathComponent(subFileName+".jpg")
                
                if FileManager.default.fileExists(atPath: rComparePath) {
                    if FileManager.default.fileExists(atPath: cComparePath) {
                        try! FileManager.default.removeItem(atPath: cComparePath)
                    }
                    try! FileManager.default.createDirectory(atPath: NSString(string: cComparePath).deletingLastPathComponent,
                                                             withIntermediateDirectories: true)
                    try! FileManager.default.copyItem(atPath: rComparePath, toPath: cComparePath)
                }
            }
        }
    }
    
    
    
    /// Path
    func originFileDirectory() -> String {
        return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kOriginPathComponent)")
    }
    
    func resultFileDirectory() -> String {
        return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kResultPathComponent)")
    }
    
    func checkFileDirectory() -> String {
        return DataModel.shared.directoryPath().appendingFormat("/\(self.fileType())/\(self.type())/\(kCheckPathComponent)")
    }
    
    // check File Exist
    func hasOriginFile() -> Bool {
        if nil != self.originFileDirectory() && FileManager.default.fileExists(atPath: self.originFileDirectory()) {
            let files = try? FileManager.default.subpathsOfDirectory(atPath: self.originFileDirectory())
            return (files ?? []).count > 0
        }
        
        return false
    }
    
    func hasResultFile() -> Bool {
        if nil != self.resultFileDirectory() && FileManager.default.fileExists(atPath: self.resultFileDirectory()) {
            let files = try? FileManager.default.subpathsOfDirectory(atPath: self.resultFileDirectory())
            return (files ?? []).count > 0
        }
        
        return false
    }
    
    ///
    func clearCacheFiles() {
        reportString = NSMutableAttributedString.init();
        
        let resultDirectory = self.resultFileDirectory()
        
        var isDirectory = ObjCBool(false)
        if FileManager.default.fileExists(atPath: resultDirectory, isDirectory: &isDirectory) && isDirectory.boolValue {
            let searchItems = try! FileManager.default.contentsOfDirectory(atPath: resultDirectory)
            for item in NSArray(array: searchItems) {
                let path = NSString(string: resultDirectory).appendingPathComponent(item as! String)
                if FileManager.default.fileExists(atPath: path) {
                    try! FileManager.default.removeItem(atPath:  path)
                }
            }
        }
        
        
        _degree = 0.0;
        _fileDegreeInfo.removeAll()
    }
}


extension AutoTest {
    func stringToImage(_ string:String) ->NSImage? {
        let length = Int(string.lengthOfBytes(using: .utf8)/2)
        
        var bytes = malloc(length)!
        
        for i in 0..<length {
            let index = i
            let subString = String(NSString(string: string).substring(with: NSMakeRange(Int(index * 2), 2)))
            bytes.storeBytes(of: UInt8(hexForString(subString)), as: UInt8.self)
        }
        
        var data = Data.init(bytes: bytes, count: length)
        let image = NSImage.init(data: data)
        
        return image
    }
    
    func hexForString(_ string:String) -> UInt8 {
        let chars = string.utf8CString
        
        if (string.lengthOfBytes(using: .utf8) >= 2) {
            return UInt8(intvalueForChar(chars[0]) * 16 + intvalueForChar(chars[1]))
        }
        
        return UInt8(0)
    }
    
    func intvalueForChar(_ char:CChar) -> UInt8 {
        let iValue = char
        
        // 0 ~ 9s
        if iValue >= 48 && iValue <= 57 {
            return UInt8(iValue - 48)
        }else if iValue >= 65 && iValue <= 70 {
            return UInt8(iValue - 65 + 10)
        }else if iValue >= 97 && iValue <= 102 {
            return UInt8(iValue - 97 + 10)
        }
        
        return UInt8(0)
    }
}
