//
//  lkfcmasstests.swift
//  lkfcTests
//
//  Created by Alexander Holzer on 22.09.19.
//  Copyright © 2019 Alexander Holzer. All rights reserved.
//

import XCTest
@testable import lkfc

class MassTestConfig {
    let path: URL
    let stamm: String
    let privateStamm: Bool
    let model: UInt32
    let year: UInt32
    var compare = LKFBatchDifferenceMode.print
    var caseStat : LKFCaseStat?
    var removeBM = false
    var contextFlags : Int32?
    var errorFiles = LKFBatchErrorFileMode.write
    var repeatCount : Int = 1
    var packageSize : Int?
    let threadCount : Int
    let outPath: URL
    var memoryConfig: LKFBatchFileDebugMemoryConfig?
    
    static var enableMassTest = true

    init (url: URL, stamm:String, privateStamm: Bool = false, model: UInt32, year: UInt32, contextFlags: Int32? = nil, errorFiles: LKFBatchErrorFileMode? = nil, outPath: String? = nil, threadCount: Int = 1, memoryConfig: LKFBatchFileDebugMemoryConfig? = nil) {
        self.path = url
        self.stamm = stamm
        self.model = model
        self.year = year
        self.contextFlags = contextFlags
        self.threadCount = threadCount
        self.privateStamm = privateStamm
        self.memoryConfig = memoryConfig
        
        if let outPath {
            self.outPath = URL(fileURLWithPath: outPath)
        }
        else {
            self.outPath = lkfcTests.outPath
        }
        
        if let errorFiles {
            self.errorFiles = errorFiles
        }
    }
    
    convenience init (source: MassTestConfig) {
        self.init (url: source.path, stamm: source.stamm, privateStamm: source.privateStamm, model: source.model, year: source.year, contextFlags: source.contextFlags, errorFiles: source.errorFiles, outPath: source.outPath.path, threadCount: source.threadCount, memoryConfig: source.memoryConfig)
        removeBM = source.removeBM
    }

    
    convenience init (path: String, stamm:String, privateStamm: Bool = false, model: UInt32, year: UInt32, contextFlags: Int32? = nil, errorFiles: LKFBatchErrorFileMode? = nil, outPath: String? = nil, threadCount: Int = 1, memoryConfig: LKFBatchFileDebugMemoryConfig? = nil) {
        self.init (url: URL(fileURLWithPath: path, isDirectory: true), stamm: stamm, privateStamm: privateStamm, model: model, year: year, contextFlags: contextFlags, errorFiles: errorFiles, outPath: outPath, threadCount: threadCount, memoryConfig: memoryConfig)
    }
    
    convenience init(path: String, year: UInt32, contextFlags: Int32? = nil, errorFiles: LKFBatchErrorFileMode? = nil, outPath: String? = nil, threadCount: Int = 1, memoryConfig: LKFBatchFileDebugMemoryConfig? = nil) {
        self.init(url: URL(fileURLWithPath: path, isDirectory: true), stamm: path, model: year, year: year, contextFlags: contextFlags,  errorFiles: errorFiles, outPath: outPath, threadCount: threadCount, memoryConfig: memoryConfig)
    }
    
    convenience init(path: String, model: UInt32, year: UInt32, contextFlags: Int32? = nil, errorFiles: LKFBatchErrorFileMode? = nil, outPath: String? = nil, threadCount: Int = 1, memoryConfig: LKFBatchFileDebugMemoryConfig? = nil) {
        self.init(url: URL(fileURLWithPath: path, isDirectory: true), stamm: path, model: model, year: year, contextFlags: contextFlags, errorFiles: errorFiles, outPath: outPath, threadCount: threadCount, memoryConfig: memoryConfig)
    }
    
    func with (removeBM: Bool) -> MassTestConfig {
        let mtc = MassTestConfig(source:self)
        mtc.removeBM = removeBM
        return mtc
    }
    
    func with (errorFiles: LKFBatchErrorFileMode) -> MassTestConfig {
        let mtc = MassTestConfig(source:self)
        mtc.errorFiles = errorFiles
        return mtc
    }
    
    func with (contextFlags : Int32) -> MassTestConfig {
        let mtc = MassTestConfig(source:self)
        mtc.contextFlags = contextFlags
        return mtc
    }
    
    func test(testOutput: Bool = false, diff: LKFBatchDifferenceMode = .print) {
        do {
            try testThrowing(testOutput: testOutput, diff: diff)
        }
        catch {
            XCTFail(error.localizedDescription)
        }
    }

    func testThrowing (testOutput: Bool = false, diff: LKFBatchDifferenceMode = .print) throws {
        let files : [URL]
        files = try FileManager.default.contentsOfDirectory(at: path, includingPropertiesForKeys: nil, options: [])
        
        let batchParameters = LKFBatchFileExecutionParametersForTest (outPath: outPath)
        batchParameters.rootPath = stamm
        batchParameters.createTestOutput = testOutput
        batchParameters.compareMode = diff
        batchParameters.year = year
        batchParameters.model = model
        batchParameters.errorFiles = errorFiles
        batchParameters.includeStat = false
        batchParameters.removeBM = removeBM
        batchParameters.contextFlags = contextFlags
        batchParameters.debugMemoryConfig = memoryConfig
        
        var failCount = 0
        var lastFailure : String? = nil
        var fileCount = 0
        
        let threadCount = self.threadCount
        
        if !FileManager.default.fileExists(atPath: outPath.path()) {
            var p = outPath.path()
            if (p.starts(with: "file:")) {
                p = p.substr(from:5) ?? p
            }
            
            try FileManager.default.createDirectory(atPath: p, withIntermediateDirectories: true)
        }
        
        if threadCount > 1 {
            
            print ("Executing with \(threadCount) threads.")
            
            let semaphore = DispatchSemaphore(value: threadCount)
            let endSemaphore = DispatchSemaphore(value: 0)
            let serialQueue = DispatchQueue(label: "counter")
            
            let metaDataFilesContext : LKFMetaDataFilesContext?
            
            if (privateStamm) {
                metaDataFilesContext = LKFMetaDataFilesContext(model: model, baseUrl: URL(fileURLWithPath: stamm))
                
                guard let metaDataFilesContext else {
                    XCTAssert(false, "metadata with path \(stamm) invalid")
                    return
                }
                
                _ = try metaDataFilesContext.load()
            }
            else {
                metaDataFilesContext = nil
            }
            
            for inputFile in files {
                let lpc = inputFile.lastPathComponent
                
                if lpc.starts(with: ".") || !lpc.contains(".") {
                    serialQueue.async {
                        fileCount += 1
                        if fileCount == files.count {
                            endSemaphore.signal()
                        }
                    }
                    continue
                }
                
                semaphore.wait()
                Task {
                    print ("******* testdata: \(inputFile)")
                    
                    let execution = LKFBatchFileExecution(file: inputFile, metaData: metaDataFilesContext, parameters: batchParameters)
                    
                    if let packageSize = packageSize {
                        execution.packageSize = packageSize
                    }

                    let _ = try execution.execute(iteration: nil)
                    /*
                    if let caseStat = execution.caseStat {
                        if self.caseStat == nil {
                            self.caseStat = caseStat
                        }
                        else {
                            self.caseStat = self.caseStat!.adding (caseStat)
                        }
                    }
                    
                    if let failure = result as? LKFBatchFileExecutionResultFailed {
                        lastFailure = failure.failureTextWithFile
                        failCount += 1
                    }
                    */
                    serialQueue.async {
                        fileCount += 1
                        if fileCount == files.count {
                            endSemaphore.signal()
                        }
                    }
                    semaphore.signal()
                }
            }
            endSemaphore.wait()
        }
        else {
            batchParameters.unloadStamm = true
            for inputFile in files {

                let lpc = inputFile.lastPathComponent
                
                if lpc.starts(with: ".") || !lpc.contains(".") {
                    continue
                }
                
                print ("******* testdata: \(inputFile)")
                
                let memoryTracker : LKFMemoryTracker?
                
                if (batchParameters.debugMemoryConfig != nil) {
                    memoryTracker = LKFMemory.initMemoryTracker()
                }
                else {
                    memoryTracker = nil
                }
                
                for r in 0..<repeatCount {
                    if (r == 1) {
                        memoryTracker?.reset()
                    }
                    else if r > 1, let memoryTracker {
                        
                        if batchParameters.unloadStamm {
                            LKFContext.unloadStamm()
                        }
                        
                        if let res = memoryTracker.getResult() {
                            print ("**** memory leak iteration \(r):")
                            res.forEach {
                                print ("\($0.key): \($0.value.0) (\($0.value.1))")
                            }
                        }
                        
                        memoryTracker.reset()
                    }
                    
                    let execution = LKFBatchFileExecution(file: inputFile, parameters: batchParameters)
                    
                    if let packageSize = packageSize {
                        execution.packageSize = packageSize
                    }
                    
                    let result = try execution.execute(iteration: repeatCount > 1 ? r : nil)
                    
                    if let caseStat = execution.caseStat {
                        if self.caseStat == nil {
                            self.caseStat = caseStat
                        }
                        else {
                            self.caseStat = self.caseStat!.adding (caseStat)
                        }
                    }
                    
                    if let failure = result as? LKFBatchFileExecutionResultFailed {
                        lastFailure = failure.failureTextWithFile
                        failCount += 1
                    }
                }
                
                if (memoryTracker != nil) {
                    
                    if let memoryConfig, memoryConfig.returnMemoryTracker {
                        memoryConfig.memoryTracker = memoryTracker
                    }
                    LKFMemory.memoryCallbackClear()
                }
            }
        }
               
        if failCount > 0 {
            if failCount == 1 {
                XCTAssert(false, lastFailure!)
            }
            else {
                XCTAssert(false, "\(failCount) failed, last = \(lastFailure!)")
            }
        }
    }
}

class lkfcmasstests: XCTestCase {
    
    static let configs : [MassTestConfig] = {
        LKFContext.unloadStamm()
        var configs = [MassTestConfig]()
/*
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2019", year: 2019))
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2020", model: 2020, year: 2019))
 
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)ina2020", year: 2020))
 
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)ina2021", year: 2021))
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2021", model: 2021, year: 2019))
 
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2022", model: 2022, year: 2022))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024/a", stamm: "\(LKFTest.rootPath)in2024", model: 2024, year: 2024, contextFlags: 1, threadCount: 8))
    configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024/b", stamm: "\(LKFTest.rootPath)in2024", model: 2024, year: 2024, contextFlags: 1, threadCount: 8))
    configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024/c", stamm: "\(LKFTest.rootPath)in2024", model: 2024, year: 2024, contextFlags: 1, threadCount: 8))
    configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024/d", stamm: "\(LKFTest.rootPath)in2024", model: 2024, year: 2024, contextFlags: 1, threadCount: 8))
*/
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/a", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/a", threadCount: 8))
 
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/b", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/b", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/c", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/c", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/d", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/d", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/stmk", stamm: "\(LKFTest.rootPath)in2025/stmk", privateStamm: true, model: 2025, year: 2025, contextFlags: 0, outPath: "\(LKFTest.outPath)/stmk", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/a", stamm: "\(LKFTest.rootPath)in2026", model: 2026, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/a",threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/b", stamm: "\(LKFTest.rootPath)in2026", model: 2026, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/b", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/c", stamm: "\(LKFTest.rootPath)in2026", model: 2026, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/c", threadCount: 8))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/d", stamm: "\(LKFTest.rootPath)in2026", model: 2026, year: 2024, contextFlags: 1, outPath: "\(LKFTest.outPath)/d", threadCount: 8))

  configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/stmk", stamm: "\(LKFTest.rootPath)in2026/stmk", privateStamm: true, model: 2026, year: 2025, contextFlags: 0, outPath: "\(LKFTest.outPath)/stmk", threadCount: 8))
        
      /*


        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2023", model: 2023, year: 2023, contextFlags: 1, errorFiles: .compareAndWriteToResult))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024", model: 2024, year: 2023, contextFlags: 1, errorFiles: .compareAndWriteToResult))
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025", model: 2025, year: 2023, contextFlags: 1, errorFiles: .compareAndWriteToResult))

 configs.append(MassTestConfig(path: "\(LKFTest.rootPath)inamb2019", stamm: "\(LKFTest.rootPath)in2019", model: 2019, year: 2019))
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)inamb2020", stamm: "\(LKFTest.rootPath)in2020", model: 2020, year: 2019))
 
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)inamb2021", stamm: "\(LKFTest.rootPath)in2021", model: 2021, year: 2019))

        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)inamb2022", stamm: "\(LKFTest.rootPath)inamb2022", model: 2022, year: 2022))

 */
        return configs
    }()
    
    static let npconfigs : [MassTestConfig] = {
        LKFContext.unloadStamm()
        var npconfigs = [MassTestConfig]()
//        npconfigs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2024/np", stamm: "\(LKFTest.rootPath)in2024", model: 2024, year: 2024, contextFlags: 2049))
//        npconfigs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2025/np", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 2049))
        npconfigs.append(MassTestConfig(path: "\(LKFTest.rootPath)in2026/np", stamm: "\(LKFTest.rootPath)in2026", model: 2026, year: 2024, contextFlags: 2049))

        return npconfigs
    }()
    
    static let adhoc_configs : [MassTestConfig] = {
        LKFContext.unloadStamm()
        var configs = [MassTestConfig]()
        configs.append(MassTestConfig(path: "\(LKFTest.rootPath)bernie/K6_Data", stamm: "\(LKFTest.rootPath)bernie", model: 2021, year: 2021))
        return configs
    }()
    
    override func setUp() {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
    
    func testAdHoc() throws {
      
        let context = try LKFContext(model: 2025, path: LKFTest.rootPath)
        context.flags = 0
        context.setContextParameters(year: 2025)
        
        let adHocFile = URL(fileURLWithPath: LKFTest.rootPath).appendingPathComponent("adHoc", isDirectory: true).appendingPathComponent("k901j001.s25", isDirectory: false)
        
        try context.load(file: adHocFile, encoding: .windowsCP1252)
        
        try context.execute()
        
        let sumFile = LKFErrorFilterSums(context: context)
        
        if let errors = sumFile.errors {
            for e in errors {
                print("\(e.0): \(e.1) \(e.2)")
            }
        }
        
        /*
         for c in lkfcmasstests.adhoc_configs {
             c.contextFlags = 8388608
             c.packageSize = 5000
             c.test(testOutput: true, diff: .disable)
         }
          */
    }
    
    func testMass() {
       
        if MassTestConfig.enableMassTest {
            for c in lkfcmasstests.configs {
                c.errorFiles = .compareAndWriteToResult
                c.test()
            }
        }
        else {
            XCTAssert(false, "Mass Test deactivated")
        }
    }
    
    func testNP () {
        if MassTestConfig.enableMassTest {
            for c in lkfcmasstests.npconfigs {
                c.errorFiles = .write
                c.test()
            }
        }
        else {
            XCTAssert(false, "Mass Test deactivated")
        }
    }
}
