--[[
    Logger

    This file contains functions to print debug info, warnings and errors.

    DISCLAIMER: For this script to work properly with the printDev functions, the game needs to get started with the
                parameter "-devWarnings"! Otherwise the printDev functions will not print anything!

        @author:    BayernGamers
        @date:      25.03.2026
        @version:   2.4

        History:    v1.0 @30.11.2023 - initial implementation in FS 22
                    -------------------------------------------------------------------------------------------
                    v2.0 @12.11.2024 - convert to FS25
                    -------------------------------------------------------------------------------------------
                    v2.1 @01.04.2025 - added xml DevWarning functions
                    -------------------------------------------------------------------------------------------
                    v2.2 @20.12.2025 - added console command to set debug level at runtime
                    -------------------------------------------------------------------------------------------
                    v2.3 @08.03.2026 - added possibility to add parameters after debugLevel for format strings
                                       in logging messages
                    -------------------------------------------------------------------------------------------
                    v2.4 @25.03.2026 - added option to enable dev infos on dedicated servers and print them in
                                       the console. Renamed Logger to Logger.
                    -------------------------------------------------------------------------------------------

        License:    Terms:
                        Usage:
                            Feel free to use this work as-is as long as you adhere to the following terms:
						Attribution:
							You must give appropriate credit to the original author when using this work.
						No Derivatives:
							You may not alter, transform, or build upon this work in any way.
						Usage: 
							The work may be used for personal and commercial purposes, provided it is not modified or adapted.
						Additional Clause:
							This script may not be converted, adapted, or incorporated into any other game versions or platforms except by GIANTS Software.
]]
Logger = {}
local Logger_mt = Class(Logger)

Logger.XML_SCHEMA = XMLSchema.new("Logger")
Logger.SERVER_XML = getUserProfileAppPath() .. "modSettings/logger.xml"
Logger.DEBUG_LEVELS = {
    LOW = 0,
    MEDIUM = 1,
    HIGH = 2,
    NONE = 3
}

local function initXMLSchema()
    local schema = Logger.XML_SCHEMA

    schema:register(XMLValueType.BOOL, "logger.enableServerDevInfo#enable", "Enable or disable server dev logs", false)
end

local function createServerDevInfoXML()
    local xmlFile = XMLFile.create("loggerXML", Logger.SERVER_XML, "logger", Logger.XML_SCHEMA)
    xmlFile:save()
    return xmlFile
end

local function loadEnableServerDevInfoFromXML()
    local xmlFile = XMLFile.loadIfExists("loggerXML", Logger.SERVER_XML, Logger.XML_SCHEMA)

    if xmlFile == nil then
        xmlFile = createServerDevInfoXML()
        xmlFile:setValue("logger.enableServerDevInfo#enable", false)
        xmlFile:save()
    end

    local enableServerDevInfo = xmlFile:getValue("logger.enableServerDevInfo#enable", false)

    return enableServerDevInfo
end

local function saveEnableServerDevInfoToXML(enable)
    local xmlFile = XMLFile.load("loggerXML", Logger.SERVER_XML, Logger.XML_SCHEMA)

    if xmlFile == nil then
        xmlFile = createServerDevInfoXML()
    end

    xmlFile:setValue("logger.enableServerDevInfo#enable", enable)
    xmlFile:save()
end

if g_globalMods.g_logger == nil then
    initXMLSchema()
    g_globalMods.g_logger = {}
    g_globalMods.g_logger.scriptsByMods = {}
    g_globalMods.g_logger.consoleCommandAdded = false
    g_globalMods.g_logger.enableServerDevInfo = loadEnableServerDevInfoFromXML()
end

g_isDedi = g_dedicatedServer ~= nil
g_logger = g_globalMods.g_logger

-- Create a new Logger object
-- @param enableDebug: If true, debug messages will be printed
-- @param debugLevel: The debug level of the messages
-- @param luaName: The name of the lua file
-- @return: The Logger
function Logger.new(enableDebug, debugLevel, luaName)
    local self = setmetatable({}, Logger_mt)

    self.enableDebug = enableDebug
    self.debugLevel = debugLevel
    self.luaName = luaName

    if g_logger.scriptsByMods[g_currentModName] == nil then
        g_logger.scriptsByMods[g_currentModName] = {}
    end

    if g_logger.scriptsByMods[g_currentModName][luaName] == nil then
        g_logger.scriptsByMods[g_currentModName][luaName] = self
    end

    if g_logger.consoleCommandAdded == false then
        g_logger.consoleCommandAdded = true
        addConsoleCommand("gsLoggerSetDebugLevel", "Set the debug level for a script", "consoleCommandSetDebugLevel", Logger, "[modName]; [scriptName]; [debugLevel]")
        addConsoleCommand("gsLoggerListScriptsByMod", "List all scripts registered for a mod", "consoleCommandListScriptsByMod", Logger, "[modName]")

        if not g_isDedi and g_server == nil then
            addConsoleCommand("gsLoggerEnableServerDevInfo", "Enable or disable server dev infos", "consoleCommandEnableServerDevInfo", Logger, "[true|false]")
            addConsoleCommand("gsLoggerSetServerDebugLevel", "Set the debug level for a script on the server", "consoleCommandSetServerDebugLevel", Logger, "[modName]; [scriptName]; [debugLevel]")
        end
    end

    return self
end

function Logger.consoleCommandSetDebugLevel(_, modName, scriptName, debugLevelStr)
    if not string.startsWith(modName, "FS25_") then
        modName = "FS25_" .. modName
    end

    if g_logger.scriptsByMods[modName] == nil then
        Logging.error("Mod not found: " .. tostring(modName))
        return
    end

    if not string.endsWith(scriptName, ".lua") then
        scriptName = scriptName .. ".lua"
    end

    if g_logger.scriptsByMods[modName][scriptName] == nil then
        Logging.error("Script not found: " .. tostring(scriptName) .. " in mod " .. tostring(modName))
        return
    end

    local debugLevel = Logger.DEBUG_LEVELS[debugLevelStr:upper()]
    if debugLevel == nil then
        Logging.error("Invalid debug level: " .. tostring(debugLevelStr) .. ". Valid levels are [LOW | MEDIUM | HIGH | NONE]")
        return
    end

    local logger = g_logger.scriptsByMods[modName][scriptName]
    logger.debugLevel = debugLevel
    Logging.info("Set debug level for '" .. tostring(scriptName) .. "' in mod '" .. tostring(modName) .. "' to '" .. tostring(debugLevelStr) .. "'")
end

function Logger.consoleCommandSetServerDebugLevel(_, modName, scriptName, debugLevelStr)
    if not string.startsWith(modName, "FS25_") then
        modName = "FS25_" .. modName
    end

    if g_logger.scriptsByMods[modName] == nil then
        Logging.error("Mod not found: " .. tostring(modName))
        return
    end

    if not string.endsWith(scriptName, ".lua") then
        scriptName = scriptName .. ".lua"
    end

    if g_logger.scriptsByMods[modName][scriptName] == nil then
        Logging.error("Script not found: " .. tostring(scriptName) .. " in mod " .. tostring(modName))
        return
    end

    local debugLevel = Logger.DEBUG_LEVELS[debugLevelStr:upper()]
    if debugLevel == nil then
        Logging.error("Invalid debug level: " .. tostring(debugLevelStr) .. ". Valid levels are [LOW | MEDIUM | HIGH | NONE]")
        return
    end

    SetServerDebugLevelEvent.sendEvent(modName, scriptName, debugLevel)
end

function Logger.consoleCommandListScriptsByMod(_, modName)
    if not string.startsWith(modName, "FS25_") then
        modName = "FS25_" .. modName
    end

    if g_logger.scriptsByMods[modName] == nil then
        Logging.error("Mod not found: " .. tostring(modName))
        return
    end

    print("Scripts registered for mod '" .. tostring(modName) .. "':")
    for scriptName, logger in pairs(g_logger.scriptsByMods[modName]) do
        local debugLevelStr = "UNKNOWN"
        for levelStr, levelValue in pairs(Logger.DEBUG_LEVELS) do
            if levelValue == logger.debugLevel then
                debugLevelStr = levelStr
                break
            end
        end
        print(" - " .. tostring(scriptName) .. " (Debug Level: " .. tostring(debugLevelStr) .. ")")
    end
end

function Logger.consoleCommandEnableServerDevInfo(_, enableStr)
    local valid = enableStr:lower() == "true" or enableStr:lower() == "false"

    if not valid then
        Logging.error("Invalid value for enable: " .. tostring(enableStr) .. ". Valid values are [true | false]")
        return
    end

    local enable = enableStr:lower() == "true"
    EnableServerDevInfoEvent.sendEvent(enable)
end

-- Print debug info
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevInfo(message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            print(string.format("  DevInfo: " .. prefix .. message, ...))
        else
            Logging.devInfo(prefix .. message, ...)
        end
    end
end

-- Print debug warning
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevWarning(message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            printWarning(string.format("  DevWarning: " .. prefix .. message, ...))
        else
            Logging.devWarning(prefix .. message, ...)
        end
    end
end

-- Print debug error
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevError(message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            printError(string.format("  DevError: " .. prefix .. message, ...))
        else
            Logging.devError(prefix .. message, ...)
        end
    end
end

-- Print info
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printInfo(message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.info(prefix .. message, ...)
end

-- Print warning
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printWarning(message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.warning(prefix .. message, ...)
end

-- Print error
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printError(message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.error(prefix .. message, ...)
end

-- Print XML info
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printXMLInfo(xmlFile, message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.xmlInfo(xmlFile, prefix .. message, ...)
end

-- Print XML warning
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printXMLWarning(xmlFile, message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.xmlWarning(xmlFile, prefix .. message, ...)
end

-- Print XML error
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printXMLError(xmlFile, message, ...)
    local prefix = ""
    prefix = "::" .. self.luaName .. ":: "
    Logging.xmlError(xmlFile, prefix .. message, ...)
end

-- Print XML info with debug level
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevXMLInfo(xmlFile, message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            local filename = getFilename(xmlFile)
		    print(string.format("  DevInfo (%s): " .. prefix .. message, filename, ...))
        else
            Logging.xmlDevInfo(xmlFile, prefix .. message, ...)
        end
    end
end

-- Print XML warning with debug level
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevXMLWarning(xmlFile, message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            local filename = getFilename(xmlFile)
            printWarning(string.format("  DevWarning (%s): " .. prefix .. message, filename, ...))
        else
            Logging.xmlDevWarning(xmlFile, prefix .. message, ...)
        end
    end
end

-- Print XML error with debug level
-- @param xmlFile: the xml file
-- @param message: the message to print
-- @param debugLevel: the debug level of the message
-- @param addPrefix: if true, the luaName will be added as prefix to the message
-- @param luaName: the name of the lua file
function Logger:printDevXMLError(xmlFile, message, debugLevel, ...)
    if self.enableDebug and debugLevel >= self.debugLevel then
        local prefix = ""
        prefix = "::" .. self.luaName .. ":: "

        if g_isDedi and g_logger.enableServerDevInfo then
            local filename = getFilename(xmlFile)
            printError(string.format("  DevError (%s): " .. prefix .. message, filename, ...))
        else
            Logging.xmlDevError(xmlFile, prefix .. message, ...)
        end
    end
end

EnableServerDevInfoEvent = {}

EnableServerDevInfoEvent_mt = Class(EnableServerDevInfoEvent, Event)

InitEventClass(EnableServerDevInfoEvent, "EnableServerDevInfoEvent")

function EnableServerDevInfoEvent:emptyNew()
    local self = Event.new(EnableServerDevInfoEvent_mt)
    return self
end

function EnableServerDevInfoEvent.new(enable)
    local self = EnableServerDevInfoEvent.emptyNew()
    self.enable = enable
    return self
end

function EnableServerDevInfoEvent:writeStream(streamId, connection)
    streamWriteBool(streamId, self.enable)
end

function EnableServerDevInfoEvent:readStream(streamId, connection)
    self.enable = streamReadBool(streamId)
    self:run(connection)
end

function EnableServerDevInfoEvent:run(connection)
    if g_isDedi then
        g_logger.enableServerDevInfo = self.enable
        saveEnableServerDevInfoToXML(self.enable)
    end
end

function EnableServerDevInfoEvent.sendEvent(enable, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if not g_isDedi and g_server == nil then
            g_client:getServerConnection():sendEvent(EnableServerDevInfoEvent.new(enable))
        end
    end
end


SetServerDebugLevelEvent = {}
SetServerDebugLevelEvent_mt = Class(SetServerDebugLevelEvent, Event)

InitEventClass(SetServerDebugLevelEvent, "SetServerDebugLevelEvent")

function SetServerDebugLevelEvent:emptyNew()
    local self = Event.new(SetServerDebugLevelEvent_mt)
    return self
end

function SetServerDebugLevelEvent.new(modName, scriptName, debugLevel)
    local self = SetServerDebugLevelEvent.emptyNew()
    self.modName = modName
    self.scriptName = scriptName
    self.debugLevel = debugLevel
    return self
end

function SetServerDebugLevelEvent:writeStream(streamId, connection)
    streamWriteString(streamId, self.modName)
    streamWriteString(streamId, self.scriptName)
    streamWriteInt32(streamId, self.debugLevel)
end

function SetServerDebugLevelEvent:readStream(streamId, connection)
    self.modName = streamReadString(streamId)
    self.scriptName = streamReadString(streamId)
    self.debugLevel = streamReadInt32(streamId)
    self:run(connection)
end

function SetServerDebugLevelEvent:run(connection)
    if g_isDedi then
        if g_logger.scriptsByMods[self.modName] ~= nil and g_logger.scriptsByMods[self.modName][self.scriptName] ~= nil then
            local logger = g_logger.scriptsByMods[self.modName][self.scriptName]
            logger.debugLevel = self.debugLevel
            Logging.info("Set debug level for '" .. tostring(self.scriptName) .. "' in mod '" .. tostring(self.modName) .. "' to '" .. tostring(self.debugLevel) .. "'")
        else
            Logging.error("Script not found: " .. tostring(self.scriptName) .. " in mod " .. tostring(self.modName))
        end
    end
end

function SetServerDebugLevelEvent.sendEvent(modName, scriptName, debugLevel, noEventSend)
    if noEventSend == nil or noEventSend == false then
        if not g_isDedi and g_server == nil then
            g_client:getServerConnection():sendEvent(SetServerDebugLevelEvent.new(modName, scriptName, debugLevel))
        end
    end
end