//
//  Logging.swift
//  Astro
//
//  Created by Jeremy Wiebe on 2015-04-28.
//  Copyright (c) 2015 Mobify Research & Development Inc. All rights reserved.
//

import Foundation

public enum AstroLogLevel: Int32, CustomStringConvertible {
    case error = 0
    case info = 1
    case debug = 2

    public var description: String {
        switch self {
        case .error: return "ERR"
        case .info: return "INF"
        case .debug: return "DBG"
        }
    }
}

extension AstroLogLevel: Comparable {
    public static func <(lhs: AstroLogLevel, rhs: AstroLogLevel) -> Bool {
        return lhs.rawValue < rhs.rawValue
    }

    public static func ==(lhs: AstroLogLevel, rhs: AstroLogLevel) -> Bool {
        return lhs.rawValue == rhs.rawValue
    }
}

public class AstroLogger {
    public let logName: String
    public var enabled: Bool
    public var level: AstroLogLevel
    private static let logger: UnsafeMutablePointer<Logger> = {
        let logger = LoggerInit()

        LoggerSetOptions(logger,
                         UInt32(kLoggerOption_LogToConsole
                            | kLoggerOption_BufferLogsUntilConnection
                            | kLoggerOption_BrowseBonjour
                            | kLoggerOption_BrowseOnlyLocalDomain))

        LoggerStart(logger)

        return logger!
    }()

    public init(logName: String, enabled: Bool, level: AstroLogLevel) {
        self.logName = logName
        self.enabled = enabled
        self.level = level
    }

    public func debug(_ message: @autoclosure () -> String, file: String = #file, lineNumber: Int32 = #line, function: String = #function) {
        logMessage(.debug, message: message(), file: file, lineNumber: lineNumber, function: function)
    }

    public func info(_ message: @autoclosure () -> String, file: String = #file, lineNumber: Int32 = #line, function: String = #function) {
        logMessage(.info, message: message(), file: file, lineNumber: lineNumber, function: function)
    }

    public func error(_ message: @autoclosure () -> String, file: String = #file, lineNumber: Int32 = #line, function: String = #function) {
        logMessage(.error, message: message(), file: file, lineNumber: lineNumber, function: function)
    }

    private func logMessage(_ level: AstroLogLevel, message: @autoclosure () -> String, file: UnsafePointer<Int8>, lineNumber: Int32, function: UnsafePointer<Int8>) {
        if enabled && self.level >= level {
            LogMessageRawToF(AstroLogger.logger, file, lineNumber, function, logName, level.rawValue, "\(level) \(message())")
        }
    }
}

public struct AstroLog {
    public static let Application = "Application"
    public static let Localization = "Localization"
    public static let WebAdaptor = "Web Adaptor"
    public static let Messaging = "Messaging"
    public static let Plugins = "Plugins"
    public static let Json = "JSON"
    public static let Worker = "Worker"

    private static var loggers = [
        AstroLogger(logName: Application, enabled: true, level: .debug),
        AstroLogger(logName: Localization, enabled: true, level: .debug),
        AstroLogger(logName: WebAdaptor, enabled: true, level: .debug),
        AstroLogger(logName: Messaging, enabled: true, level: .debug),
        AstroLogger(logName: Plugins, enabled: true, level: .debug),
        AstroLogger(logName: Json, enabled: true, level: .debug),
        AstroLogger(logName: Worker, enabled: true, level: .debug)
    ]

    private static var loggerLookup = [String: AstroLogger]()

    public static func logger(_ logName: String) -> AstroLogger {
        if let logger = AstroLog.loggerLookup[logName] {
            return logger
        } else if let logger = AstroLog.loggers.first(where: { $0.logName == logName }) {
            loggerLookup[logName] = logger
            return logger
        } else {
            fatalError("There is no longer named '\(logName)'")
        }
    }

    public static func disableAllBut(_ logNames: String...) {
        for logger in loggers {
            logger.enabled = logNames.contains(logger.logName)
        }
    }

    public static func addLogger(_ logger: AstroLogger) {
        for l in loggers {
            if l.logName == logger.logName {
                fatalError("Logger named '\(l.logName)' already exists.  Re-configure existing logger instead of overwriting it.")
            }
        }

        loggers.append(logger)
    }

    public static func forEachLogger(_ action: (AstroLogger) -> Void) {
        for logger in loggers {
            action(logger)
        }
    }
}
