//
//  LKFMetaDataGroupNode.swift
//  LKF
//
//  Created by Alexander Holzer on 30.12.24.
//  Copyright © 2024 Alexander Holzer. All rights reserved.
//

import Foundation
@_implementationOnly import LibLKF

public class LKFMetaDataGroupNode {
    
    public let active : Bool
    
    init (active: Bool = true) {
        self.active = active
    }
    
    static func nodeForIndex (context: LKFContext, ptr: OpaquePointer, index: UInt32) -> LKFMetaDataGroupNode? {
        if let contextptr = context.ptr {
            let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:1024)
            defer {
                lineBuffer.deallocate()
            }
            if LibLKF.lkf_stamm_getGroupLine (contextptr, ptr, index, lineBuffer, 1024) > 0 {
                let r = String(cString: lineBuffer)
                return nodeForIndex(context: context, ptr: ptr, definition: r, index: index)
            }
        }
        return nil
    }
    
    static func nodeForIndex (context: LKFContext, ptr: OpaquePointer, definition: String, index: UInt32) -> LKFMetaDataGroupNode? {
        if definition.starts(with: "L") {
            return LKFMetaDataGroupLeafNode (context: context, index: index, definition: definition)
        }
        return LKFMetaDataGroupSplitNode (context: context, index: index, ptr: ptr, definition: definition)
    }
    
    public func parallelChildrenCount (maxDepth: Int = 99) -> Int {
        return 1
    }
    
    public var depth : Int {
        return 0
    }
    
    public var leafCount : Int {
        return 0
    }
    
    public func minMaxPoints() -> (Double,Double)? {
        return nil
    }
    
    public func minMaxLP() -> (Double,Double)? {
        return nil
    }
    
    public func inactive() -> LKFMetaDataGroupNode {
        fatalError("inactive(): not implemented in base class")
    }
    
    public func withMel(mel: LKFMetaDataMel) -> LKFMetaDataGroupNode {
        return self
    }
    
    public func with(knt: String) -> LKFMetaDataGroupNode {
        return self
    }
    
    public func withDiag (diag: LKFMetaDataDiag) -> LKFMetaDataGroupNode {
        return self
    }
}

public class LKFMetaDataGroupLeafNode : LKFMetaDataGroupNode {
    public let knoten : String
    public let tk : Double
    public let lk : Double
    public let melzu : Double
    public let splitLK: Double?
    public let nfLK: Double?
    public let vdu: Int
    public let vdo: Int
    public let vdmw : Double
    
    init (source: LKFMetaDataGroupLeafNode, active: Bool = true) {
        self.knoten = source.knoten
        self.tk = source.tk
        self.lk = source.lk
        self.melzu = source.melzu
        self.splitLK = source.splitLK
        self.nfLK = source.nfLK
        self.vdu = source.vdu
        self.vdo = source.vdo
        self.vdmw = source.vdmw
        super.init(active: active)
    }
    
    init? (context: LKFContext, index: UInt32, definition: String, active: Bool = true) {
        let parts = definition.split(separator: "|")
        if parts.count > 8, let tk = Double (parts[2]), let lk = Double (parts[3]) {
            self.tk = tk
            self.lk = lk
            self.knoten = String(parts[1])
            self.melzu  = Double(parts[4]) ?? 0.0
            self.vdu = Int(parts[6]) ?? 0
            self.vdo = Int(parts[7]) ?? 0
            self.vdmw = Double(parts[8]) ?? 0.0
            if parts.count > 10, let slk = Double(parts[9]), let nflk = Double(parts[10]) {
                self.splitLK = slk
                self.nfLK = nflk
            }
            else {
                self.splitLK = nil
                self.nfLK = nil
            }
            super.init(active: active)
        }
        else {
            return nil
        }
    }
    
    override public func inactive() -> LKFMetaDataGroupNode {
        if !active { return self }
        return LKFMetaDataGroupLeafNode(source: self, active: false)
    }
    
    override public var leafCount : Int {
        return 1
    }
    
    override public func minMaxPoints() -> (Double,Double)? {
        guard active else { return nil }
        let p = tk + lk
        return (p,p)
    }
    
    override public func minMaxLP() -> (Double,Double)? {
        guard active else { return nil }
        return (lk, lk)
    }
    
    override public func with(knt: String) -> LKFMetaDataGroupNode {
        if knoten == knt {
            return self
        }
        return self.inactive()
    }
}

public class LKFMetaDataGroupSplitNode : LKFMetaDataGroupNode {
    public let trueNode : LKFMetaDataGroupNode
    public let falseNode : LKFMetaDataGroupNode
    public let nodeType : Int
    public let compare : String
    public let intervalOperator : String?
    public let interval0 : Int?
    public let interval1 : Int?
    
    init (source: LKFMetaDataGroupSplitNode, active: Bool = true, trueNode: LKFMetaDataGroupNode? = nil, falseNode: LKFMetaDataGroupNode? = nil) {
        
        if let trueNode {
            self.trueNode = trueNode
        }
        else if active {
            self.trueNode = source.trueNode
        }
        else {
            self.trueNode = source.trueNode.inactive()
        }
        
        if let falseNode {
            self.falseNode = falseNode
        }
        else if active {
            self.falseNode = source.falseNode
        }
        else {
            self.falseNode = source.falseNode.inactive()
        }
        
        self.nodeType = source.nodeType
        self.compare = source.compare
        self.interval1 = source.interval1
        self.interval0 = source.interval0
        self.intervalOperator = source.intervalOperator
        super.init(active: active)
    }
    
    init? (context: LKFContext, index: UInt32, ptr: OpaquePointer, definition: String) {
        
        let parts = definition.split(separator: "|")
        if parts.count > 4, let trueIndex = UInt32(parts[1]), let falseIndex = UInt32(parts[2]),
           let nodeType = Int(parts[3]),
           let trueNode = LKFMetaDataGroupNode.nodeForIndex(context: context, ptr: ptr, index: trueIndex),
           let falseNode = LKFMetaDataGroupNode.nodeForIndex(context: context, ptr: ptr, index: falseIndex) {
            self.trueNode = trueNode
            self.falseNode = falseNode
            self.compare = String(parts[4])
            self.nodeType = nodeType
        }
        else {
            return nil
        }
        
        if parts.count > 6 {
            self.intervalOperator = String(parts[5])
            self.interval0 = Int(parts[6])
            if parts.count > 7 {
                self.interval1 = Int(parts[7])
            }
            else {
                self.interval1 = nil
            }

        }
        else {
            self.intervalOperator = nil
            self.interval0 = nil
            self.interval1 = nil
        }
    }
    
    public var nodeTypeHasInterval : Bool {
        switch (nodeType) {
        case 1, 2, 3, 4, 5, 7, 12:
            return false
        default:
            return true
        }
    }
    
    override public var leafCount : Int {
        return trueNode.leafCount + falseNode.leafCount
    }
    
    public override func parallelChildrenCount (maxDepth: Int = 99) -> Int {
        if maxDepth > 0 {
            return trueNode.parallelChildrenCount(maxDepth: maxDepth - 1) + falseNode.parallelChildrenCount(maxDepth: maxDepth - 1)
        }
        return 1
    }
    
    public override var depth: Int {
        return max(trueNode.depth + 1, falseNode.depth + 1)
    }
    
    public var nodeTypeText : String {
        switch (nodeType) {
        case 1: return "SPEZMEL"
        case 2: return "SPEZHDG4"
        case 3: return "SPEZHDG"
        case 4: return "SPEZDG4"
        case 5: return "SPEZDG"
        case 6: return "ALTER"
        case 7: return "AGR01"
        case 12: return "AGR02"
       default:
            if nodeType >= 101 && nodeType <= 199 {
                return String(format:"HG%03d", nodeType)
            }
            if nodeType > 300 && nodeType <= 400 {
                return String(format:"ALG%02d", nodeType - 300)
            }
            if nodeType > 400 && nodeType <= 500 {
                return String(format:"AFG%02d", nodeType - 400)
            }
            if nodeType <= 699 {
                return String(format:"LG%03d", nodeType - 400)
            }
            return "\(nodeType)"
            
        }
    }
    
    public var intervalString : String? {
        if nodeTypeHasInterval, let op = intervalOperator {
            let interval0String = interval0 != nil ? String(interval0!) : ""
            if op == "><" {
                let interval1String = interval1 != nil ? String(interval1!) : ""
                return "\(interval0String) - \(interval1String)"
            }
            else {
            
                return "\(op)\(interval0String)"
            }
        }
        return nil
    }
    
    override public func minMaxPoints() -> (Double,Double)? {
        guard active else {
            return nil
        }
        guard let minMax = trueNode.minMaxPoints() else {
            return falseNode.minMaxPoints()
        }
        guard let falseNodePoints = falseNode.minMaxPoints() else {
            return minMax
        }
        return (min(minMax.0, falseNodePoints.0), max(minMax.1, falseNodePoints.1))
    }
    
    override public func minMaxLP() -> (Double,Double)? {
        guard active else {
            return nil
        }
        guard let minMax = trueNode.minMaxLP() else {
            return falseNode.minMaxLP()
        }
        guard let falseNodePoints = falseNode.minMaxLP() else {
            return minMax
        }
        return (min(minMax.0, falseNodePoints.0), max(minMax.1, falseNodePoints.1))
    }

    override public func inactive() -> LKFMetaDataGroupNode {
        if !active { return self }
        return LKFMetaDataGroupSplitNode(source: self, active: false)
    }
    
    override public func with(knt: String) -> LKFMetaDataGroupNode {
        let trueNode = trueNode.with(knt: knt)
        let falseNode = falseNode.with(knt: knt)
        
        if !trueNode.active && !falseNode.active {
            return self.inactive()
        }
        return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode, falseNode: falseNode)
    }
    
    public override func withMel(mel: LKFMetaDataMel) -> LKFMetaDataGroupNode {
        
        if nodeType == 1 {
            if compare == mel.Mel {
                return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withMel(mel: mel), falseNode: falseNode.inactive())
            }
            return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.inactive(), falseNode: falseNode.withMel(mel: mel))
        }
        else if nodeType > 300 && nodeType <= 500 {
            if mel.AMBLGR == nodeTypeText {
                return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withMel(mel: mel), falseNode: falseNode.inactive())
            }
            return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.inactive(), falseNode: falseNode.withMel(mel: mel))
        }
        if nodeType > 500 && nodeType <= 699 {
            if mel.LGR == nodeTypeText {
                return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withMel(mel: mel), falseNode: falseNode.inactive())
            }
            return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.inactive(), falseNode: falseNode.withMel(mel: mel))
        }
        
        return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withMel(mel: mel), falseNode: falseNode.withMel(mel: mel))
    }
    
    public override func withDiag (diag: LKFMetaDataDiag) -> LKFMetaDataGroupNode {
        
        if nodeType >= 101 && nodeType <= 199 {
            if diag.DGR == nodeTypeText {
                return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withDiag(diag: diag), falseNode: falseNode.inactive())
            }
            return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.inactive(), falseNode: falseNode.withDiag(diag: diag))
        }
        
        
        return LKFMetaDataGroupSplitNode(source: self, trueNode: trueNode.withDiag(diag: diag), falseNode: falseNode.withDiag (diag: diag))
    }
}
