/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:          log.inc
 *  Type:          Core
 *  Description:   Logging API.
 *
 *  Copyright (C) 2009-2013  Greyscale, Richard Helgeby
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * ============================================================================
 */

/*
 * Note: See log.h.inc for header types and defines.
 */

LogInit()
{
    // Destroy existing handle to prevent memory leak.
    if (hLogModuleFilter != INVALID_HANDLE)
    {
        CloseHandle(hLogModuleFilter);
    }

    // Initialize module filter array.
    hLogModuleFilter = CreateArray(32);
}

/**
 * Convert module type to a string.
 *
 * @param buffer        Destination string buffer.
 * @param maxlen        Size of destination buffer.
 * @param module        Module type to convert.
 * @param shortName     Optional. Use short name instead of human readable
 *                      names. Default is false
 * @return              Number of cells written.
 */
LogGetModuleNameString(String:buffer[], maxlen, LogModules:module, bool:shortName = false)
{
    switch (module)
    {
        case LogModule_Account:
        {
            return shortName ? strcopy(buffer, maxlen, "account") : strcopy(buffer, maxlen, "Account");
        }
        case LogModule_AntiStick:
        {
            return shortName ? strcopy(buffer, maxlen, "antistick") : strcopy(buffer, maxlen, "Anti-Stick");
        }
        case LogModule_Config:
        {
            return shortName ? strcopy(buffer, maxlen, "config") : strcopy(buffer, maxlen, "Config");
        }
        case LogModule_Cvars:
        {
            return shortName ? strcopy(buffer, maxlen, "cvars") : strcopy(buffer, maxlen, "CVARs");
        }
        case LogModule_Damage:
        {
            return shortName ? strcopy(buffer, maxlen, "damage") : strcopy(buffer, maxlen, "Damage");
        }
        case LogModule_Downloads:
        {
            return shortName ? strcopy(buffer, maxlen, "downloads") : strcopy(buffer, maxlen, "Downloads");
        }
        case LogModule_Hitgroups:
        {
            return shortName ? strcopy(buffer, maxlen, "hitgroups") : strcopy(buffer, maxlen, "Hit Groups");
        }
        case LogModule_Infect:
        {
            return shortName ? strcopy(buffer, maxlen, "infect") : strcopy(buffer, maxlen, "Infect");
        }
        case LogModule_Models:
        {
            return shortName ? strcopy(buffer, maxlen, "models") : strcopy(buffer, maxlen, "Models");
        }
        case LogModule_Napalm:
        {
            return shortName ? strcopy(buffer, maxlen, "napalm") : strcopy(buffer, maxlen, "Napalm");
        }
        case LogModule_Playerclasses:
        {
            return shortName ? strcopy(buffer, maxlen, "playerclasses") : strcopy(buffer, maxlen, "Player Classes");
        }
        case LogModule_VEffects:
        {
            return shortName ? strcopy(buffer, maxlen, "veffects") : strcopy(buffer, maxlen, "Visual Effects");
        }
        case LogModule_SEffects:
        {
            return shortName ? strcopy(buffer, maxlen, "seffects") : strcopy(buffer, maxlen, "Sound Effects");
        }
        case LogModule_Tools:
        {
            return shortName ? strcopy(buffer, maxlen, "tools") : strcopy(buffer, maxlen, "Tools");
        }
        case LogModule_Volfeatures:
        {
            return shortName ? strcopy(buffer, maxlen, "volfeatures") : strcopy(buffer, maxlen, "Volumetric Features");
        }
        case LogModule_Weapons:
        {
            return shortName ? strcopy(buffer, maxlen, "weapons") : strcopy(buffer, maxlen, "Weapons");
        }
        case LogModule_Weaponrestrict:
        {
            return shortName ? strcopy(buffer, maxlen, "weaponrestrict") : strcopy(buffer, maxlen, "Weapon Restrictions");
        }
        case LogModule_ZSpawn:
        {
            return shortName ? strcopy(buffer, maxlen, "zspawn") : strcopy(buffer, maxlen, "ZSpawn");
        }
        case LogModule_ZTele:
        {
            return shortName ? strcopy(buffer, maxlen, "ztele") : strcopy(buffer, maxlen, "ZTele");
        }
    }

    // Module mismatch.
    return 0;
}

/**
 * Converts a string module name into a module type.
 *
 * @param moduleName    A string with the short module name. Case insensitive,
 *                      but not trimmed for white space.
 * @return              The matcing module type or LogModules_Invalid if failed.
 */
LogModules:LogGetModule(const String:moduleName[])
{
    if (StrEqual(moduleName, "account", false))
    {
        return LogModule_Account;
    }
    else if (StrEqual(moduleName, "antistick", false))
    {
        return LogModule_AntiStick;
    }
    else if (StrEqual(moduleName, "config", false))
    {
        return LogModule_Config;
    }
    else if (StrEqual(moduleName, "cvars", false))
    {
        return LogModule_Cvars;
    }
    else if (StrEqual(moduleName, "damage", false))
    {
        return LogModule_Damage;
    }
    else if (StrEqual(moduleName, "downloads", false))
    {
        return LogModule_Downloads;
    }
    else if (StrEqual(moduleName, "hitgroups", false))
    {
        return LogModule_Hitgroups;
    }
    else if (StrEqual(moduleName, "infect", false))
    {
        return LogModule_Infect;
    }
    else if (StrEqual(moduleName, "models", false))
    {
        return LogModule_Models;
    }
    else if (StrEqual(moduleName, "napalm", false))
    {
        return LogModule_Napalm;
    }
    else if (StrEqual(moduleName, "playerclasses", false))
    {
        return LogModule_Playerclasses;
    }
    else if (StrEqual(moduleName, "veffects", false))
    {
        return LogModule_VEffects;
    }
    else if (StrEqual(moduleName, "seffects", false))
    {
        return LogModule_SEffects;
    }
    else if (StrEqual(moduleName, "tools", false))
    {
        return LogModule_Tools;
    }
    else if (StrEqual(moduleName, "volfeatures", false))
    {
        return LogModule_Volfeatures;
    }
    else if (StrEqual(moduleName, "weapons", false))
    {
        return LogModule_Weapons;
    }
    else if (StrEqual(moduleName, "weaponrestrict", false))
    {
        return LogModule_Weaponrestrict;
    }
    else if (StrEqual(moduleName, "zspawn", false))
    {
        return LogModule_ZSpawn;
    }
    else if (StrEqual(moduleName, "ztele", false))
    {
        return LogModule_ZTele;
    }

    // No match.
    return LogModule_Invalid;
}

/**
 * Check if the specified log flag is set.
 *
 * @param eventType     The log flag to check.
 * @return              True if set, false otherwise.
 */
bool:LogCheckFlag(eventType)
{
    // Check if eventType is set.
    if (GetConVarInt(g_hCvarsList[CVAR_LOG_FLAGS]) & eventType)
    {
        return true;
    }
    else
    {
        return false;
    }
}

/**
 * Check if the specified module is enabled in the log module filter cache.
 *
 * @param module        Module to check.
 * @return              True if enabled, false otherwise.
 */
bool:LogCheckModuleFilter(LogModules:module)
{
    if (LogModuleFilterCache[module])
    {
        return true;
    }
    else
    {
        return false;
    }
}

/**
 * Print a formatted message to logs depending on log settings.
 *
 * @param isConsole     Optional. Specifies whether the log event came from
 *                      client 0. Used in console commands, do not mix with
 *                      regular log events. Default is false.
 * @param logType       Optional. Log type and action. Default is
 *                      LogType_Normal.
 * @param eventType     Optional. A log flag describing What kind of log event
 *                      it is. Default is LOG_CORE_EVENTS.
 * @param module        Module the log event were executed in.
 * @param description   Event type or function name. A short descriptive phrase
 *                      to group together similar logs.
 * @param text          Log message. Can be formatted.
 * @param ...           Formatting parameters.
 */
LogEvent(bool:isConsole = false, LogTypes:logType = LogType_Normal, eventType = LOG_CORE_EVENTS, LogModules:module, const String:description[], const String:text[], any:...)
{
    // Check filter overrides. Always log fatal errors, and check error override setting on error log types.
    if ((logType != LogType_Fatal && logType != LogType_Error) || (logType == LogType_Error && !GetConVarBool(g_hCvarsList[CVAR_LOG_ERROR_OVERRIDE])))
    {
        // Check if logging is disabled.
        if (!GetConVarBool(g_hCvarsList[CVAR_LOG]))
        {
            return;
        }

        // Check if console is ignored.
        if (isConsole && GetConVarBool(g_hCvarsList[CVAR_LOG_IGNORE_CONSOLE]))
        {
            return;
        }

        // Check event type (log flag).
        if (!LogCheckFlag(eventType))
        {
            return;
        }

        // Check if module filtering is enabled.
        if (GetConVarBool(g_hCvarsList[CVAR_LOG_MODULE_FILTER]))
        {
            // Check if the specified module is enabled.
            if (!LogCheckModuleFilter(module))
            {
                return;
            }
        }
    }

    // Format extra parameters into the log buffer.
    decl String:logbuffer[LOG_MAX_LENGTH_FILE];
    VFormat(logbuffer, sizeof(logbuffer), text, 7);

    // Get human readable module name.
    new String:modulename[64];
    LogGetModuleNameString(modulename, sizeof(modulename), module);

    // Format
    Format(logbuffer, sizeof(logbuffer), "[%s] [%s] %s", modulename, description, logbuffer);

    // Format other parameters onto the log text.
    switch (logType)
    {
        // Log type is normal.
        case LogType_Normal:
        {
            LogMessage(logbuffer);
        }
        // Log type is error.
        case LogType_Error:
        {
            LogError(logbuffer);
        }
        // Log type is fatal error.
        case LogType_Fatal:
        {
            SetFailState(logbuffer);
        }
    }

    // Note: The phrase "Literal text" is a blank phrase to pass any string we want into it.

    // Check if printing log events to admins is enabled.
    if (GetConVarBool(g_hCvarsList[CVAR_LOG_PRINT_ADMINS]))
    {
        // Print text to admins.
        TranslationPrintToChatAll(false, true, "Literal text", logbuffer);
    }

    // Check if printing log events to public chat is enabled.
    if (GetConVarBool(g_hCvarsList[CVAR_LOG_PRINT_CHAT]))
    {
        // Print text to public chat.
        TranslationPrintToChatAll(false, false, "Literal text", logbuffer);
    }
}

/**
 * Adds a module to the module filter and updates the cache. If it already
 * exist the command is ignored.
 *
 * @param module    The module to add.
 * @return          True if added, false otherwise.
 */
bool:LogModuleFilterAdd(LogModules:module)
{
    decl String:modulename[64];

    // Check if empty.
    if (strlen(modulename) == 0)
    {
        return false;
    }

    // Convert module name.
    LogGetModuleNameString(modulename, sizeof(modulename), module, true);

    // Check if the module isn't already is listed.
    if (FindStringInArray(hLogModuleFilter, modulename) < 0)
    {
        // Add module to filter.
        PushArrayString(hLogModuleFilter, modulename);
        return true;
    }

    return false;
}

/**
 * Removes a module to the module filter and updates the cache. If it doesn't
 * exist the command is ignored.
 *
 * @param module    The module to remove.
 * @return          True if removed, false otherwise.
 */
bool:LogModuleFilterRemove(LogModules:module)
{
    decl String:modulename[64];
    new moduleindex;

    // Check if empty.
    if (strlen(modulename) == 0)
    {
        return false;
    }

    // Convert module name.
    LogGetModuleNameString(modulename, sizeof(modulename), module, true);

    // Get the module index.
    moduleindex = FindStringInArray(hLogModuleFilter, modulename);

    // Check if successful.
    if (moduleindex >= 0)
    {
        // Remove module from filter.
        RemoveFromArray(hLogModuleFilter, moduleindex);
        return true;
    }

    return false;
}

/**
 * Update module filter cache.
 */
LogModuleFilterCacheUpdate()
{
    decl String:modulename[64];
    new LogModules:moduletype;
    new modulecount;
    new filtersize;

    // Clear all entries in module cache.
    modulecount = sizeof(LogModuleFilterCache);
    for (new module = 1; module < modulecount; module++)
    {
        LogModuleFilterCache[LogModules:module] = false;
    }

    // Loop through the module array.
    filtersize = GetArraySize(hLogModuleFilter);
    for (new index = 0; index < filtersize; index++)
    {
        // Get the module name.
        GetArrayString(hLogModuleFilter, index, modulename, sizeof(modulename));

        // Convert to type.
        moduletype = LogGetModule(modulename);

        // Validate type.
        if (moduletype != LogModule_Invalid)
        {
            // Set value in cache.
            LogModuleFilterCache[moduletype] = true;
        }
    }
}

/**
 * Creates commands for logging module. Called when commands are created.
 */
LogOnCommandsCreate()
{
    RegConsoleCmd("zr_log_list", Command_LogList, "List available logging flags and modules with their status values.");
    RegConsoleCmd("zr_log_add_module", Command_LogAddModule, "Add one or more modules to the module filter. Usage: zr_log_add_module <module> [module] ...");
    RegConsoleCmd("zr_log_remove_module", Command_LogRemoveModule, "Remove one or more modules from the module filter. Usage: zr_log_remove_module <module> [module] ...");
}

/**
 * Handles the zr_log_list command. Displays flags and module filter cache.
 *
 * @param client    The client that executed the command.
 * @param argc      Number of arguments passed.
 */
public Action:Command_LogList(client, argc)
{
    decl String:buffer[2048];
    decl String:linebuffer[96];
    decl String:modulename[64];
    decl String:modulenameshort[64];

    new modulecount;

    // Strings to store translated phrases. Because formatting width settings
    // doesn't work with "%t", but "%s".
    decl String:phrasegenericflag[32];
    decl String:phrasevalue[32];
    decl String:phrasemodule[32];
    decl String:phraseshortname[32];

    // Quick initialize string buffer.
    buffer[0] = 0;

    // Set language.
    SetGlobalTransTarget(client);

    // Get phrases.
    Format(phrasegenericflag, sizeof(phrasegenericflag), "%t", "Log Generic Flag");
    Format(phrasevalue, sizeof(phrasevalue), "%t", "Log Value");
    Format(phrasemodule, sizeof(phrasemodule), "%t", "Log Module");
    Format(phraseshortname, sizeof(phraseshortname), "%t", "Log Module Short Name");

    // Log flags:
    Format(linebuffer, sizeof(linebuffer), "%-19s %-7s %t\n", phrasegenericflag, phrasevalue, "Log Status");
    StrCat(buffer, sizeof(buffer), linebuffer);
    StrCat(buffer, sizeof(buffer), "--------------------------------------------------------------------------------\n");

    Format(linebuffer, sizeof(linebuffer), "LOG_CORE_EVENTS     1       %t\n", LogCheckFlag(LOG_CORE_EVENTS) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    Format(linebuffer, sizeof(linebuffer), "LOG_GAME_EVENTS     2       %t\n", LogCheckFlag(LOG_GAME_EVENTS) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    Format(linebuffer, sizeof(linebuffer), "LOG_PLAYER_COMMANDS 4       %t\n", LogCheckFlag(LOG_PLAYER_COMMANDS) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    Format(linebuffer, sizeof(linebuffer), "LOG_DEBUG           8       %t\n", LogCheckFlag(LOG_DEBUG) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    Format(linebuffer, sizeof(linebuffer), "LOG_DEBUG_DETAIL    16      %t\n", LogCheckFlag(LOG_DEBUG_DETAIL) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    ReplyToCommand(client, buffer);
    buffer[0] = 0;

    // Module filtering status:
    Format(linebuffer, sizeof(linebuffer), "%t %t\n\n", "Log Module Filtering", GetConVarBool(g_hCvarsList[CVAR_LOG_MODULE_FILTER]) ? "On" : "Off");
    StrCat(buffer, sizeof(buffer), linebuffer);

    Format(linebuffer, sizeof(linebuffer), "%-23s %-19s %t\n", phrasemodule, phraseshortname, "Log Status");
    StrCat(buffer, sizeof(buffer), linebuffer);
    StrCat(buffer, sizeof(buffer), "--------------------------------------------------------------------------------");

    ReplyToCommand(client, buffer);
    buffer[0] = 0;

    // Module status:
    modulecount = sizeof(LogModuleFilterCache);
    for (new module = 1; module < modulecount; module++)
    {
        LogGetModuleNameString(modulename, sizeof(modulename), LogModules:module);
        LogGetModuleNameString(modulenameshort, sizeof(modulenameshort), LogModules:module, true);
        Format(linebuffer, sizeof(linebuffer), "%-23s %-19s %t", modulename, modulenameshort, LogModuleFilterCache[LogModules:module] ? "On" : "Off");
        ReplyToCommand(client, linebuffer);
    }

    return Plugin_Handled;
}

/**
 * Handles the zr_log_add_module command. Add one or modules to module filter.
 *
 * @param client    The client that executed the command.
 * @param argc      Number of arguments passed.
 */
public Action:Command_LogAddModule(client, argc)
{
    decl String:buffer[256];
    decl String:argument[32];
    buffer[0] = 0;

    // Check if privileged.
    if (!ZRIsClientPrivileged(client, OperationType_Configuration))
    {
        TranslationReplyToCommand(client, "No access to command");
        return Plugin_Handled;
    }

    new LogModules:logmodule;

    // Check if no arguments.
    if (argc < 1)
    {
        // Display syntax info.
        StrCat(buffer, sizeof(buffer), "Add one or more modules to the module filter. Usage: zr_log_add_module <module> [module] ...\n");
        StrCat(buffer, sizeof(buffer), "See zr_log_list to list available module names (short names).");
        ReplyToCommand(client, buffer);
    }

    // Loop through each argument.
    for (new arg = 1; arg <= argc; arg++)
    {
        // Get argument string.
        GetCmdArg(arg, argument, sizeof(argument));

        // Convert to module type.
        logmodule = LogGetModule(argument);

        // Check if invalid.
        if (logmodule == LogModule_Invalid)
        {
            ReplyToCommand(client, "Invalid module name: \"%s\"", argument);

            // Skip to next argument.
            continue;
        }

        LogModuleFilterAdd(logmodule);
        ReplyToCommand(client, "Added \"%s\" to module filter.", argument);
    }

    // Update cache.
    LogModuleFilterCacheUpdate();

    return Plugin_Handled;
}

/**
 * Handles the zr_log_add_module command. Remove one or modules to module filter.
 *
 * @param client    The client that executed the command.
 * @param argc      Number of arguments passed.
 */
public Action:Command_LogRemoveModule(client, argc)
{
    decl String:buffer[256];
    decl String:argument[32];
    buffer[0] = 0;

    // Check if privileged.
    if (!ZRIsClientPrivileged(client, OperationType_Configuration))
    {
        TranslationReplyToCommand(client, "No access to command");
        return Plugin_Handled;
    }

    new LogModules:logmodule;

    // Check if no arguments.
    if (argc < 1)
    {
        // Display syntax info.
        StrCat(buffer, sizeof(buffer), "Remove one or more modules to the module filter. Usage: zr_log_remove_module <module> [module] ...\n");
        StrCat(buffer, sizeof(buffer), "See zr_log_list to list available module names (short names).");
        ReplyToCommand(client, buffer);
    }

    // Loop through each argument.
    for (new arg = 1; arg <= argc; arg++)
    {
        // Get argument string.
        GetCmdArg(arg, argument, sizeof(argument));

        // Convert to module type.
        logmodule = LogGetModule(argument);

        // Check if invalid.
        if (logmodule == LogModule_Invalid)
        {
            ReplyToCommand(client, "Invalid module name: \"%s\"", argument);

            // Skip to next argument.
            continue;
        }

        LogModuleFilterRemove(logmodule);
        ReplyToCommand(client, "Removed \"%s\" from module filter.", argument);
    }

    // Update cache.
    LogModuleFilterCacheUpdate();

    return Plugin_Handled;
}
