//
//  LKFMetaDataGroup.swift
//  LKF
//
//  Created by Alexander Holzer on 17.01.22.
//  Copyright © 2022 Alexander Holzer. All rights reserved.
//

import Foundation
@_implementationOnly import LibLKF

public class LKFMetaDataGroup {
    public let group: String
    public let groupLabel: String?
    public weak var context: LKFContext?
    public let rootNode : LKFMetaDataGroupNode
    let groupPtr: OpaquePointer?
    
    public init (context: LKFContext, group: String? = nil, from buffer: Data? = nil, encodings: [String.Encoding]? = nil, ignoreUnknownFields: Bool = false) throws {
        
        guard let ptr = context.ptr else {
            if let contextError = context.lastError?.message {
                throw LKFError("context not initialized: \(contextError)")
            }
            else {
                throw LKFError("context not initialized")
            }
        }

        self.context = context
        
        let encodings = encodings ?? [.utf8, .ascii, .isoLatin1, .windowsCP1252]
        
        let groupName: String
        let groupPtr: OpaquePointer
        
        if let buffer {
            let stringBuffer = buffer.toString(encodings: encodings)
            
            guard let stringBuffer else {
                if let first = encodings.first {
                    throw LKFError("buffer for \(group ?? "group") cannot be encoded using \(first)")
                }
                throw LKFError(message: "buffer for \(group ?? "group") cannot be encoded: no encoding")
            }
            
            let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:1024)
            defer {
                lineBuffer.deallocate()
            }
            guard let p = LibLKF.lkf_stamm_getGroupFromBuffer(ptr, context.model, stringBuffer.0.cString(using: .utf8), lineBuffer) else {
                let errorString = "file cannot be read: \(String(cString: lineBuffer))"
                throw LKFError(errorString)
            }
            
            groupPtr = p
            self.groupPtr = groupPtr
            LibLKF.lkf_stamm_getGroupInfo(groupPtr, 1, lineBuffer, 1024)
            groupName = String(cString: lineBuffer)
            LibLKF.lkf_stamm_getGroupInfo(groupPtr, 2, lineBuffer, 1024)
            groupLabel = String(cString: lineBuffer).trimmingCharacters(in: .whitespacesAndNewlines)
        }
        else if let group {
            guard let p = LibLKF.lkf_stamm_getGroup (ptr, group.cString(using: .utf8)) else {
                throw LKFError("group \(group) cannot be found")
            }
            groupPtr = p
            self.groupPtr = nil
            groupName = group
            let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:1024)
            LibLKF.lkf_stamm_getGroupInfo(groupPtr, 2, lineBuffer, 1024)
            groupLabel = String(cString: lineBuffer).trimmingCharacters(in: .whitespacesAndNewlines)
            lineBuffer.deallocate()
        }
        else {
            throw LKFError("cannot read group - neither name nor buffer provided")
        }
        
        guard let rootNode = LKFMetaDataGroupNode.nodeForIndex(context: context, ptr: groupPtr, index: 0) else {
            throw LKFError("cannot find root node in \(groupName)")
        }
        
        self.rootNode = rootNode
        self.group = groupName
    }
    
    deinit {
        if let ptr = context?.ptr, let groupPtr {
            LibLKF.lkf_stamm_freeGroupFromBuffer(ptr, groupPtr)
        }
    }
    
    public func minMaxPoints() -> (Double,Double)? {
        return rootNode.minMaxPoints()
    }
    
    public var isDiagGroup : Bool {
        return group.starts(with: "HDG")
    }
    
    public func allDiags () -> [LKFMetaDataDiag]? {
        guard let diags = context?.metaData.allDiags() else {
            return nil
        }
        return diags.filter { $0.GroupName.starts(with: group) }
    }
    
    public func allMels () -> [LKFMetaDataMel]? {
        
        guard let mels = context?.metaData.allMels() else {
            return nil
        }
        return mels.filter { $0.AmbGroupName.starts(with: group) || $0.StatGroupName.starts(with: group) }
    }
    
    public func minMaxLP() -> (Double,Double)? {
        return rootNode.minMaxLP()
    }
    
    public func getLeafCount() -> Int {
        return rootNode.leafCount
    }
}
