//
//  lkfcMemoryTests.swift
//  lkfcTests
//
//  Created by Alexander Holzer on 23.09.23.
//  Copyright © 2023 Alexander Holzer. All rights reserved.
//

import Foundation
import XCTest
@testable import lkfc

final class lkfMemoryTests: XCTestCase {
    
    static let inPath = URL(fileURLWithPath: "\(LKFTest.rootPath)memoryIn", isDirectory: true)

    
    func createContextWith (model: UInt32, year: Int32? = nil, lines : [String]) throws -> LKFContext {
        let c = try lkfcTests.createContextWith(model:model, lines:lines)
        if let year {
            c.setContextParameters(year: year)
        }
        return c
    }
    
    func combine (_ first:[String], _ second:[String]) -> [String] {
        return lkfcTests.combine(first, second)
    }
    
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
    
    func testMemoryLeak () {
        
        let mtc = MassTestConfig(path: "\(LKFTest.rootPath)in2025/e", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1, memoryConfig: LKFBatchFileDebugMemoryConfig())
        mtc.errorFiles = .write
        mtc.repeatCount = 10
        mtc.test()
    }
    
    static let enableMemoryDebugTests = false // can only be accessed in directly, not as part of the whole unit test
    
    
    class MemoryDebugLimitSetting {
        let minOccurrence : Int
        let factor: Int
        
        init (minOccurrence: Int, factor: Int) {
            self.minOccurrence = minOccurrence
            self.factor = factor
        }
        
        var description: String {
            return "**minOccurrence=\(minOccurrence), factor=\(factor)"
        }
        
        static let first = MemoryDebugLimitSetting(minOccurrence: 1, factor: 40)
        static let second = MemoryDebugLimitSetting(minOccurrence: 5, factor: 60)
        static let third = MemoryDebugLimitSetting(minOccurrence: 20, factor: 75)
        static let forth = MemoryDebugLimitSetting(minOccurrence: 40, factor: 90)
    }
    
    func testMemoryDebugLimit () {

        if (!lkfMemoryTests.enableMemoryDebugTests) { return; }
        
        let initialMemoryConfig = LKFBatchFileDebugMemoryConfig(returnMemoryTracker: true)
        LKFMemory.traceMemory();
        
        let path = "\(LKFTest.rootPath)in2026/e"
        let stamm = "\(LKFTest.rootPath)in2026"
        let model : UInt32 = 2026
        let year : UInt32 = 2024
        let flags : Int32 = 1
        
        let mtc1 = MassTestConfig(path: path, stamm: stamm, model: model, year: year, contextFlags: flags)
        mtc1.test()
        
        let mtc = MassTestConfig(path: path, stamm: stamm, model: model, year: year, contextFlags: flags, memoryConfig: initialMemoryConfig)
        mtc.test()
        
        guard let initialMemoryTracker = initialMemoryConfig.memoryTracker else {
            XCTFail("no memory tracker")
            return
        }

        let addedMemory = initialMemoryTracker.getAdded().sorted(by: { $0.value.1 < $1.value.1 })
        
        var breakCount: Int = 0
        print ("*** Testing \(addedMemory.count) memory categories")

        let limitSettings = [MemoryDebugLimitSetting.first, MemoryDebugLimitSetting.second, MemoryDebugLimitSetting.third, MemoryDebugLimitSetting.forth]
        
        //let limitSettings = [MemoryDebugLimitSetting.second]
        
        
        for ls in limitSettings {
            
            print (ls.description)
            
            for ind in 0..<addedMemory.count {
                
                let k = addedMemory[ind]
                
                if k.value.0 < ls.minOccurrence { continue }
                
                print ("Testing #\(ind): \(k.key) (count=\(k.value.0), size=\(k.value.1))")
                
                breakCount = k.value.0 * ls.factor / 100 - 1
                
                LKFMemory.memoryLimitSet(used: 0, available: 0, breaking: k.key, breakCount: Int32(breakCount))
                let mtc = MassTestConfig(path: path, stamm: stamm, model: model, year: year, contextFlags: flags)
                do {
                    try mtc.testThrowing(diff: .disable)
                }
                catch {
                    if let oom = error as? LKFOutOfMemoryError {
                        print ("LKFOutOfMemoryError: \(oom.localizedDescription)")
                    }
                    else if let le = error as? LKFError {
                        print ("LKFError: \(le.localizedDescription)")
                    }
                    else {
                        print ("exception: " + error.localizedDescription)
                    }
                    continue
                }
                print("MS (\(k.key)): \(LKFMemory.getDebugMemoryStatus())")
            }
        }
    }
    
    func testMemoryDebugLimitSingle () {

        if !lkfMemoryTests.enableMemoryDebugTests { return; }
        
        LKFMemory.traceMemory();
        LKFMemory.memoryCallbackSet (flag: 0) {
            op, key, ptr, elem, siz in
            
            if !op.starts(with: "free") && ptr == 0 {
                print ("LIMIT REACHED!!!")
            }
        }
        LKFMemory.memoryLimitSet(used: 0, available: 0, breaking: "lkf_add_scoring_abt_2017.i11", breakCount: 76)
        let mtc = MassTestConfig(path: "\(LKFTest.rootPath)in2025/e", stamm: "\(LKFTest.rootPath)in2025", model: 2025, year: 2024, contextFlags: 1)
        do {
            try mtc.testThrowing()
        }
        catch {
            if let oom = error as? LKFOutOfMemoryError {
                print ("LKFOutOfMemoryError: \(oom.localizedDescription)")
            }
            else if let le = error as? LKFError {
                print ("LKFError: \(le.localizedDescription)")
            }
            else {
                print ("exception: " + error.localizedDescription)
            }
        }
        print("LKFMemory.getDebugMemoryStatus()")
    }
    
    func testCrash() {
        let files : [URL]
        do {
            files = try FileManager.default.contentsOfDirectory(at: lkfMemoryTests.inPath, includingPropertiesForKeys: nil, options: [])
        }
        catch {
            assert(false, "cannot read input files")
            return
        }
        
        let batchParameters = LKFBatchFileExecutionParametersForTest (outPath: lkfcTests.outPath)
        batchParameters.rootPath = "\(LKFTest.rootPath)in2026"
        batchParameters.createTestOutput = false
        batchParameters.comparePath = lkfcTests.comparePath
        batchParameters.keepContext = true
        batchParameters.year = 2024
        batchParameters.model = 2026
        batchParameters.contextFlags = 1
        
        for inputFile in files {

            guard !inputFile.lastPathComponent.starts(with: ".") else {
                continue
            }
            
            print ("******* testdata: \(inputFile)")
            
            let execution = LKFBatchFileExecution(file: inputFile, parameters: batchParameters)
            do {
                let result = try execution.execute()
                if let failureText = result.message() {
                    print (failureText)
                }
            }
            catch {
                print ("EXCEPTION: \(error.localizedDescription)")
            }
            
        }

    }
    
    func testMemory() {
        self.measure(metrics: [XCTMemoryMetric()]) {
            testCrash()
        }
    }
}

