/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  Copyright (C) 1996-1997  Id Software, Inc.

  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/>.
*/
#include "pch.h"
#include "quakedef.h"
#include "filesystem/filesystem.h"

/*

A server can always be started, even if the system started out as a client
to a remote system.

A client can NOT be started if the system started as a dedicated server.

Memory is cleared / released when a server or client begins, not when they end.

*/

sound::Service sound_service;

quakeparms_t host_parms;

qboolean    host_initialized;        // true if into command execution

double        host_frametime;
double        host_time;
double        realtime;                // without any filtering or bounding
double        oldrealtime;            // last frame run
int            host_framecount;

int            host_hunklevel;

client_t    *host_client;            // current client

jmp_buf     host_abortserver;

byte        *host_basepal;


/*
================
Host_EndGame
================
*/
void Host_EndGame (char *message, ...)
{
    va_list        argptr;
    char        string[1024];

    va_start (argptr,message);
    vsprintf (string,message,argptr);
    va_end (argptr);
    Con_DPrintf ("Host_EndGame: %s\n",string);

    if (sv.active)
        Host_ShutdownServer (false);

    if (cls.demonum != -1)
        CL_NextDemo ();
    else
        CL_Disconnect ();

    longjmp (host_abortserver, 1);
}

/*
================
Host_Error

This shuts down both the client and server
================
*/
void Host_Error (char *error, ...)
{
    va_list        argptr;
    char        string[1024];
    static    qboolean inerror = false;

    if (inerror)
        Sys_Error ("Host_Error: recursively entered");
    inerror = true;

    SCR_EndLoadingPlaque ();        // reenable screen updates

    va_start (argptr,error);
    vsprintf (string,error,argptr);
    va_end (argptr);
    Con_Printf ("Host_Error: %s\n",string);

    if (sv.active)
        Host_ShutdownServer (false);

    CL_Disconnect ();
    cls.demonum = -1;

    inerror = false;

    longjmp (host_abortserver, 1);
}

/*
================
Host_FindMaxClients
================
*/
void    Host_FindMaxClients (void)
{
    cls.state = ca_disconnected;
    svs.maxclients = 1;
//    svs.maxclients = 8; FIXME maxclients multiplayer

    if (svs.maxclients < 1)
        svs.maxclients = 8;
    else if (svs.maxclients > MAX_SCOREBOARD)
        svs.maxclients = MAX_SCOREBOARD;

    svs.maxclientslimit = svs.maxclients;
    if (svs.maxclientslimit < 4)
        svs.maxclientslimit = 4;
    svs.clients = (client_t *)Hunk_AllocName (svs.maxclientslimit*sizeof(client_t), "clients");

    if (svs.maxclients > 1)
        Cvar_SetValue ("deathmatch", 1.0);
    else
        Cvar_SetValue ("deathmatch", 0.0);
}


/*
=======================
Host_InitLocal
======================
*/
void Host_InitLocal (void)
{
    Host_InitCommands ();

    Cvar_RegisterVariable("host_framerate", "0");

    Cvar_RegisterVariable("fraglimit", "0");
    Cvar_RegisterVariable("timelimit", "0");
    Cvar_RegisterVariable("teamplay", "0");
    Cvar_RegisterVariable("samelevel", "0");
    Cvar_RegisterVariable("noexit", "0");
    Cvar_RegisterVariable("skill", "1");
    Cvar_RegisterVariable("developer", "0");
    Cvar_RegisterVariable("deathmatch", "0");
    Cvar_RegisterVariable("coop", "0");

    Cvar_RegisterVariable("pausable", "1");

    Cvar_RegisterVariable("temp1", "0");

    Host_FindMaxClients();

    host_time = 1.0;        // so a think at time 0 won't get called
}


/*
===============
Host_WriteConfiguration

Writes key bindings and archived cvars to config.cfg
===============
*/
void Host_WriteConfiguration (void)
{
    if (host_initialized)
    {
        File fp;
        if (!fp.OpenWrite("config.cfg"))
        {
            Con_Printf ("Couldn't write config.cfg.\n");
            return;
        }

        Key_WriteBindings (fp);
        Cvar_WriteVariables (fp);

        fp.Close();
    }
}


/*
=================
SV_ClientPrintf

Sends text across to be displayed
FIXME: make this just a stuffed echo?
=================
*/
void SV_ClientPrintf (char *fmt, ...)
{
    va_list        argptr;
    char        string[1024];

    va_start (argptr,fmt);
    vsprintf (string, fmt,argptr);
    va_end (argptr);

    MSG_WriteByte (&host_client->message, svc_print);
    MSG_WriteString (&host_client->message, string);
}

/*
=================
SV_BroadcastPrintf

Sends text to all active clients
=================
*/
void SV_BroadcastPrintf (char *fmt, ...)
{
    va_list        argptr;
    char        string[1024];
    int            i;

    va_start (argptr,fmt);
    vsprintf (string, fmt,argptr);
    va_end (argptr);

    for (i=0 ; i<svs.maxclients ; i++)
        if (svs.clients[i].active && svs.clients[i].spawned)
        {
            MSG_WriteByte (&svs.clients[i].message, svc_print);
            MSG_WriteString (&svs.clients[i].message, string);
        }
}

/*
=================
Host_ClientCommands

Send text over to the client to be executed
=================
*/
void Host_ClientCommands (char *fmt, ...)
{
    va_list        argptr;
    char        string[1024];

    va_start (argptr,fmt);
    vsprintf (string, fmt,argptr);
    va_end (argptr);

    MSG_WriteByte (&host_client->message, svc_stufftext);
    MSG_WriteString (&host_client->message, string);
}

/*
=====================
SV_DropClient

Called when the player is getting totally kicked off the host
if (crash = true), don't bother sending signofs
=====================
*/
void SV_DropClient (qboolean crash)
{
    int        saveSelf;
    int        i;
    client_t *client;

    if (!crash)
    {
        // send any final messages (don't check for errors)
        if (NET_CanSendMessage (host_client->netconnection))
        {
            MSG_WriteByte (&host_client->message, svc_disconnect);
            NET_SendMessage (host_client->netconnection, &host_client->message);
        }

        if (host_client->edict && host_client->spawned)
        {
        // call the prog function for removing a client
        // this will set the body to a dead frame, among other things
            saveSelf = pr_global_struct->self;
            pr_global_struct->self = EDICT_TO_PROG(host_client->edict);
            PR_ExecuteProgram (pr_global_struct->ClientDisconnect);
            pr_global_struct->self = saveSelf;
        }

        Con_DPrintf ("Client %s removed\n",host_client->name);
    }

// break the net connection
    NET_Close (host_client->netconnection);
    host_client->netconnection = NULL;

// free the client (the body stays around)
    host_client->active = false;
    host_client->name[0] = 0;
    host_client->old_frags = -999999;
    net_activeconnections--;

// send notification to all clients
    for (i=0, client = svs.clients ; i<svs.maxclients ; i++, client++)
    {
        if (!client->active)
            continue;
        MSG_WriteByte (&client->message, svc_updatename);
        MSG_WriteByte (&client->message, host_client - svs.clients);
        MSG_WriteString (&client->message, "");
        MSG_WriteByte (&client->message, svc_updatefrags);
        MSG_WriteByte (&client->message, host_client - svs.clients);
        MSG_WriteShort (&client->message, 0);
        MSG_WriteByte (&client->message, svc_updatecolors);
        MSG_WriteByte (&client->message, host_client - svs.clients);
        MSG_WriteByte (&client->message, 0);
    }
}

/*
==================
Host_ShutdownServer

This only happens at the end of a game, not between levels
==================
*/
void Host_ShutdownServer(qboolean crash)
{
    int        i;
    int        count;
    sizebuf_t    buf;
    char        message[4];
    double    start;

    if (!sv.active)
        return;
    
    sv.active = false;
    
// stop all client sounds immediately
    if (cls.state == ca_connected)
        CL_Disconnect ();

// flush any pending messages - like the score!!!
    start = Sys_FloatTime();
    do
    {
        count = 0;
        for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
        {
            if (host_client->active && host_client->message.cursize)
            {
                if (NET_CanSendMessage (host_client->netconnection))
                {
                    NET_SendMessage(host_client->netconnection, &host_client->message);
                    SZ_Clear (&host_client->message);
                }
                else
                {
                    NET_GetMessage(host_client->netconnection);
                    count++;
                }
            }
        }
        if ((Sys_FloatTime() - start) > 3.0)
            break;
    }
    while (count);

// make sure all the clients know we're disconnecting
    buf.data = (byte *)message;
    buf.maxsize = 4;
    buf.cursize = 0;
    MSG_WriteByte(&buf, svc_disconnect);
    count = NET_SendToAll(&buf, 5);
    if (count)
        Con_Printf("Host_ShutdownServer: NET_SendToAll failed for %u clients\n", count);

    for (i=0, host_client = svs.clients ; i<svs.maxclients ; i++, host_client++)
        if (host_client->active)
            SV_DropClient(crash);

//
// clear structures
//
    memset (&sv, 0, sizeof(sv));
    memset (svs.clients, 0, svs.maxclientslimit*sizeof(client_t));
    PR_FreeProgs();
}


/*
================
Host_ClearMemory

This clears all the memory used by both the client and server, but does
not reinitialize anything.
================
*/
void Host_ClearMemory (void)
{
    Con_DPrintf ("Clearing memory\n");
    Mod_ClearAll ();
    if (host_hunklevel)
        Hunk_FreeToLowMark (host_hunklevel);

    cls.signon = 0;
    memset (&sv, 0, sizeof(sv));
    memset (&cl, 0, sizeof(cl));
}


//============================================================================


/*
===================
Host_FilterTime

Returns false if the time is too short to run a frame
===================
*/
qboolean Host_FilterTime (float time)
{
    realtime += time;

    if (!cls.timedemo && realtime - oldrealtime < 1.0/72.0)
        return false;        // framerate is too high

    host_frametime = realtime - oldrealtime;
    oldrealtime = realtime;

    if (Cvar_VariableValue("host_framerate") > 0)
        host_frametime = Cvar_VariableValue("host_framerate");
    else
    {    // don't allow really long or short frames
        if (host_frametime > 0.1)
            host_frametime = 0.1;
        if (host_frametime < 0.001)
            host_frametime = 0.001;
    }

    return true;
}


/*
==================
Host_ServerFrame

==================
*/
void Host_ServerFrame (void)
{
// run the world state
    pr_global_struct->frametime = host_frametime;

// set the time and clear the general datagram
    SV_ClearDatagram ();

// check for new clients
    SV_CheckForNewClients ();

// read client messages
    SV_RunClients ();

// move things around and think
// always pause in single player if in console or menus
    if (!sv.paused && (svs.maxclients > 1 || key_dest == key_game) )
        SV_Physics ();

// send all messages to the clients
    SV_SendClientMessages ();
}

/*
==================
Host_Frame

Runs all active servers
==================
*/
void Host_Frame (float time)
{
    if (setjmp (host_abortserver) )
        return;            // something bad happened, or the server disconnected

// keep the random time dependent
    rand ();

// decide the simulation time
    if (!Host_FilterTime (time))
        return;            // don't run too fast, or packets will flood out

// get new key events
    Sys_SendKeyEvents ();

// process console commands
    Cbuf_Execute ();

// if running the server locally, make intentions now
    if (sv.active)
        CL_SendCmd ();

//-------------------
//
// server operations
//
//-------------------

    if (sv.active)
        Host_ServerFrame ();

//-------------------
//
// client operations
//
//-------------------

// if running the server remotely, send intentions now after
// the incoming messages have been read
    if (!sv.active)
        CL_SendCmd ();

    host_time += host_frametime;

// fetch results from server
    if (cls.state == ca_connected)
        CL_ReadFromServer ();

// update video
    SCR_UpdateScreen ();

    if (cls.signon == SIGNONS)
        CL_DecayLights ();

    host_framecount++;
}

//============================================================================


/*
====================
Host_Init
====================
*/
void Host_Init (quakeparms_t *parms)
{
    host_parms = *parms;

    filesystem::Init(parms->argv0);
    filesystem::Add("./id1");
    Memory_Init (parms->membase, parms->memsize);
    Cbuf_Init ();
    Cmd_Init ();
    V_Init ();
    Chase_Init ();
    COM_Init (parms->basedir);
    Host_InitLocal ();
    W_LoadWadFile ("gfx.wad");
    Key_Init ();
    Con_Init ();
    PR_Init ();
    Mod_Init ();
    NET_Init ();
    SV_Init ();

    File fp;
    if (!fp.OpenRead("gfx/palette.lmp") || !(host_basepal = fp.Read()))
        Sys_Error ("Couldn't load gfx/palette.lmp");
    fp.Close();

    VID_Init (host_basepal);
    SCR_Init ();
    R_Init ();
    Draw_Init ();
    M_Init ();
    sound_service.Initialize();
    Sbar_Init ();
    CL_Init ();
    IN_Init ();

    Cbuf_InsertText ("exec quake.rc\n");

    Hunk_AllocName (0, "-HOST_HUNKLEVEL-");
    host_hunklevel = Hunk_LowMark ();

    host_initialized = true;
}


/*
===============
Host_Shutdown

FIXME: this is a callback from Sys_Quit and Sys_Error.  It would be better
to run quit through here before the final handoff to the sys code.
===============
*/
void Host_Shutdown(void)
{
// keep Con_Printf from trying to update the screen
    scr_disabled_for_loading = true;

    Host_WriteConfiguration();

    NET_Shutdown();
    sound_service.Shutdown();
    IN_Shutdown();
    Draw_Shutdown();
    R_Shutdown();
    VID_Shutdown();
    
    delete[] host_basepal;
    
    W_UnloadWadFile();
    filesystem::Shutdown();
}

