//
//  LKFMetaDataMel.swift
//  LKF
//
//  Created by Alexander Holzer on 14.01.22.
//  Copyright © 2022 Alexander Holzer. All rights reserved.
//

import Foundation
@_implementationOnly import LibLKF

public class LKFMetaDataMel : LKFMetaDataItem, Identifiable {
    
    public enum Fields : UInt32 {
        case Mel = 12
        case StatGroup = 4
        case AmbGroup = 5
        case Seite = 7
        case Geschl = 8
        case Text = 20
        case LGR = 31
        case AMBLGR = 32
        case WarnCount = 41
        case ErrorCount = 42
        case DayWarnCount = 43
        case DayErrorCount = 44
        case AmbWarnCount = 45
        case AmbErrorCount = 46
    }
    
    public let context : LKFContext
    public let melIndex: UInt32
    public let id: String
    
    private var _melCustomChecks : LKFMetaDataMelCustomChecks?
    private var customChecksLoaded: Bool = false
    
    public var melCustomChecks : LKFMetaDataMelCustomChecks? {
        if !customChecksLoaded {
            customChecksLoaded = true
            _melCustomChecks = LKFMetaDataMelCustomChecks(context: context, melIndex: melIndex)
        }
        return _melCustomChecks
    }
    
    public init (context: LKFContext, melIndex: UInt32) {
        self.context = context
        self.melIndex = melIndex
        self.id = LKFMetaDataMel.utf8value (for: .Mel, context: context, melIndex: melIndex)
    }
    
    public static func utf8value (for field:LKFMetaDataMel.Fields, context: LKFContext, melIndex: UInt32) -> String {
        if let ptr = context.ptr {
            let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:256)
            defer {
                lineBuffer.deallocate()
            }
            LibLKF.lkf_stamm_getMelField (ptr, melIndex, field.rawValue, lineBuffer, 256)
            return String(cString: lineBuffer).trimmingCharacters(in: .whitespaces)
        }
        return ""
    }
    
    public func value (for field:LKFMetaDataMel.Fields, encodings:[String.Encoding]? = [.utf8, .windowsCP1252, .ascii, .isoLatin1]) -> String {
        if let ptr = context.ptr {
            let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:256)
            defer {
                lineBuffer.deallocate()
            }
            LibLKF.lkf_stamm_getMelField (ptr, melIndex, field.rawValue, lineBuffer, 256)
            if let encodings = encodings {
                for e in encodings {
                    if let r = String(cString: lineBuffer, encoding: e) {
                        return r.trimmingCharacters(in: .whitespaces)
                    }
                }
            }
            return String(cString: lineBuffer).trimmingCharacters(in: .whitespaces)
        }
        return ""
    }
    
    public var Mel : String {
        return id
    }
    
    public var StatGroupName : String {
        return value (for: .StatGroup)
    }
    
    public var AmbGroupName : String {
        return value (for: .AmbGroup)
    }
    
    public var LGR : String {
        return value (for: .LGR)
    }
    
    public var AMBLGR : String {
        return value (for: .AMBLGR)
    }
    
    public var Text: String {
        return value (for: .Text)
    }
    
    public lazy var ambGroup : LKFMetaDataGroup? = { () -> LKFMetaDataGroup? in
        let g = AmbGroupName
        if g.count > 0 {
            return try? LKFMetaDataGroup (context: context, group: g)
        }
        return nil
    }()

    public lazy var statGroup : LKFMetaDataGroup? = { () -> LKFMetaDataGroup? in
        let g = StatGroupName
        if g.count > 0 {
            return try? LKFMetaDataGroup (context: context, group: g)
        }
        return nil
    }()
    
    public func getMELDiags() -> [String] {
        guard let ptr = context.ptr else {
            return []
        }
        
        let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:16384)
        defer {
            lineBuffer.deallocate()
        }
        if LibLKF.lkf_stamm_getMelDiags (ptr, melIndex, lineBuffer, 16384) > 0 {
            if let r = String(cString: lineBuffer, encoding: .utf8) {
                return r.split(separator: ";").map { String($0) }
            }
        }
        return []
    }
    
    public func getMELMEL() -> [String] {
        guard let ptr = context.ptr else {
            return []
        }
        
        let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:16384)
        defer {
            lineBuffer.deallocate()
        }
        if LibLKF.lkf_stamm_getMelMel (ptr, melIndex, lineBuffer, 16384) > 0 {
            if let r = String(cString: lineBuffer, encoding: .utf8) {
                return r.split(separator: ";").map { String($0) }
            }
        }
        return []
    }
    
    override public var itemKey: String { return self.Mel }
    override public var itemDescription: String { return self.Text }
    
    override public func getLowercasedSearchTexts() -> [String] {
        var arr = [self.Mel.lowercased(), self.Text.lowercased()]
        let statGroupName = self.StatGroupName
        let ambGroupName = self.AmbGroupName
        
        if statGroupName.count > 0 {
            arr.append(statGroupName.lowercased())
        }
        if ambGroupName.count > 0, ambGroupName != statGroupName {
            arr.append(ambGroupName.lowercased())
        }
        return arr
    }
}

public class LKFMetaDataMelCustomCheck {
    public let ptr: OpaquePointer
    public let errorCode: String?
    public let description: String?
    public let nextTrue : LKFMetaDataMelCustomCheck?
    public let nextFalse: LKFMetaDataMelCustomCheck?
    
    init (ptr: OpaquePointer, errorCode: String?, description: String?, nextTrue: LKFMetaDataMelCustomCheck? = nil, nextFalse: LKFMetaDataMelCustomCheck? = nil) {
        self.errorCode = errorCode
        self.description = description
        self.nextTrue = nextTrue
        self.nextFalse = nextFalse
        self.ptr = ptr
    }
    
    public func array() -> [LKFMetaDataMelCustomCheck] {
        guard nextTrue != nil || nextFalse != nil else {
            return [self]
        }
        
        var arr = [self]

        if let nextTrue {
            arr.append(contentsOf: nextTrue.array())
        }
        
        if let nextFalse {
            if nextTrue == nil {
                arr.append(contentsOf: nextFalse.array())
            }
            else {
                for x in nextFalse.array() {
                    if !arr.contains(where: { $0.ptr == x.ptr } ) {
                        arr.append(x)
                    }
                }
            }
        }
        return arr
    }
}

public class LKFMetaDataMelCustomChecks {
    
    public let root: LKFMetaDataMelCustomCheck
    
    static func create (contextPtr: OpaquePointer, checkPtr: OpaquePointer, errorBuffer: UnsafeMutablePointer<CChar>, descriptionBuffer: UnsafeMutablePointer<UnsafePointer<CChar>?>, trueBuffer: UnsafeMutablePointer<OpaquePointer?>, falseBuffer: UnsafeMutablePointer<OpaquePointer?>) -> LKFMetaDataMelCustomCheck {
        
        
        LibLKF.lkf_context_getMelCustomCheckData(contextPtr, checkPtr, errorBuffer, descriptionBuffer, trueBuffer, falseBuffer)
        
        let errorCode : String?
        let description: String?
        let truePointer: OpaquePointer?
        let falsePointer: OpaquePointer?
        
        let ec = String(cString: errorBuffer)
        if ec.count > 0 {
            errorCode = ec
        }
        else {
            errorCode = nil
        }
        
        if let p = descriptionBuffer.pointee {
            description = String(cString: p)
        }
        else {
            description = nil
        }
        
        truePointer = trueBuffer.pointee
        falsePointer = falseBuffer.pointee

        let trueChild : LKFMetaDataMelCustomCheck?
        let falseChild: LKFMetaDataMelCustomCheck?
        
        if let truePointer {
            trueChild = LKFMetaDataMelCustomChecks.create(contextPtr: contextPtr, checkPtr: truePointer, errorBuffer: errorBuffer, descriptionBuffer: descriptionBuffer, trueBuffer: trueBuffer, falseBuffer: falseBuffer)
        }
        else {
            trueChild = nil
        }
        
        if let falsePointer {
            falseChild = LKFMetaDataMelCustomChecks.create(contextPtr: contextPtr, checkPtr: falsePointer, errorBuffer: errorBuffer, descriptionBuffer: descriptionBuffer, trueBuffer: trueBuffer, falseBuffer: falseBuffer)
        }
        else {
            falseChild = nil
        }
                
        return LKFMetaDataMelCustomCheck(ptr: checkPtr, errorCode: errorCode, description: description, nextTrue: trueChild, nextFalse: falseChild)
    }
    
    init? (context: LKFContext, melIndex: UInt32) {
        
        guard let contextPtr = context.ptr else {
            return nil
        }
        
        guard let ptr = LibLKF.lkf_context_getMelCustomCheck(contextPtr, melIndex) else {
            return nil
        }
        
        let errorBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:12)
        let descriptionBuffer = UnsafeMutablePointer<UnsafePointer<CChar>?>.allocate(capacity:1)
        
        let trueBuffer = UnsafeMutablePointer<OpaquePointer?>.allocate(capacity: 1)
        let falseBuffer = UnsafeMutablePointer<OpaquePointer?>.allocate(capacity: 1)
        
        defer {
            errorBuffer.deallocate()
            descriptionBuffer.deallocate()
            trueBuffer.deallocate()
            falseBuffer.deallocate()
        }
        
        self.root = LKFMetaDataMelCustomChecks.create(contextPtr: contextPtr, checkPtr: ptr, errorBuffer: errorBuffer, descriptionBuffer: descriptionBuffer, trueBuffer: trueBuffer, falseBuffer: falseBuffer)
    }
    
    public func array() -> [LKFMetaDataMelCustomCheck] {
        return root.array()
    }
}
