#include "coduo_engine_structs.h"
#include "../core_cvar/cvar_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "../networking/netchan_private.h"
#include "../networking/sv_snapshot_private.h"
#include "server_private.h"
#include "sv_globals_private.h"

#include <stdint.h>
#include <stdlib.h>
#include <string.h>

enum {
    SV_CVAR_NO_FLAGS = 0,
    SV_PROTOCOL_VERSION = 22,
    SV_FINAL_MESSAGE_PASSES = 2
};

static const char svCvarNamePunkBuster[] = "sv_punkbuster";
static const char svCvarNameGameType[] = "g_gametype";
static const char svCvarDefaultGameType[] = "dm";
static const char svCvarNameAllowJeeps[] = "scr_allow_jeeps";
static const char svCvarNameAllowTanks[] = "scr_allow_tanks";
static const char svCvarDefaultEnabled[] = "1";
static const char svCvarNameKeywords[] = "sv_keywords";
static const char svCvarDefaultEmpty[] = "";
static const char svProtocolFormat[] = "%i";
static const char svCvarNameProtocol[] = "protocol";
static const char svCvarNameMapname[] = "mapname";
static const char svCvarDefaultMapname[] = "nomap";
static const char svCvarNamePrivateClients[] = "sv_privateClients";
static const char svCvarDefaultDisabled[] = "0";
static const char svCvarNameHostname[] = "sv_hostname";
static const char svCvarDefaultHostname[] = "CoDHost";
static const char svCvarNameMaxClients[] = "sv_maxclients";
static const char svCvarDefaultMaxClients[] = "20";
static const char svCvarNameMaxRate[] = "sv_maxRate";
static const char svCvarNameMinPing[] = "sv_minPing";
static const char svCvarNameMaxPing[] = "sv_maxPing";
static const char svCvarNameFloodProtect[] = "sv_floodProtect";
static const char svCvarNameAllowAnonymous[] = "sv_allowAnonymous";
static const char svCvarNameShowCommands[] = "sv_showCommands";
static const char svCvarNameDisableClientConsole[] =
    "sv_disableClientConsole";
static const char svCvarNameCheats[] = "sv_cheats";
static const char svCvarNameServerId[] = "sv_serverid";
static const char svCvarNamePure[] = "sv_pure";
static const char svCvarNamePaks[] = "sv_paks";
static const char svCvarNamePakNames[] = "sv_pakNames";
static const char svCvarNameReferencedPaks[] = "sv_referencedPaks";
static const char svCvarNameReferencedPakNames[] = "sv_referencedPakNames";
static const char svCvarNameRconPassword[] = "rconPassword";
static const char svCvarNamePrivatePassword[] = "sv_privatePassword";
static const char svCvarNameFps[] = "sv_fps";
static const char svCvarDefaultFps[] = "20";
static const char svCvarNameTimeout[] = "sv_timeout";
static const char svCvarDefaultTimeout[] = "240";
static const char svCvarNameZombietime[] = "sv_zombietime";
static const char svCvarDefaultZombietime[] = "2";
static const char svCvarNameNextmap[] = "nextmap";
static const char svCvarNameAllowDownload[] = "sv_allowDownload";
static const char svCvarNameReconnectLimit[] = "sv_reconnectlimit";
static const char svCvarDefaultReconnectLimit[] = "3";
static const char svCvarNameShowLoss[] = "sv_showloss";
static const char svCvarNamePadPackets[] = "sv_padPackets";
static const char svCvarNameKillserver[] = "sv_killserver";
static const char svCvarNameOnlyVisibleClients[] = "sv_onlyVisibleClients";
static const char svCvarNamePacketInfo[] = "sv_packet_info";
static const char svCvarNameShowAverageBps[] = "sv_showAverageBPS";
static const char svCvarNameKickBanTime[] = "sv_kickBanTime";
static const char svCvarDefaultKickBanTime[] = "300";
static const char svCvarNameComplaintLimit[] = "g_complaintlimit";
static const char svCvarNameMapRotation[] = "sv_mapRotation";
static const char svCvarNameMapRotationCurrent[] = "sv_mapRotationCurrent";
static const char svCvarNameFsGame[] = "fs_game";
static const char svCvarNameWwwDownload[] = "sv_wwwDownload";
static const char svCvarNameWwwBaseUrl[] = "sv_wwwBaseURL";
static const char svCvarNameWwwDlDisconnected[] = "sv_wwwDlDisconnected";
static const char svFinalMessageCommand[] = "e \"%s\"";
static const char svFinalWaitCommand[] = "w";
static const char svShutdownBanner[] = "----- Server Shutdown -----\n";
static const char svShutdownEndBanner[] = "---------------------------\n";
static const char svRunningCvarName[] = "sv_running";
static const char svMasterFlatlineHeartbeat[] = "flatline";

void SV_MasterShutdown(void)
{
    svs.nextHeartbeatTime = INT32_MIN;
    SV_MasterHeartbeat(svMasterFlatlineHeartbeat);
}

void SV_SetPunkBusterCvar(const char *value)
{
    Cvar_Set(svCvarNamePunkBuster, value);
}

void SV_Init(void)
{
    const char *protocol;

    SV_AddOperatorCommands();
    host_cvar_g_gametype_pointer_slot =
        Cvar_Get(svCvarNameGameType, svCvarDefaultGameType,
                 CODUO_CVAR_LATCH | CODUO_CVAR_SERVERINFO);
    host_cvar_scr_allow_jeeps_pointer_slot =
        Cvar_Get(svCvarNameAllowJeeps, svCvarDefaultEnabled,
                 CODUO_CVAR_LATCH | CODUO_CVAR_SERVERINFO);
    host_cvar_scr_allow_tanks_pointer_slot =
        Cvar_Get(svCvarNameAllowTanks, svCvarDefaultEnabled,
                 CODUO_CVAR_LATCH | CODUO_CVAR_SERVERINFO);
    Cvar_Get(svCvarNameKeywords, svCvarDefaultEmpty, CODUO_CVAR_SERVERINFO);

    protocol = va(svProtocolFormat, SV_PROTOCOL_VERSION);
    Cvar_Get(svCvarNameProtocol, protocol,
             CODUO_CVAR_ROM | CODUO_CVAR_SERVERINFO);
    Cvar_Set(svCvarNameProtocol, protocol);

    host_cvar_mapname_pointer_slot =
        Cvar_Get(svCvarNameMapname, svCvarDefaultMapname,
                 CODUO_CVAR_ROM | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_privateClients_pointer_slot =
        Cvar_Get(svCvarNamePrivateClients, svCvarDefaultDisabled,
                 CODUO_CVAR_SERVERINFO);
    host_cvar_sv_hostname_pointer_slot =
        Cvar_Get(svCvarNameHostname, svCvarDefaultHostname,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_maxclients_pointer_slot =
        Cvar_Get(svCvarNameMaxClients, svCvarDefaultMaxClients,
                 CODUO_CVAR_LATCH | CODUO_CVAR_SERVERINFO);
    sv_maxclients = host_cvar_sv_maxclients_pointer_slot;
    host_cvar_sv_punkbuster_pointer_slot =
        Cvar_Get(svCvarNamePunkBuster, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_ROM | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_maxRate_pointer_slot =
        Cvar_Get(svCvarNameMaxRate, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SERVERINFO);
    sv_maxRate = host_cvar_sv_maxRate_pointer_slot;
    host_cvar_sv_minPing_pointer_slot =
        Cvar_Get(svCvarNameMinPing, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_maxPing_pointer_slot =
        Cvar_Get(svCvarNameMaxPing, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_floodProtect_pointer_slot =
        Cvar_Get(svCvarNameFloodProtect, svCvarDefaultEnabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_allowAnonymous_pointer_slot =
        Cvar_Get(svCvarNameAllowAnonymous, svCvarDefaultDisabled,
                 CODUO_CVAR_SERVERINFO);
    host_cvar_sv_showCommands_pointer_slot =
        Cvar_Get(svCvarNameShowCommands, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_disableClientConsole_pointer_slot =
        Cvar_Get(svCvarNameDisableClientConsole, svCvarDefaultDisabled,
                 CODUO_CVAR_SYSTEMINFO);
    Cvar_Get(svCvarNameCheats, svCvarDefaultEnabled,
             CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    _host_cvar_sv_serverid_pointer_slot =
        Cvar_Get(svCvarNameServerId, svCvarDefaultDisabled,
                 CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    host_cvar_sv_pure_pointer_slot =
        Cvar_Get(svCvarNamePure, svCvarDefaultEnabled,
                 CODUO_CVAR_SYSTEMINFO | CODUO_CVAR_SERVERINFO);
    Cvar_Get(svCvarNamePaks, svCvarDefaultEmpty,
             CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    Cvar_Get(svCvarNamePakNames, svCvarDefaultEmpty,
             CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    Cvar_Get(svCvarNameReferencedPaks, svCvarDefaultEmpty,
             CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    Cvar_Get(svCvarNameReferencedPakNames, svCvarDefaultEmpty,
             CODUO_CVAR_ROM | CODUO_CVAR_SYSTEMINFO);
    host_cvar_rconPassword_pointer_slot =
        Cvar_Get(svCvarNameRconPassword, svCvarDefaultEmpty,
                 CODUO_CVAR_TEMP);
    host_cvar_sv_privatePassword_pointer_slot =
        Cvar_Get(svCvarNamePrivatePassword, svCvarDefaultEmpty,
                 CODUO_CVAR_TEMP);
    host_cvar_sv_fps_pointer_slot =
        Cvar_Get(svCvarNameFps, svCvarDefaultFps, CODUO_CVAR_TEMP);
    sv_fps = host_cvar_sv_fps_pointer_slot;
    host_cvar_sv_timeout_pointer_slot =
        Cvar_Get(svCvarNameTimeout, svCvarDefaultTimeout, CODUO_CVAR_TEMP);
    host_cvar_sv_zombietime_pointer_slot =
        Cvar_Get(svCvarNameZombietime, svCvarDefaultZombietime,
                 CODUO_CVAR_TEMP);
    host_cvar_sv_cracked_pointer_slot =
        Cvar_Get("sv_cracked", "0", CODUO_CVAR_SERVERINFO);
    Cvar_Get(svCvarNameNextmap, svCvarDefaultEmpty, CODUO_CVAR_TEMP);
    host_cvar_sv_allowDownload_pointer_slot =
        Cvar_Get(svCvarNameAllowDownload, svCvarDefaultEnabled,
                 CODUO_CVAR_ARCHIVE | CODUO_CVAR_SYSTEMINFO);
    host_cvar_sv_reconnectlimit_pointer_slot =
        Cvar_Get(svCvarNameReconnectLimit, svCvarDefaultReconnectLimit,
                 SV_CVAR_NO_FLAGS);
    _host_cvar_sv_showloss_pointer_slot =
        Cvar_Get(svCvarNameShowLoss, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_padPackets_pointer_slot =
        Cvar_Get(svCvarNamePadPackets, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    sv_padPackets = host_cvar_sv_padPackets_pointer_slot;
    host_cvar_sv_killserver_pointer_slot =
        Cvar_Get(svCvarNameKillserver, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_onlyVisibleClients_pointer_slot =
        Cvar_Get(svCvarNameOnlyVisibleClients, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_packet_info_pointer_slot =
        Cvar_Get(svCvarNamePacketInfo, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_showAverageBPS_pointer_slot =
        Cvar_Get(svCvarNameShowAverageBps, svCvarDefaultDisabled,
                 SV_CVAR_NO_FLAGS);
    sv_showAverageBPS = host_cvar_sv_showAverageBPS_pointer_slot;
    host_cvar_sv_kickBanTime_pointer_slot =
        Cvar_Get(svCvarNameKickBanTime, svCvarDefaultKickBanTime,
                 SV_CVAR_NO_FLAGS);
    Cvar_Get(svCvarNameComplaintLimit, svCvarDefaultReconnectLimit,
             CODUO_CVAR_ARCHIVE);
    host_cvar_sv_mapRotation_pointer_slot =
        Cvar_Get(svCvarNameMapRotation, svCvarDefaultEmpty,
                 SV_CVAR_NO_FLAGS);
    host_cvar_sv_mapRotationCurrent_pointer_slot =
        Cvar_Get(svCvarNameMapRotationCurrent, svCvarDefaultEmpty,
                 SV_CVAR_NO_FLAGS);
    Cvar_Get(svCvarNameFsGame, svCvarDefaultEmpty,
             CODUO_CVAR_SYSTEMINFO | CODUO_CVAR_SERVERINFO);
    host_cvar_sv_wwwDownload_pointer_slot =
        Cvar_Get(svCvarNameWwwDownload, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE);
    host_cvar_sv_wwwBaseURL_pointer_slot =
        Cvar_Get(svCvarNameWwwBaseUrl, svCvarDefaultEmpty,
                 CODUO_CVAR_ARCHIVE);
    host_cvar_sv_wwwDlDisconnected_pointer_slot =
        Cvar_Get(svCvarNameWwwDlDisconnected, svCvarDefaultDisabled,
                 CODUO_CVAR_ARCHIVE);
}

void SV_FinalMessage(const char *message)
{
    for (int32_t pass = 0; pass < SV_FINAL_MESSAGE_PASSES; ++pass) {
        client_t *client = svs.clients;

        for (int32_t clientNum = 0;
             clientNum < host_cvar_sv_maxclients_pointer_slot->integer;
             ++clientNum) {
            if (client->state > CODUO_CS_ZOMBIE) {
                if (client->netchan.remoteAddress.type != CODUO_NA_LOOPBACK) {
                    SV_SendServerCommand(client, qfalse, svFinalMessageCommand,
                                         message);
                    SV_SendServerCommand(client, qtrue, svFinalWaitCommand);
                }
                client->nextSnapshotTime = -1;
                SV_SendClientSnapshot(client);
            }
            ++client;
        }
    }
}

void SV_Shutdown(const char *finalMessage)
{
    if (host_cvar_sv_running_pointer_slot == NULL ||
        host_cvar_sv_running_pointer_slot->integer == 0) {
        return;
    }

    Com_Printf(svShutdownBanner);
    if (svs.clients != NULL && com_errorEntered == qfalse) {
        SV_FinalMessage(finalMessage);
    }

    SV_RemoveOperatorCommands();
    SV_MasterShutdown();
    SV_ShutdownGameProgs();
    SV_ClearServer();
    if (svs.clients != NULL) {
        FUN_0808ac2b();
    }
    SV_FreeSnapshotArchive();
    /* Stock clears 0x1510c bytes beginning at 0x084f7000. The native
     * representation splits that span into svs, the challenge table, the
     * rcon and master addresses, the OOB profile pointer, and the authorize
     * GUID cache, so clear every partition in original address order. */
    memset(&svs, 0, sizeof(svs));
    memset(host_svs_challenges, 0, sizeof(host_svs_challenges));
    coduomp_sv_clear_rcon_redirect_address();
    coduomp_sv_clear_master_address();
    svs_oobNetProfile = NULL;
    memset(host_authorizeGuidCache, 0, sizeof(host_authorizeGuidCache));
    Cvar_Set(svRunningCvarName, svCvarDefaultDisabled);
    Com_Printf(svShutdownEndBanner);
    CL_Disconnect(qfalse);
}
