//
//  LKFMemory.swift
//  lkfc
//
//  Created by Alexander Holzer on 08.11.25.
//  Copyright © 2025 Alexander Holzer. All rights reserved.
//

import Foundation
@_implementationOnly import LibLKF

public class LKFMemory {
    
    var callback : @convention(c) (Optional<UnsafePointer<Int8>>, Optional<UnsafePointer<Int8>>, Int64, Int32, Int32) -> Void =
    { (op, key, ptr, elem, siz) -> Void in
        
        /*
        if let context = context, let current = traceDictionary[context], let traceLogger = current.traceLogger, let line = line {
            traceLogger(String(cString:line))
        }
         */
    }
    
    public private(set) var memoryTracker: LKFMemoryTracker?
    
    static var memoryCallback: ((String, String, Int64, Int, Int)->Void)?

    static var memorycb : @convention(c) (Optional<OpaquePointer>, Optional<UnsafePointer<Int8>>, Optional<UnsafePointer<Int8>>, Int64, Int, Int) -> Void =
    { ( ctx, op, key, ptr, elem, siz) -> Void in
        
        
        let opLine: String
        let keyString: String
        
        if let op {
            opLine = String(cString:op);
        }
        else {
            opLine = ""
        }
        
        if let key {
            keyString = String(cString: key)
        }
        else {
            keyString = ""
        }
        
        LKFMemory.memoryCallback?(opLine, keyString, ptr, elem, siz)
    }

    static public func traceMemory() {
        LibLKF.lkf_enableMemoryTrace();
    }
    
    static func memoryCallbackClear() {
        LKFMemory.memoryCallback = nil
        LibLKF.lkf_setMemoryTraceDelegate(nil,0)
    }

    static func memoryCallbackSet (flag: Int32 = 1, _ cb: @escaping (String, String, Int64, Int, Int)->Void)  {

        LKFMemory.memoryCallback = cb
        LibLKF.lkf_setMemoryTraceDelegate (LKFMemory.memorycb, flag)
    }
    
    static public func memoryLimitSet (used: Int64, available: Int64, breaking: String? = nil, breakCount: Int32? = nil) {
        
        LibLKF.lkf_set_memoryDebugLimit(used, available, breaking?.cString(using: .utf8), breakCount ?? 0);
    }
    
    static public func memoryLimitClear () {
        LibLKF.lkf_clear_debug_memory_limit();
    }
    
    static public func getDebugMemoryStatus() -> String {
    
        let memString = UnsafeMutablePointer<CChar>.allocate(capacity:512)
        LibLKF.lkf_get_debugmemoryStatus(memString, 128)
        let v = String(cString: memString)
        memString.deallocate()
        return v
    }

    public static func getDebugMemoryBytes () -> Int64 {
        return LibLKF.lkf_debugmemory_bytes()
    }
    
    public static func getDebugMemoryBlocks() -> Int64 {
        return LibLKF.lkf_debugmemory_blocks()
    }
    
    public static func initMemoryTracker() -> LKFMemoryTracker {
        let memoryTracker = LKFMemoryTracker()
        LKFMemory.memoryCallbackSet {
            op, key, ptr, elem, siz in
            
            if op.starts(with: "free") {
                _ = try? memoryTracker.del(ptr: ptr)
            }
            else {
                memoryTracker.add(ptr: ptr, key: key, siz: elem * siz)
            }
        }
        return memoryTracker
    }
}

public class LKFMemoryTracker {
    var dict = [Int64:(String,Int)]()
    var addedKeys = [String:(Int,Int)]()
    
    func add (ptr: Int64, key: String, siz: Int) {
        dict[ptr] = (key, siz)
        
        if let v = addedKeys[key] {
            addedKeys[key] = (v.0 + 1, v.1 + siz)
        }
        else {
            addedKeys[key] = (1, siz)
        }
    }
    
    func del (ptr: Int64) throws -> (String,Int)? {
        if dict[ptr] != nil {
            return dict.removeValue(forKey: ptr)
        }
        throw LKFError("key \(ptr) not found")
    }
    
    public func reset() {
        dict = [:]
        addedKeys = [:]
    }
    
    public func getResult() -> [String:(Int,Int)]? {
        
        guard dict.count > 0 else {
            return nil
        }
        
        return dict.values.reduce(into: [String:(Int,Int)]()) { (res, v) in
            
            if let r = res[v.0] {
                res[v.0] = (r.0 + 1, r.1 + v.1)
            }
            else {
                res[v.0] = (1, v.1)
            }
        }
    }
    
    public func getAdded() -> [String:(Int,Int)] {
        return addedKeys
    }
    
    public func reportAdded() -> [String] {
        
        let sortedAdded = getAdded().sorted(by: { $0.value.1 > $1.value.1 })
        
        return sortedAdded.reduce(into: [String]()) { (res, v) in
            res.append(String(format: "%48.48@;%lld;%6lld", v.key, v.value.0, v.value.1))
        }
    }
}
