/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:          zombiereloaded.inc
 *  Type:          Core
 *  Description:   General plugin functions and defines.
 *
 *  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/>.
 *
 * ============================================================================
 */

/**
 * Index of server console.
 */
#define ZR_CONSOLE_INDEX 0

/**
 * @section Conversion factors.
 */
#define CONVERSION_UNITS_TO_FEET    16.000
#define CONVERSION_FEET_TO_UNITS    0.0625
/**
 * @endsection
 */

/**
 * Options that a condition must pass to be eligible.
 */
enum EligibleCondition
{
    Condition_False = 0,    /** Condition must be false. */
    Condition_True = 1,     /** Condition must be true. */
    Condition_Either = -1   /** Condition can be either true or false. */
}

/**
 * Global variable set to true when the first zombie(s) is/are spawned.
 */
new bool:g_bZombieSpawned;

/**
 * Supported games.
 */
enum Game
{
    Game_Unknown = -1,
    Game_CSS,
    Game_CSGO
}

/**
 * Current game.
 */
new Game:g_Game = Game_Unknown;
#pragma unused g_Game

/**
 * Updates g_game. Will log a warning if a unsupported game is detected.
 */
UpdateGameFolder()
{
    new String:gameFolder[PLATFORM_MAX_PATH];
    GetGameFolderName(gameFolder, sizeof(gameFolder));

    if (StrEqual(gameFolder, "cstrike", false))
    {
        g_Game = Game_CSS;
        PrintToServer("Game detected: cstrike");
        return;
    }
    else if (StrEqual(gameFolder, "csgo", false))
    {
        g_Game = Game_CSGO;
        PrintToServer("Game detected: csgo");
        return;
    }

    LogError("Warning: Zombie:Reloaded doesn't support this game: %s", gameFolder);
    g_Game = Game_Unknown;
}

/**
 * Function to convert numbers to defined units.
 *
 * @param number        The number to convert.
 * @param conversion    The conversion factor to multiply by. (See defines above)
 * @return              The converted number.
 */
Float:ZRConvertUnitsFloat(Float:number, Float:conversion)
{
    return number / conversion;
}

/**
 * Create an array populated with eligible clients to be zombie.
 *
 * @param arrayEligibleClients  The handle of the array, don't forget to call CloseHandle
 *                              on it when finished!
 * @param team                  Client is only eligible if on a team.
 * @param alive                 Client is only eligible if alive.
 * @param human                 Client is only eligible if human.
 * @param plugin                True to ignore clients immune from mother infect, false to count them.
 */
stock ZRCreateEligibleClientList(&Handle:arrayEligibleClients, bool:team = false, bool:alive = false, bool:human = false, bool:plugin = false)
{
    new count = 0;
    // Populate list with eligible clients.
    // x = client index.
    for (new x = 1; x <= MaxClients; x++)
    {
        // If client isn't in-game, then stop.
        if (!IsClientInGame(x))
        {
            continue;
        }

        // If client isn't on a team, then stop.
        if (team && !ZRIsClientOnTeam(x))
        {
            continue;
        }

        // If client is dead, then stop.
        if (alive && !IsPlayerAlive(x))
        {
            continue;
        }

        // If client is already zombie (via admin), then stop.
        if (human && !InfectIsClientHuman(x))
        {
            continue;
        }

        // Ask plugin API what they think about our client
        if (plugin && APIOnClientMotherZombieEligible(x) == Plugin_Handled)
        {
            continue;
        }

        // Add eligible client to array.
        if (arrayEligibleClients != INVALID_HANDLE)
            PushArrayCell(arrayEligibleClients, x);

        count++;
    }

    return count;
}

/**
 * Checks if a timer is currently running.
 *
 * @param timer     The timer handle.
 */
stock bool:ZRIsTimerRunning(Handle:timer)
{
    // Return true if the handle isn't empty.
    return (timer != INVALID_HANDLE);
}

/**
 * Wrapper functions for KillTimer.
 * Ends a timer if running, and resets its timer handle variable.
 *
 * @param timer     The timer handle.
 * @param kill      True to kill the timer and reset the variable, false to only reset the variable.
 *                  Using false is useful when calling from the timer callback, because the timer is already killed.
 *
 * @return          True if the handle wasn't INVALID_HANDLE, false if the handle wasn't valid.
 */
stock bool:ZREndTimer(&Handle:timer, bool:kill = true)
{
    // If the timer is running, then kill it.
    if (ZRIsTimerRunning(timer))
    {
        // Kill if caller says to.
        if (kill)
        {
            KillTimer(timer);
        }

        // Reset variable.
        timer = INVALID_HANDLE;

        return true;
    }

    // Reset variable.
    timer = INVALID_HANDLE;

    return false;
}

/**
 * Check if a client index is a valid player.
 *
 * @param client    The client index.
 * @param console   True to include console (index 0), false if not.
 * @return          True if client is valid, false otherwise.
 */
stock bool:ZRIsClientValid(client, bool:console = false)
{
    // If index is greater than max number of clients, then return false.
    if (client > MaxClients)
    {
        return false;
    }

    // If console is true, return if client is >= 0, if not, then return client > 0.
    return console ? (client >= 0) : (client > 0);
}

/**
 * Check if a given index is console.
 *
 * @param client    The client index.
 * @param console   True to include console (index 0), false if not.
 * @return          True if client is valid, false otherwise.
 */
stock bool:ZRIsConsole(index)
{
    // Return true if index is equal to console's index.
    return (index == ZR_CONSOLE_INDEX);
}

/**
 * Count clients on each team.
 *
 * @param zombies   This is set to the number of clients that are zombies.
 * @param humans    This is set to the number of clients that are humans.
 * @param alive     If true it will only count live players, false will count alive and dead.
 * @return          True if successful (zombie has spawned), false otherwise.
 */
stock bool:ZRCountValidClients(&zombiecount = 0, &humancount = 0, bool:alive = true, bool:ignorezombiespawned = false)
{
    // If zombie hasn't spawned and were not only counting humans, then stop.
    if (!InfectHasZombieSpawned() && !ignorezombiespawned)
    {
        return false;
    }

    // x = client index.
    for (new x = 1; x <= MaxClients; x++)
    {
        // If client isn't in-game, then stop.
        if (!IsClientInGame(x))
        {
            continue;
        }

        // If client isn't on a team, then stop.
        if (!ZRIsClientOnTeam(x))
        {
            continue;
        }

        // If player must be alive, and player is dead, then stop.
        if (alive && !IsPlayerAlive(x))
        {
            continue;
        }

        // If player is a zombie, then increment zombie variable.
        if (InfectIsClientInfected(x))
        {
            zombiecount++;
        }
        // If player is a human, then increment human variable.
        else if (InfectIsClientHuman(x))
        {
            humancount++;
        }
    }

    return true;
}

/**
 * Check if a client index is on a team.
 *
 * @param client    The client index.
 * @param team      Team to check if player is on, -1 to check both.
 * @return          True if client is on a team, false otherwise.
 */
stock bool:ZRIsClientOnTeam(client, team = -1)
{
    // If index is invalid, then stop.
    if (!ZRIsClientValid(client) || !IsClientInGame(client))
    {
        return false;
    }

    // Get client team.
    new clientteam = GetClientTeam(client);

    if (team == -1)
    {
        return (clientteam == CS_TEAM_T || clientteam == CS_TEAM_CT);
    }

    return (clientteam == team);
}

/**
 * Check if there are clients on a team.
 *
 * @param team      (Optional) Team to check if there are clients on.
 */
stock bool:ZRTeamHasClients(team = -1)
{
    // If team is
    if (team == -1)
    {
        // Return true if both teams have at least 1 client.
        return (GetTeamClientCount(CS_TEAM_T) && GetTeamClientCount(CS_TEAM_CT));
    }

    // Return true if given team has at least 1 client.
    return bool:GetTeamClientCount(team);
}

/**
 * Returns whether a player is a admin or not.
 *
 * @param client    The client index.
 * @param flag      Optional. Flag to check. Default is generic admin flag.
 * @return          True if generic admin, false otherwise.
 */
stock bool:ZRIsClientAdmin(client, AdminFlag:flag = Admin_Generic)
{
    // If index is invalid, then stop.
    if (!ZRIsClientValid(client))
    {
        return false;
    }

    // If client doesn't have the specified flag, then stop.
    if (!GetAdminFlag(GetUserAdmin(client), flag))
    {
        return false;
    }

    // Client is an admin.
    return true;
}

/**
 * Replies to a client with a given message describing a targetting
 * failure reason. (formatted for ZR)
 *
 * Note: The translation phrases are found in common.phrases.txt.
 *
 * @param client        Client index, or 0 for server.
 * @param reason        COMMAND_TARGET reason.
 */
stock ZRReplyToTargetError(client, reason)
{
    switch (reason)
    {
        case COMMAND_TARGET_NONE:
        {
            TranslationReplyToCommand(client, "No matching client");
        }
        case COMMAND_TARGET_NOT_ALIVE:
        {
            TranslationReplyToCommand(client, "Target must be alive");
        }
        case COMMAND_TARGET_NOT_DEAD:
        {
            TranslationReplyToCommand(client, "Target must be dead");
        }
        case COMMAND_TARGET_NOT_IN_GAME:
        {
            TranslationReplyToCommand(client, "Target is not in game");
        }
        case COMMAND_TARGET_IMMUNE:
        {
            TranslationReplyToCommand(client, "Unable to target");
        }
        case COMMAND_TARGET_EMPTY_FILTER:
        {
            TranslationReplyToCommand(client, "No matching clients");
        }
        case COMMAND_TARGET_NOT_HUMAN:
        {
            TranslationReplyToCommand(client, "Cannot target bot");
        }
        case COMMAND_TARGET_AMBIGUOUS:
        {
            TranslationReplyToCommand(client, "More than one client matched");
        }
    }
}

/**
 * Adds support for printing strings longer than 1 KB to console. Max 4 KB.
 *
 * Note: 1024 characters is max for the console, including newline and null
 *       terminator.
 *
 * @param client        The client index.
 * @param text          Long text to write.
 * @param splitsize     Optional. Sets the split size. 1022 is default.
 *                      Allowed range: 128 to 1022.
 */
stock ZRPrintToConsoleLong(client, const String:text[], splitsize = 1022)
{
    // Validate split size.
    if (splitsize < 128 || splitsize > 1022)
    {
        return;
    }

    decl String:partbuffer[splitsize];
    new pos;
    new cellswritten = 1;           // Initialize for the loop.

    while (cellswritten)
    {
        cellswritten = strcopy(partbuffer, splitsize, text[pos]);
        (client > 0) ? PrintToConsole(client, partbuffer) : PrintToServer(partbuffer);
        pos += cellswritten;
    }
}

/**
 * Converts a boolean value into a string.
 *
 * @param value     The value to convert to string.
 * @param output    The converted string.
 * @param maxlen    The maximum length of the string.
 */
ZRBoolToString(bool:value, String:output[], maxlen)
{
    // If the value is true, then set string to "1".
    if (value)
    {
        strcopy(output, maxlen, "1");
    }
    // If the value is false, then set string to "0".
    else
    {
        strcopy(output, maxlen, "0");
    }
}

/**
 * (from SMLIB 0.10.2)
 *
 * Returns a random, uniform Integer number in the specified (inclusive) range.
 * This is safe to use multiple times in a function.
 * The seed is set automatically for each plugin.
 * Rewritten by MatthiasVance, thanks.
 *
 * @param min           Min value used as lower border
 * @param max           Max value used as upper border
 * @return              Random Integer number between min and max
 */
#define SIZE_OF_INT     2147483647      // without 0
stock Math_GetRandomInt(min, max)
{
    new random = GetURandomInt();

    if (random == 0) {
        random++;
    }

    return RoundToCeil(float(random) / (float(SIZE_OF_INT) / float(max - min + 1))) + min - 1;
}

/**
 * (from SMLIB)
 * Gets the parent entity of an entity.
 *
 * @param entity        Entity Index.
 * @return              Entity Index of the parent.
 */
stock Entity_GetParent(entity)
{
    return GetEntPropEnt(entity, Prop_Data, "m_pParent");
}

/**
 * Returns whether an entity is referred to as the parent entity.
 *
 * @praram entity       Entity index.
 *
 * @return              True if entity has children, false otherwise.
 */
stock bool:Entity_HasChildren(entity)
{
    new maxEntities = GetMaxEntities();

    // Loop through all entity indexes, after players.
    for (new loopEntity = MAXPLAYERS + 1; loopEntity < maxEntities; loopEntity++)
    {
        if (!IsValidEntity(loopEntity))
        {
            continue;
        }

        new parentEntity = Entity_GetParent(loopEntity);
        if (parentEntity == entity)
        {
            return true;
        }
    }

    return false;
}
