//
//  Glider
//  Fast, Lightweight yet powerful logging system for Swift.
//
//  Created by Daniele Margutti
//  Email: hello@danielemargutti.com
//  Web: http://www.danielemargutti.com
//
//  Copyright ©2021 Daniele Margutti. All rights reserved.
//  Licensed under MIT License.
//

import Foundation

/// `StringDataFormatter` is a formatter which transform payloads and scopes into `String`.
/// It's the most common formatter because it's used in a variety of transports.
open class StructuredFormatter: LogFormatter {
    
    // MARK: - Public Properties
    
    /// Fields used to produce the output.
    public let formattedFields: [StructuredFormatterField]
    
    // MARK: - Private Properties
    
    private static let startSign = "•"
    
    // MARK: - Initialization
    
    /// Initialize a new structured string formatter with the following fields to get.
    ///
    /// - Parameter fields: fields to get along their own formatting options.
    public init(format fields: [StructuredFormatterField]) {
        self.formattedFields = fields
    }
    
    /// Instantiate a new formatter for payload which will be recorder on a file or console.
    ///
    /// - Returns: `FieldPayloadFormatter`
    public static func defaultStringFormatter() -> StructuredFormatter {
        let fields: [StructuredFormatterField] = [
            .init(.timestamp(.iso8601), padding: .right(columns: 20)), // timestamp
            .pipeDelimiter,
            .init(.level(.short), padding: .left(columns: 3)), // severity level
            .literal(" "),
            .init(.message) // message
        ]
        return StructuredFormatter(format: fields)
    }
    
    // MARK: - Public Functions
    
    public func format(_ item: LoggableRecord) throws -> LogTransportFormattedData {
        switch item {
        case let payload as LogPayload:
            return try format(payload: payload)
            
        case let scope as LogScopeContext:
            return try format(scope: scope)
        
        default:
            throw GliderError(.generic, reason: "\(String(describing: item)) format is not supported.")
        }
    }
    
    open func format(payload: LogPayload) throws -> LogTransportFormattedData {
        valueForFieldsInPayload(payload).reduce(into: String()) { result, field in
            result.append(field.value ?? "")
        }
    }
    
    open func format(scope: LogScopeContext) throws -> LogTransportFormattedData {
        valueForFieldsInScope(scope.scope, entered: scope.entered).reduce(into: String()) { result, field in
            result.append(field.value ?? "")
        }
    }
    
    // MARK: - Private Functions
    
    open func valueForFieldsInPayload(_ payload: LogPayload) -> [(field: StructuredFormatterField, value: String?)] {
        formattedFields.map { field in
            var value = payload.value(forField: field.kind)?.stringRepresentation
            return formatTextValue(&value, field: field)
        }
    }
    
    open func valueForFieldsInScope(_ scope: LogScope, entered: Bool) -> [(field: StructuredFormatterField, value: String?)] {
        formattedFields.map { field in
            var value = scope.value(forField: field.kind, entered: entered)?.stringRepresentation
            return formatTextValue(&value, field: field)
        }
    }
    
    private func formatTextValue(_ value: inout String?, field: StructuredFormatterField) -> (field: StructuredFormatterField, value: String?) {
        if let transform = field.transform { // custom text transform
            value = transform(value)
        }
        
        value = value?.trunc(field.truncate).padded(field.padding)
        return (field, value)
    }
    
}
