/*
Copyright (C) 1997-2001 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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "server.h"

server_static_t svs; // persistant server info
server_t sv;         // local server

/*
================
SV_FindIndex

================
*/
int SV_FindIndex(char *name, int start, int max, bool create) {
  int i;

  if(!name || !name[0])
    return 0;

  for(i = 1; i < max && sv.configstrings[start + i][0]; i++)
    if(!strcmp(sv.configstrings[start + i], name))
      return i;

  if(!create)
    return 0;

  if(i == max)
    Com_Error(ERR_DROP, "*Index: overflow");

  strncpy(sv.configstrings[start + i], name, sizeof(sv.configstrings[i]));

  if(sv.state != ss_loading) { // send the update to everyone
    SZ_Clear(&sv.multicast);
    MSG_WriteChar(&sv.multicast, svc_configstring);
    MSG_WriteShort(&sv.multicast, start + i);
    MSG_WriteString(&sv.multicast, name);
    SV_Multicast(CMODEL_A, vec3_origin, MULTICAST_ALL_R);
  }

  return i;
}

int SV_ModelIndex(char *name) {
  return SV_FindIndex(name, CS_MODELS + CMODEL_COUNT, MAX_MODELS - CMODEL_COUNT, true) + CMODEL_COUNT;
}

int SV_SoundIndex(char *name) { return SV_FindIndex(name, CS_SOUNDS, MAX_SOUNDS, true); }

int SV_ImageIndex(char *name) { return SV_FindIndex(name, CS_IMAGES, MAX_IMAGES, true); }

/*
================
SV_CreateBaseline

Entity baselines are used to compress the update messages
to the clients -- only the fields that differ from the
baseline will be transmitted
================
*/
void SV_CreateBaseline(void) {
  edict_t *svent;
  int entnum;

  for(entnum = 1; entnum < ge->num_edicts; entnum++) {
    svent = EDICT_NUM(entnum);
    if(!svent->inuse)
      continue;
    if(!svent->s.modelindex && !svent->s.sound && !svent->s.effects)
      continue;
    svent->s.number = entnum;

    //
    // take current state as baseline
    //
    VectorCopy(svent->s.origin, svent->s.old_origin);
    sv.baselines[entnum] = svent->s;
  }
}

/*
=================
SV_CheckForSavegame
=================
*/
void SV_CheckForSavegame(void) {
  char name[MAX_OSPATH];
  FILE *f;
  int i;

  if(sv_noreload->value)
    return;

  if(Cvar_VariableValue("deathmatch"))
    return;

  Com_sprintf(name, sizeof(name), "%s/save/current/%s.sav", FS_Gamedir(), sv.name);
  f = fopen(name, "rb");
  if(!f)
    return; // no savegame

  fclose(f);

  SV_ClearWorld(CMODEL_A);
  SV_ClearWorld(CMODEL_B);
  SV_ClearWorld(CMODEL_C);

  // get configstrings and areaportals
  SV_ReadLevelFile();

  if(!sv.loadgame) { // coming back to a level after being in a different
    // level, so run it for ten seconds

    // rlava2 was sending too many lightstyles, and overflowing the
    // reliable data. temporarily changing the server state to loading
    // prevents these from being passed down.
    server_state_t previousState; // PGM

    previousState = sv.state; // PGM
    sv.state = ss_loading;    // PGM
    for(i = 0; i < 100; i++)
      ge->RunFrame();

    sv.state = previousState; // PGM
  }
}

/*
================
SV_SpawnServer

Change the server to a new map, taking all connected
clients along with it.

================
*/
void SV_SpawnServer(char *server, char *spawnpoint, server_state_t serverstate, bool attractloop, bool loadgame) {
  int i;
  unsigned checksum;

  if(attractloop)
    Cvar_Set("paused", "0");

  Com_Printf("------- Server Initialization -------\n");

  Com_DPrintf("SpawnServer: %s\n", server);
  if(sv.demofile)
    fclose(sv.demofile);

  svs.spawncount++; // any partially connected client will be
                    // restarted
  sv.state = ss_dead;
  Com_SetServerState(sv.state);

  // wipe the entire per-level structure
  memset(&sv, 0, sizeof(sv));
  svs.realtime = 0;
  sv.loadgame = loadgame;
  sv.attractloop = attractloop;

  // save name for levels that don't set message
  strcpy(sv.configstrings[CS_NAME], server);
  if(Cvar_VariableValue("deathmatch")) {
    sprintf(sv.configstrings[CS_AIRACCEL], sizeof(sv.configstrings[CS_AIRACCEL]), "%g", sv_airaccelerate->value);
    pm_airaccelerate = sv_airaccelerate->value;
  } else {
    strcpy(sv.configstrings[CS_AIRACCEL], "0");
    pm_airaccelerate = 0;
  }

  SZ_Init(&sv.multicast, sv.multicast_buf, sizeof(sv.multicast_buf));

  strcpy(sv.name, server);

  // leave slots at start for clients only
  for(i = 0; i < maxclients->value; i++) {
    // needs to reconnect
    if(svs.clients[i].state > cs_connected)
      svs.clients[i].state = cs_connected;
    svs.clients[i].lastframe = -1;
  }

  sv.time = 1000;

  strcpy(sv.name, server);
  strcpy(sv.configstrings[CS_NAME], server);

  if(serverstate != ss_game) {
    sv.models[CMODEL_A][0] = CM_LoadMap(CMODEL_A, "", false, &checksum); // no real map
  } else {
    Com_sprintf(sv.configstrings[CS_MODELS + 1], sizeof(sv.configstrings[CS_MODELS + 1]), "maps/%s.bsp", server);
    sv.models[CMODEL_A][0] = CM_LoadMap(CMODEL_A, sv.configstrings[CS_MODELS + 1], false, &checksum);
  }
  Com_sprintf(sv.configstrings[CS_MODELS + 1], sizeof(sv.configstrings[CS_MODELS + 1]), "maps/%s.bsp;%i", server,
              checksum);

  //
  // clear physics interaction links
  //
  SV_ClearWorld(CMODEL_A);
  SV_ClearWorld(CMODEL_B);
  SV_ClearWorld(CMODEL_C);

  for(i = 1; i < CM_NumInlineModels(CMODEL_A); i++) {
    // Com_sprintf(sv.configstrings[CS_MODELS + 1 + i], sizeof(sv.configstrings[CS_MODELS + 1 + i]), "*%i", i);
    char inline_name[6];
    sprintf(inline_name, sizeof(inline_name), "*%i", i);
    sv.models[CMODEL_A][i] = CM_InlineModel(CMODEL_A, inline_name);
  }

  //
  // spawn the rest of the entities on the map
  //

  // precache and static commands can be issued during
  // map initialization
  sv.state = ss_loading;
  Com_SetServerState(sv.state);

  // load and spawn all other entities
  ge->SpawnEntities(CMODEL_A, sv.name, CM_EntityString(CMODEL_A), spawnpoint);

  // run two frames to allow everything to settle
  ge->RunFrame();
  ge->RunFrame();

  // all precaches are complete
  sv.state = serverstate;
  Com_SetServerState(sv.state);

  // create a baseline for more efficient communications
  SV_CreateBaseline();

  // check for a savegame
  SV_CheckForSavegame();

  // set serverinfo variable
  Cvar_FullSet("mapname", sv.name, CVAR_SERVERINFO | CVAR_NOSET);

  Com_Printf("-------------------------------------\n");
}

/*
==============
SV_InitGame

A brand new game has been started
==============
*/
void SV_InitGame(void) {
  int i;
  edict_t *ent;
  char idmaster[32];

  if(svs.initialized) {
    // cause any connected clients to reconnect
    SV_Shutdown("Server restarted\n", true);
  } else {
    // make sure the client is down
    CL_Drop();
    SCR_BeginLoadingPlaque();
  }

  // get any latched variable changes (maxclients, etc)
  Cvar_GetLatchedVars();

  svs.initialized = true;

  if(Cvar_VariableValue("coop") && Cvar_VariableValue("deathmatch")) {
    Com_Printf("Deathmatch and Coop both set, disabling Coop\n");
    Cvar_FullSet("coop", "0", CVAR_SERVERINFO | CVAR_LATCH);
  }

  // dedicated servers are can't be single player and are usually DM
  // so unless they explicity set coop, force it to deathmatch
  if(dedicated->value) {
    if(!Cvar_VariableValue("coop"))
      Cvar_FullSet("deathmatch", "1", CVAR_SERVERINFO | CVAR_LATCH);
  }

  // init clients
  if(Cvar_VariableValue("deathmatch")) {
    if(maxclients->value <= 1)
      Cvar_FullSet("maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH);
    else if(maxclients->value > MAX_CLIENTS)
      Cvar_FullSet("maxclients", va("%i", MAX_CLIENTS), CVAR_SERVERINFO | CVAR_LATCH);
  } else if(Cvar_VariableValue("coop")) {
    if(maxclients->value <= 1 || maxclients->value > 4)
      Cvar_FullSet("maxclients", "4", CVAR_SERVERINFO | CVAR_LATCH);
#ifdef COPYPROTECT
    if(!sv.attractloop && !dedicated->value)
      Sys_CopyProtect();
#endif
  } else // non-deathmatch, non-coop is one player
  {
    Cvar_FullSet("maxclients", "1", CVAR_SERVERINFO | CVAR_LATCH);
#ifdef COPYPROTECT
    if(!sv.attractloop)
      Sys_CopyProtect();
#endif
  }

  svs.spawncount = rand();
  svs.clients = Z_Malloc(sizeof(client_t) * maxclients->value);
  svs.num_client_entities = maxclients->value * UPDATE_BACKUP * 64;
  svs.client_entities = Z_Malloc(sizeof(entity_state_t) * svs.num_client_entities);

  // init network stuff
  NET_Config((maxclients->value > 1));

  // heartbeats will always be sent to the id master
  svs.last_heartbeat = -99999; // send immediately
  Com_sprintf(idmaster, sizeof(idmaster), "192.246.40.37:%i", PORT_MASTER);
  NET_StringToAdr(idmaster, &master_adr[0]);

  // init game
  SV_InitGameProgs();
  for(i = 0; i < maxclients->value; i++) {
    ent = EDICT_NUM(i + 1);
    ent->s.number = i + 1;
    svs.clients[i].edict = ent;
    memset(&svs.clients[i].lastcmd, 0, sizeof(svs.clients[i].lastcmd));
  }
}

/*
======================
SV_Map

  the full syntax is:

  map [*]<map>$<startspot>+<nextserver>

command from the console or progs.
Map can also be a.cin, .pcx, or .dm2 file
Nextserver is used to allow a cinematic to play, then proceed to
another level:

  map tram.cin+jail_e3
======================
*/
void SV_Map(bool attractloop, char *levelstring, bool loadgame) {
  char level[MAX_QPATH];
  char *ch;
  int l;
  char spawnpoint[MAX_QPATH];

  sv.loadgame = loadgame;
  sv.attractloop = attractloop;

  if(sv.state == ss_dead && !sv.loadgame)
    SV_InitGame(); // the game is just starting

  strcpy(level, levelstring);

  // if there is a + in the map, set nextserver to the remainder
  ch = strstr(level, "+");
  if(ch) {
    *ch = 0;
    Cvar_Set("nextserver", va("gamemap \"%s\"", ch + 1));
  } else
    Cvar_Set("nextserver", "");

  // ZOID special hack for end game screen in coop mode
  if(Cvar_VariableValue("coop") && !Q_stricmp(level, "victory.pcx"))
    Cvar_Set("nextserver", "gamemap \"*base1\"");

  // if there is a $, use the remainder as a spawnpoint
  ch = strstr(level, "$");
  if(ch) {
    *ch = 0;
    strcpy(spawnpoint, ch + 1);
  } else
    spawnpoint[0] = 0;

  // skip the end-of-unit flag if necessary
  if(level[0] == '*')
    strcpy(level, level + 1);

  l = strlen(level);
  if(l > 4 && !strcmp(level + l - 4, ".cin")) {
    SCR_BeginLoadingPlaque(); // for local system
    SV_BroadcastCommand("changing\n");
    SV_SpawnServer(level, spawnpoint, ss_cinematic, attractloop, loadgame);
  } else if(l > 4 && !strcmp(level + l - 4, ".dm2")) {
    SCR_BeginLoadingPlaque(); // for local system
    SV_BroadcastCommand("changing\n");
    SV_SpawnServer(level, spawnpoint, ss_demo, attractloop, loadgame);
  } else if(l > 4 && !strcmp(level + l - 4, ".pcx")) {
    SCR_BeginLoadingPlaque(); // for local system
    SV_BroadcastCommand("changing\n");
    SV_SpawnServer(level, spawnpoint, ss_pic, attractloop, loadgame);
  } else {
    SCR_BeginLoadingPlaque(); // for local system
    SV_BroadcastCommand("changing\n");
    SV_SendClientMessages();
    SV_SpawnServer(level, spawnpoint, ss_game, attractloop, loadgame);
    Cbuf_CopyToDefer();
  }

  SV_BroadcastCommand("reconnect\n");
}