//
//  LKFMetaDataFilesContext.swift
//  lkfc
//
//  Created by Alexander Holzer on 03.04.24.
//  Copyright © 2024 Alexander Holzer. All rights reserved.
//

import Foundation
@_implementationOnly import LibLKF


actor LKFMetaDataGroupFileLoader {
    
    public var groups: [String:LKFMetaDataGroup]?
    
    public func get (fileContext: LKFMetaDataFilesContext, group: String, encodings: [String.Encoding]? = nil) throws -> LKFMetaDataGroup {
        if let groups, let g = groups[group] {
            return g
        }
        let data = try fileContext.load(file: group)
        let metaDataGroup = try LKFMetaDataGroup(context: fileContext.lkfContext, group: group, from: data, encodings: encodings)
        if groups != nil {
            groups![group] = metaDataGroup
        }
        else {
            groups = [group: metaDataGroup]
        }
        return metaDataGroup
    }
}

open class LKFMetaDataFilesContext {
    public let model: UInt32
    public let baseUrl : URL
    public let urlWithYear : URL
    public let name: String
    public var stammPtr : OpaquePointer?
    private var _lkfContext : LKFContext?
    private var _metaData: LKFMetaData?
    public var loadErrorText : String?
    
    let groupFileLoader = LKFMetaDataGroupFileLoader()
    
    public init (model: UInt32, name: String? = nil, baseUrl: URL) {
        self.model = model
        self.baseUrl = baseUrl
        self.name = name ?? ""
        
        let yearUrl = baseUrl.appending(path: "\(model)", directoryHint: .isDirectory)
        if FileManager.default.fileExists(atPath: yearUrl.path()) {
            self.urlWithYear = yearUrl
        }
        else {
            self.urlWithYear = baseUrl
        }
    }
    
    public var label : String {
        return "\(name) (\(model))"
    }
    
    public var lkfContext : LKFContext {
        if let _lkfContext {
            return _lkfContext
        }
        do {
            _lkfContext = try LKFContext(metaDataContext: self)
        }
        catch {
            _lkfContext = LKFContext(error: error)
        }
        return _lkfContext!
    }
    
    public func equal (to context: LKFMetaDataFilesContext?) -> Bool {
        
        guard let context else {
            return false
        }
        
        return model == context.model && baseUrl == context.baseUrl
    }
    
    deinit {
        if let stammPtr {
            LibLKF.lkf_free_stamm (stammPtr)
            self.stammPtr = nil
        }
    }
    
    public func unload() {
        if let stammPtr {
            LibLKF.lkf_free_stamm (stammPtr)
            self.stammPtr = nil
        }
    }
    
    public func load() throws -> OpaquePointer {
        
        if let stammPtr {
            return stammPtr
        }
        
        let customPath = urlWithYear.path()
        try prepareMetaDataAccess()
        let lineBuffer = UnsafeMutablePointer<CChar>.allocate(capacity:2048)
        self.stammPtr = LibLKF.lkf_alloc_stamm(model, customPath.cString(using: .utf8), model, lineBuffer)
        finishMetaDataAccess()
        let s = String(cString: lineBuffer)
        lineBuffer.deallocate()
        
        if let stamm = self.stammPtr {
            self.loadErrorText = s
            return stamm
        }
                
        if s.count > 0 {
            throw LKFError(message: s)
        }
        throw LKFError("cannot load private metadata from \(customPath)")
    }
    
    open func prepareMetaDataAccess() throws {
        
    }
    
    open func finishMetaDataAccess() {
        
    }
    
    open func possibleLocations (for file: String) -> [URL]? {
        if file.count == 8, let group = file.substr(to:3)?.uppercased() {

            return [urlWithYear.appending(component: file, directoryHint: .notDirectory),
                    urlWithYear.appending(component: group, directoryHint: .isDirectory).appending(component: file, directoryHint: .notDirectory)]
        }
        else {
            return [urlWithYear.appending(component: file, directoryHint: .notDirectory)]
        }
    }
    
    open func load (file: String) throws -> Data {
        guard let possibleLocations = possibleLocations(for: file) else {
            throw LKFError("load not supported without baseUrl")
        }
        
        var firstError : Error?
        for pl in possibleLocations {
            do {
                let data = try Data(contentsOf: pl)
                return data
            }
            catch {
                if firstError == nil {
                    firstError = error
                }
            }
        }
        
        if let firstError {
            throw firstError
        }
        return Data() // unreachable
    }
    
    public func get (group: String, encodings: [String.Encoding]? = nil) async throws -> LKFMetaDataGroup {
        return try await groupFileLoader.get(fileContext: self, group: group, encodings: encodings)
    }
    
    public var metaData: LKFMetaData {
        if let _metaData {
            return _metaData
        }
        let metaData = LKFMetaData(context: lkfContext)
        _metaData = metaData
        return metaData
    }
}
