/*
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.

*/
// sv_game.c -- interface to the game dll

#include "server.h"

game_export_t *ge;

/*
===============
PF_Unicast

Sends the contents of the mutlicast buffer to a single client
===============
*/
void PF_Unicast(edict_t *ent, bool reliable) {
  int p;
  client_t *client;

  if(!ent)
    return;

  p = NUM_FOR_EDICT(ent);
  if(p < 1 || p > maxclients->value)
    return;

  client = svs.clients + (p - 1);

  if(reliable)
    SZ_Write(&client->netchan.message, sv.multicast.data, sv.multicast.cursize);
  else
    SZ_Write(&client->datagram, sv.multicast.data, sv.multicast.cursize);

  SZ_Clear(&sv.multicast);
}

/*
===============
PF_dprintf

Debug print to server console
===============
*/
void PF_dprintf(char *fmt, ...) {
  char msg[1024];
  va_list argptr;

  va_start(argptr, fmt);
  vsprintf(msg, sizeof(msg), fmt, argptr);
  va_end(argptr);

  Com_Printf("%s", msg);
}

/*
===============
PF_cprintf

Print to a single client
===============
*/
void PF_cprintf(edict_t *ent, int level, char *fmt, ...) {
  char msg[1024];
  va_list argptr;
  int n;

  if(ent) {
    n = NUM_FOR_EDICT(ent);
    if(n < 1 || n > maxclients->value)
      Com_Error(ERR_DROP, "cprintf to a non-client");
  }

  va_start(argptr, fmt);
  vsprintf(msg, sizeof(msg), fmt, argptr);
  va_end(argptr);

  if(ent)
    SV_ClientPrintf(svs.clients + (n - 1), level, "%s", msg);
  else
    Com_Printf("%s", msg);
}

/*
===============
PF_centerprintf

centerprint to a single client
===============
*/
void PF_centerprintf(edict_t *ent, char *fmt, ...) {
  char msg[1024];
  va_list argptr;
  int n;

  n = NUM_FOR_EDICT(ent);
  if(n < 1 || n > maxclients->value)
    return; // Com_Error (ERR_DROP, "centerprintf to a non-client");

  va_start(argptr, fmt);
  vsprintf(msg, sizeof(msg), fmt, argptr);
  va_end(argptr);

  MSG_WriteByte(&sv.multicast, svc_centerprint);
  MSG_WriteString(&sv.multicast, msg);
  PF_Unicast(ent, true);
}

/*
===============
PF_error

Abort the server with a game error
===============
*/
void PF_error(char *fmt, ...) {
  char msg[1024];
  va_list argptr;

  va_start(argptr, fmt);
  vsprintf(msg, sizeof(msg), fmt, argptr);
  va_end(argptr);

  Com_Error(ERR_DROP, "Game Error: %s", msg);
}

/*
=================
PF_setmodel

Also sets mins and maxs for inline bmodels
=================
*/
void PF_setmodel(edict_t *ent, char *name) {
  int i;
  cmodel_t *mod;

  if(!name)
    Com_Error(ERR_DROP, "PF_setmodel: NULL");

  int name_len = strlen(name);

  // if it is an inline model, get the size information for it
  if(name[0] == '*') {
    ent->s.modelindex = ent->s.cmodel_index + 1;
    ent->s.modelindex2 = atoi(name + 1);

    mod = CM_InlineModel(ent->s.cmodel_index, name);
    VectorCopy(mod->mins, ent->mins);
    VectorCopy(mod->maxs, ent->maxs);
    SV_LinkEdict(ent);
  } else if(strncmp(name + name_len - 4, ".bsp", 4) == 0) {
    unsigned int checksum;

    sv.models[ent->s.cmodel_index][0] = CM_LoadMap(ent->s.cmodel_index, name, false, &checksum);

    sprintf(sv.configstrings[CS_MODELS + ent->s.cmodel_index + 1], MAX_QPATH, "%s;%i", name, checksum);

    for(int k = 1; k < CM_NumInlineModels(ent->s.cmodel_index); k++) {
      char inline_name[6];
      sprintf(inline_name, sizeof(inline_name), "*%i", k);
      sv.models[ent->s.cmodel_index][k] = CM_InlineModel(ent->s.cmodel_index, inline_name);
    }

    ge->SpawnEntities(ent->s.cmodel_index, sv.name, CM_EntityString(ent->s.cmodel_index), NULL);
  } else {
    i = SV_ModelIndex(name);

    //	ent->model = name;
    ent->s.modelindex = i;
  }
}

/*
===============
PF_Configstring

===============
*/
void PF_Configstring(int index, char *val) {
  if(index < 0 || index >= MAX_CONFIGSTRINGS)
    Com_Error(ERR_DROP, "configstring: bad index %i\n", index);

  if(!val)
    val = "";

  // change the string in sv
  strcpy(sv.configstrings[index], val);

  if(sv.state != ss_loading) { // send the update to everyone
    SZ_Clear(&sv.multicast);
    MSG_WriteChar(&sv.multicast, svc_configstring);
    MSG_WriteShort(&sv.multicast, index);
    MSG_WriteString(&sv.multicast, val);

    SV_Multicast(CMODEL_A, vec3_origin, MULTICAST_ALL_R);
  }
}

void PF_WriteChar(int c) { MSG_WriteChar(&sv.multicast, c); }
void PF_WriteByte(int c) { MSG_WriteByte(&sv.multicast, c); }
void PF_WriteShort(int c) { MSG_WriteShort(&sv.multicast, c); }
void PF_WriteLong(int c) { MSG_WriteLong(&sv.multicast, c); }
void PF_WriteFloat(float f) { MSG_WriteFloat(&sv.multicast, f); }
void PF_WriteString(char *s) { MSG_WriteString(&sv.multicast, s); }
void PF_WritePos(vec3_t pos) { MSG_WritePos(&sv.multicast, pos); }
void PF_WriteDir(vec3_t dir) { MSG_WriteDir(&sv.multicast, dir); }
void PF_WriteAngle(float f) { MSG_WriteAngle(&sv.multicast, f); }

/*
=================
PF_inPVS

Also checks portalareas so that doors block sight
=================
*/
bool PF_inPVS(int cmodel_index, vec3_t p1, vec3_t p2) {
  int leafnum;
  int cluster;
  int area1, area2;
  byte *mask;

  leafnum = CM_PointLeafnum(cmodel_index, p1);
  cluster = CM_LeafCluster(cmodel_index, leafnum);
  area1 = CM_LeafArea(cmodel_index, leafnum);
  mask = CM_ClusterPVS(cmodel_index, cluster);

  leafnum = CM_PointLeafnum(cmodel_index, p2);
  cluster = CM_LeafCluster(cmodel_index, leafnum);
  area2 = CM_LeafArea(cmodel_index, leafnum);
  if(mask && (!(mask[cluster >> 3] & (1 << (cluster & 7)))))
    return false;
  if(!CM_AreasConnected(cmodel_index, area1, area2))
    return false; // a door blocks sight
  return true;
}

/*
=================
PF_inPHS

Also checks portalareas so that doors block sound
=================
*/
bool PF_inPHS(int cmodel_index, vec3_t p1, vec3_t p2) {
  int leafnum;
  int cluster;
  int area1, area2;
  byte *mask;

  leafnum = CM_PointLeafnum(cmodel_index, p1);
  cluster = CM_LeafCluster(cmodel_index, leafnum);
  area1 = CM_LeafArea(cmodel_index, leafnum);
  mask = CM_ClusterPHS(cmodel_index, cluster);

  leafnum = CM_PointLeafnum(cmodel_index, p2);
  cluster = CM_LeafCluster(cmodel_index, leafnum);
  area2 = CM_LeafArea(cmodel_index, leafnum);
  if(mask && (!(mask[cluster >> 3] & (1 << (cluster & 7)))))
    return false; // more than one bounce away
  if(!CM_AreasConnected(cmodel_index, area1, area2))
    return false; // a door blocks hearing

  return true;
}

void PF_StartSound(edict_t *entity, int channel, int sound_num, float volume, float attenuation, float timeofs) {
  if(!entity)
    return;
  SV_StartSound(NULL, entity, channel, sound_num, volume, attenuation, timeofs);
}

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

/*
===============
SV_ShutdownGameProgs

Called when either the entire server is being killed, or
it is changing to a different game directory.
===============
*/
void SV_ShutdownGameProgs(void) {
  if(!ge)
    return;
  ge->Shutdown();
  Sys_UnloadGame();
  ge = NULL;
}

static void SV_SetAreaPortalState(int cmodel_index, int portal_num, bool open) {
  CM_SetAreaPortalState(cmodel_index, portal_num, open);
}

static bool SV_AreasConnected(int cmodel_index, int area1, int area2) {
  return CM_AreasConnected(cmodel_index, area1, area2);
}

/*
===============
SV_InitGameProgs

Init the game subsystem for a new map
===============
*/
void SCR_DebugGraph(float value, int color);

void SV_InitGameProgs(void) {
  game_import_t import;

  // unload anything we have now
  if(ge)
    SV_ShutdownGameProgs();

  // load a new game dll
  import.multicast = SV_Multicast;
  import.unicast = PF_Unicast;
  import.bprintf = SV_BroadcastPrintf;
  import.dprintf = PF_dprintf;
  import.client_printf = PF_cprintf;
  import.centerprintf = PF_centerprintf;
  import.error = PF_error;

  import.linkentity = SV_LinkEdict;
  import.unlinkentity = SV_UnlinkEdict;
  import.BoxEdicts = SV_AreaEdicts;
  import.trace = SV_Trace;
  import.pointcontents = SV_PointContents;
  import.setmodel = PF_setmodel;
  import.inPVS = PF_inPVS;
  import.inPHS = PF_inPHS;
  import.Pmove = Pmove;

  import.modelindex = SV_ModelIndex;
  import.soundindex = SV_SoundIndex;
  import.imageindex = SV_ImageIndex;

  import.configstring = PF_Configstring;
  import.sound = PF_StartSound;
  import.positioned_sound = SV_StartSound;

  import.WriteChar = PF_WriteChar;
  import.WriteByte = PF_WriteByte;
  import.WriteShort = PF_WriteShort;
  import.WriteLong = PF_WriteLong;
  import.WriteFloat = PF_WriteFloat;
  import.WriteString = PF_WriteString;
  import.WritePosition = PF_WritePos;
  import.WriteDir = PF_WriteDir;
  import.WriteAngle = PF_WriteAngle;

  import.TagMalloc = Z_TagMalloc;
  import.TagFree = Z_Free;
  import.FreeTags = Z_FreeTags;

  import.cvar = Cvar_Get;
  import.cvar_set = Cvar_Set;
  import.cvar_forceset = Cvar_ForceSet;

  import.argc = Cmd_Argc;
  import.argv = Cmd_Argv;
  import.args = Cmd_Args;
  import.AddCommandString = Cbuf_AddText;

  import.DebugGraph = SCR_DebugGraph;
  import.SetAreaPortalState = SV_SetAreaPortalState;
  import.AreasConnected = SV_AreasConnected;

  ge = (game_export_t *)Sys_GetGameAPI(&import);

  if(!ge)
    Com_Error(ERR_DROP, "failed to load game DLL");
  if(ge->apiversion != GAME_API_VERSION)
    Com_Error(ERR_DROP, "game is version %i, not %i", ge->apiversion, GAME_API_VERSION);

  ge->Init();
}