/*
 * ============================================================================
 *
 *  Zombie:Reloaded
 *
 *  File:          roundend.inc
 *  Type:          Core
 *  Description:   Handles round end actions.
 *
 *  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/>.
 *
 * ============================================================================
 */

#if defined REQUIRE_EXTENSIONS
 #define TEMP_REQUIRE_EXTENSIONS
 #undef REQUIRE_EXTENSIONS
#endif

#tryinclude "sourcetvmanager.inc"

/* Restore old REQUIRE_EXTENSIONS value if necessary */
#if defined TEMP_REQUIRE_EXTENSIONS
 #define REQUIRE_EXTENSIONS
 #undef TEMP_REQUIRE_EXTENSIONS
#endif

/**
 * @section All round end reasons.
 */
#define ROUNDEND_TARGET_BOMBED                          0        // Target Successfully Bombed!
#define ROUNDEND_VIP_ESCAPED                            1        // The VIP has escaped!
#define ROUNDEND_VIP_ASSASSINATED                       2        // VIP has been assassinated!
#define ROUNDEND_TERRORISTS_ESCAPED                     3        // The terrorists have escaped!
#define ROUNDEND_CTS_PREVENTESCAPE                      4        // The CT's have prevented most of the terrorists from escaping!
#define ROUNDEND_ESCAPING_TERRORISTS_NEUTRALIZED        5        // Escaping terrorists have all been neutralized!
#define ROUNDEND_BOMB_DEFUSED                           6        // The bomb has been defused!
#define ROUNDEND_CTS_WIN                                7        // Counter-Terrorists Win!
#define ROUNDEND_TERRORISTS_WIN                         8        // Terrorists Win!
#define ROUNDEND_ROUND_DRAW                             9        // Round Draw!
#define ROUNDEND_ALL_HOSTAGES_RESCUED                   10       // All Hostages have been rescued!
#define ROUNDEND_TARGET_SAVED                           11       // Target has been saved!
#define ROUNDEND_HOSTAGES_NOT_RESCUED                   12       // Hostages have not been rescued!
#define ROUNDEND_TERRORISTS_NOT_ESCAPED                 13       // Terrorists have not escaped!
#define ROUNDEND_VIP_NOT_ESCAPED                        14       // VIP has not escaped!
#define ROUNDEND_GAME_COMMENCING                        15       // Game Commencing!
/**
 * @endsection
 */

/**
 * Delay between round ending and new round starting. (Normal)
 */
#define ROUNDEND_DELAY 5.0

/**
 * Possible round end outcomes.
 */
enum RoundEndOutcome
{
    Restart,        /** Round is restarting. */
    Draw,           /** Round has ended in unexpected way. */
    HumansWin,      /** Humans have killed all zombies. */
    ZombiesWin,     /** Zombies have infected all humans. */
}

/**
 * Global variable to store round win timer handle.
 */
new Handle:g_tRoundEnd = INVALID_HANDLE;

new bool:g_SourceTVManagerLoaded = false;

/**
 * All plugins have finished loading.
 */
RoundEndOnAllPluginsLoaded()
{
    #if defined _stvmngr_included
    g_SourceTVManagerLoaded = LibraryExists("sourcetvmanager");
    LogMessage("SourceTV Manager: %s", (g_SourceTVManagerLoaded ? "loaded" : "not loaded"));
    #endif
}

/**
 * A library was added.
 */
RoundEndOnLibraryAdded(const String:name[])
{
    if (StrEqual(name, "sourcetvmanager"))
    {
        // sourcetvmanager loaded.
        g_SourceTVManagerLoaded = true;
    }
}

/**
 * A library was removed.
 */
RoundEndOnLibraryRemoved(const String:name[])
{
    if (StrEqual(name, "sourcetvmanager"))
    {
        // sourcetvmanager unloaded.
        g_SourceTVManagerLoaded = false;
    }
}

/**
 * Map is starting.
 */
RoundEndOnMapStart()
{
    // Reset timer handle.
    g_tRoundEnd = INVALID_HANDLE;
}

/**
 * Client has been killed.
 */
RoundEndOnClientDeath()
{
    // Terminate the round if the last player was killed.
    new RoundEndOutcome:outcome;
    if (RoundEndGetRoundStatus(outcome))
    {
        RoundEndTerminateRound(ROUNDEND_DELAY, outcome);
    }
}

/**
 * Client has been infected.
 */
RoundEndOnClientInfected()
{
    // Terminate the round if the last player was infected.
    new RoundEndOutcome:outcome;
    if (RoundEndGetRoundStatus(outcome))
    {
        RoundEndTerminateRound(ROUNDEND_DELAY, outcome);
    }
}

/**
 * The round is starting.
 */
RoundEndOnRoundStart()
{
    // Stop all overlays.
    RoundEndOverlayStop();

    // If round end timer is running, then kill it.
    if (g_tRoundEnd != INVALID_HANDLE)
    {
        // Kill timer.
        KillTimer(g_tRoundEnd);

        // Reset timer handle.
        g_tRoundEnd = INVALID_HANDLE;
    }
}

/**
 * The freeze time is ending.
 */
RoundEndOnRoundFreezeEnd()
{
    // Calculate round length, in seconds.
    // Get mp_roundtime. (in minutes)
    new Float:roundtime = GetConVarFloat(FindConVar("mp_roundtime"));

    // Convert to seconds.
    roundtime *= 60.0;

    // Subtract one second if running CS: GO to prevent round draw when round
    // ends. For some reason the timing doesn't match the actual round end.
    // Thanks to Jargon.
    if (g_Game == Game_CSGO)
    {
        roundtime--;
    }

    // Start timer.
    g_tRoundEnd = CreateTimer(roundtime, RoundEndTimer, _, TIMER_FLAG_NO_MAPCHANGE);
}

/**
 * The round is ending.
 *
 * @param reason    Reason the round has ended.
 */
RoundEndOnRoundEnd(reason)
{
    // If round end timer is running, then kill it.
    if (g_tRoundEnd != INVALID_HANDLE)
    {
        // Kill timer.
        KillTimer(g_tRoundEnd);

        // Reset timer handle.
        g_tRoundEnd = INVALID_HANDLE;
    }

    // Tell plugin no zombies have been spawned.
    g_bZombieSpawned = false;

    // Get outcome of the round.
    new RoundEndOutcome:outcome = RoundEndReasonToOutcome(reason);

    // Update team scores.
    new teamscore;
    switch(outcome)
    {
        // Zombies won the round.
        case ZombiesWin:
        {
            // Increment T score.
            teamscore = GetTeamScore(CS_TEAM_T);
            SetTeamScore(CS_TEAM_T, ++teamscore);
        }
        // Humans won the round.
        case HumansWin:
        {
            // Increment CT score.
            teamscore = GetTeamScore(CS_TEAM_CT);
            SetTeamScore(CS_TEAM_CT, ++teamscore);
        }
    }

    // Display the overlay to all clients.
    RoundEndOverlayStart(outcome);

    RoundEndDisplayStats();

    // Balance teams if enabled.
    if (GetConVarBool(g_hCvarsList[CVAR_ROUNDEND_BALANCE_TEAMS]))
    {
        RoundEndBalanceTeams();
    }
}

RoundEndDisplayStats()
{
    for(int player = 1; player <= MaxClients; player++)
    {
        if(!IsClientInGame(player) || (IsFakeClient(player) && !IsClientSourceTV(player)))
            continue;

        static char sPlayerID[8];
        static char sPlayerName[MAX_NAME_LENGTH + 2];
        static char sPlayerAuth[24];
        static char sPlayerTeam[8];
        static char sPlayerState[8];

        FormatEx(sPlayerID, sizeof(sPlayerID), "%d", GetClientUserId(player));
        FormatEx(sPlayerName, sizeof(sPlayerName), "\"%N\"", player);

        if(!GetClientAuthId(player, AuthId_Steam2, sPlayerAuth, sizeof(sPlayerAuth)))
            FormatEx(sPlayerAuth, sizeof(sPlayerAuth), "STEAM_ID_PENDING");

        if(IsPlayerAlive(player))
            FormatEx(sPlayerState, sizeof(sPlayerState), "alive");
        else
            FormatEx(sPlayerState, sizeof(sPlayerState), "dead");

        if(InfectIsClientInfected(player))
            FormatEx(sPlayerTeam, sizeof(sPlayerTeam), "zombie");
        else
            FormatEx(sPlayerTeam, sizeof(sPlayerTeam), "human");

        for(int client = 1; client <= MaxClients; client++)
        {
            if(!IsClientInGame(client))
                continue;

            PrintToConsole(client, "# %8s %40s %24s %5s %6s",
                sPlayerID, sPlayerName, sPlayerAuth, sPlayerState, sPlayerTeam);
        }

        #if defined _stvmngr_included
        if(g_SourceTVManagerLoaded)
        {
            SourceTV_PrintToDemoConsole("# %8s %40s %24s %5s %6s",
                sPlayerID, sPlayerName, sPlayerAuth, sPlayerState, sPlayerTeam);
        }
        #endif
    }
}

/**
 * Convert a round_end reason, to a round winner, or draw.
 *
 * @param reason    The round_end reason.
 * @return          The winner of the round. (see enum RoundEndOutcome)
 */
RoundEndOutcome:RoundEndReasonToOutcome(reason)
{
    switch(reason)
    {
        // CTs won the round.
        case ROUNDEND_CTS_WIN:
        {
            return HumansWin;
        }
        // Ts won the round.
        case ROUNDEND_TERRORISTS_WIN:
        {
            return ZombiesWin;
        }
        // Unexpected case.
        default:
        {
            return Draw;
        }
    }
}

/**
 * Timer callback, called when round time reaches 0.
 *
 * @param timer     The timer handle.
 */
public Action:RoundEndTimer(Handle:timer)
{
    // Set the global timer handle variable to INVALID_HANDLE.
    g_tRoundEnd = INVALID_HANDLE;

    // If there aren't clients on both teams, then stop.
    if (!ZRTeamHasClients())
    {
        return;
    }

    new bool:zombies_win = GetConVarBool(g_hCvarsList[CVAR_ROUNDEND_ZOMBIES_WIN]);
    if (zombies_win)
        RoundEndTerminateRound(ROUNDEND_DELAY, ZombiesWin);
    else
        RoundEndTerminateRound(ROUNDEND_DELAY, HumansWin);
}

/**
 * Checks if the round is over.
 *
 * @param outcome   Set to the outcome of the round, if round is over.
 * @return          True if the round is over, false otherwise.
 */
bool:RoundEndGetRoundStatus(&RoundEndOutcome:outcome)
{
    // If zombie hasn't spawned, then stop.
    if (!InfectHasZombieSpawned())
    {
        // Round isn't over.
        return false;
    }

    // Initialize count variables
    new zombiecount;
    new humancount;

    // Count valid clients. (true to only allow living clients)
    ZRCountValidClients(zombiecount, humancount, true);

    // If there are no clients on either teams, then stop.
    if (!zombiecount && !humancount)
    {
        // Round isn't active.
        return false;
    }

    // If there are clients on both teams, then stop.
    if (zombiecount && humancount)
    {
        // Round isn't over.
        return false;
    }

    // We know here, that either zombiecount or humancount is 0. (not both)

    // If there are zombies, then zombies won the round.
    if (zombiecount)
    {
        outcome = ZombiesWin;
    }
    // If there are no zombies, that means there must be humans, they win the round.
    else
    {
        outcome = HumansWin;
    }

    // Round is over.
    return true;
}

/**
 * Ends the round with the given outcome and delay.
 *
 * @param delay     Delay before new round starts.
 * @param outcome   The outcome of the round.
 */
RoundEndTerminateRound(Float:delay, RoundEndOutcome:outcome = Restart)
{
    switch(outcome)
    {
        // Round is restarting.
        case Restart:
        {
            CS_TerminateRound(delay, CSRoundEnd_GameStart, false);
        }
        // Round was a draw.
        case Draw:
        {
            CS_TerminateRound(delay, CSRoundEnd_Draw, false);
        }
        // Zombies won.
        case ZombiesWin:
        {
            CS_TerminateRound(delay, CSRoundEnd_TerroristWin, false);
        }
        // Humans won.
        case HumansWin:
        {
            CS_TerminateRound(delay, CSRoundEnd_CTWin, false);
        }
    }
}

/**
 * Balances teams.
 */
RoundEndBalanceTeams()
{
    // Create eligible player list.
    new Handle:arrayEligibleClients = CreateArray();
    new eligibleclients = ZRCreateEligibleClientList(arrayEligibleClients, true);

    // If there are no eligible client's then stop.
    if (!eligibleclients)
    {
        // Destroy handle.
        CloseHandle(arrayEligibleClients);

        return;
    }

    new client;

    // Move all clients to T

    // x = Array index.
    // client = client index.
    for (new x = 0; x < eligibleclients; x++)
    {
        // Get client stored in array index.
        client = GetArrayCell(arrayEligibleClients, x);

        // Switch client to T
        CS_SwitchTeam(client, CS_TEAM_T);
    }

    // Move every other client back to CT

    // x = array index
    // client = client index.
    for (new x = 0; x < eligibleclients; x += 2)
    {
        // Get client stored in array index.
        client = GetArrayCell(arrayEligibleClients, x);

        // Switch client to CT
        CS_SwitchTeam(client, CS_TEAM_CT);
    }

    // Destroy handle.
    CloseHandle(arrayEligibleClients);
}

/**
 * Displays overlays to clients, depending on the outcome.
 *
 * @param time      Time to display overlays.
 * @param outcome   The outcome of the round.
 */
RoundEndOverlayStart(RoundEndOutcome:outcome)
{
    // If round end overlays are disabled, then stop.
    new bool:overlay = GetConVarBool(g_hCvarsList[CVAR_ROUNDEND_OVERLAY]);
    if (!overlay)
    {
        return;
    }

    decl String:overlaypath[PLATFORM_MAX_PATH];

    switch(outcome)
    {
        // Show "zombies win" overlay.
        case ZombiesWin:
        {
            GetConVarString(g_hCvarsList[CVAR_ROUNDEND_OVERLAY_ZOMBIE], overlaypath, sizeof(overlaypath));
        }
        // Show "humans win" overlay.
        case HumansWin:
        {
            GetConVarString(g_hCvarsList[CVAR_ROUNDEND_OVERLAY_HUMAN], overlaypath, sizeof(overlaypath));
        }
        // Show no overlay.
        default:
        {
            strcopy(overlaypath, sizeof(overlaypath), "");
        }
    }

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

        // If client is fake (or bot), then stop.
        if (IsFakeClient(x))
        {
            continue;
        }

        OverlaysClientSetChannelPath(x, OVERLAYS_CHANNEL_ROUNDEND, overlaypath);
        OverlaysClientSetChannelState(x, OVERLAYS_CHANNEL_ROUNDEND, true, false, true);
    }
}

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

        // If client is fake (or bot), then stop.
        if (IsFakeClient(x))
        {
            continue;
        }

        // Disable roundend overlay channel.
        OverlaysClientSetChannelState(x, OVERLAYS_CHANNEL_ROUNDEND, true, false, false, true);
    }
}