/*
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"
#include "mvd.h"

game_export_t	*ge;


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

Sends the contents of the mutlicast buffer to a single client.
Archived in MVD stream.
===============
*/
void PF_Unicast( edict_t *ent, qboolean reliable ) {
	int		p;
	client_t	*client;
	int flags;
	int clientNum;

	if( !ent )
		return;

	p = NUM_FOR_EDICT( ent );
	if( p < 1 || p > sv_maxclients->integer )
		return;

	clientNum = p - 1;
	client = svs.clientpool + clientNum;

#if 0
	// HACK: fixes 'anti-votekick' exploit
	if( msg_write.data[0] == svc_disconnect ) {
		SV_RemoveClient( client );
	}
#endif

	flags = 0;
	if( reliable ) {
		flags |= MSG_RELIABLE;
	}

	clientNum = ( reliable << 7 ) | ( clientNum & 0x7F );

	if( client->protocol == PROTOCOL_VERSION_MVD ) {
		if( msg_write.data[0] == svc_stufftext ) {
			SV_ClientAddMessage( client, flags );
		} else {
            client->AddUnicast( client, clientNum );
			SZ_Clear( &msg_write );
		}
		return;
	}

	SV_ClientAddMessage( client, flags );

    /* dump to demofile */
	MVD_RecUnicast( clientNum );

	/* send to all MVD clients */
    FOR_EACH_CLIENT( client ) {
        if( client->protocol != PROTOCOL_VERSION_MVD ) {
            continue;
        }
		if( client->state < cs_connected ) {
			continue;
		}
        client->AddUnicast( client, clientNum );
	}

    SZ_Clear( &msg_write );

}

/*
=================
PF_bprintf

Sends text to all active clients.
Archived in MVD stream.
=================
*/
void PF_bprintf( int level, const char *fmt, ... ) {
	va_list		argptr;
	char		string[MAX_STRING_CHARS];
	client_t	*client;
	int			i;

	va_start( argptr, fmt );
	Q_vsnprintf( string, sizeof( string ), fmt, argptr );
	va_end( argptr );

	MSG_WriteByte( svc_print );
	MSG_WriteByte( level );
	MSG_WriteString( string );

	// echo to console
	if( dedicated->integer ) {
		// mask off high bits
		for( i = 0; string[i]; i++ )
			string[i] &= 127;
		Com_Printf( "%s", string );
	}

    FOR_EACH_CLIENT( client ) {
		if( client->state != cs_spawned )
			continue;
        if( client->protocol == PROTOCOL_VERSION_MVD ) {
            client->AddMulticast( client, NULL, MULTICAST_ALL_R );
            continue;
        }
		if( level >= client->messagelevel ) {
		    SV_ClientAddMessage( client, MSG_RELIABLE );
        }
	}

	MVD_RecMulticast( vec3_origin, MULTICAST_ALL_R );

	SZ_Clear( &msg_write );
}


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

Debug print to server console.
===============
*/
void PF_dprintf( const char *fmt, ... ) {
	char		msg[MAXPRINTMSG];
	va_list		argptr;
	
	va_start( argptr, fmt );
	Q_vsnprintf( msg, sizeof( msg ), fmt, argptr );
	va_end( argptr );

	Com_Printf( "%s", msg );
}


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

Print to a single client if the level passes.
Archived in MVD stream.
===============
*/
void PF_cprintf( edict_t *ent, int level, const char *fmt, ... ) {
	char		msg[MAX_STRING_CHARS];
	va_list		argptr;
	int			clientNum;
	client_t	*client;

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

	if( !ent ) {
		Com_Printf( "%s", msg );
		return;
	}

	clientNum = NUM_FOR_EDICT( ent ) - 1;
	if( clientNum < 0 || clientNum >= sv_maxclients->integer )
		Com_Error( ERR_DROP, "cprintf to a non-client" );

	client = svs.clientpool + clientNum;

	MSG_WriteByte( svc_print );
	MSG_WriteByte( level );
	MSG_WriteString( msg );

	clientNum = ( 1 << 7 ) | ( clientNum & 0x7F );

	if( client->protocol == PROTOCOL_VERSION_MVD ) {
        client->AddUnicast( client, clientNum );
		SZ_Clear( &msg_write );
		return;
	}

    if( level >= client->messagelevel ) {
    	SV_ClientAddMessage( client, MSG_RELIABLE );
    }

    /* dump to demofile */
	MVD_RecUnicast( clientNum );

	/* send to all MVD clients */
    FOR_EACH_CLIENT( client ) {
        if( client->protocol != PROTOCOL_VERSION_MVD ) {
            continue;
        }
		if( client->state < cs_connected ) {
			continue;
		}
        client->AddUnicast( client, clientNum );
	}

    SZ_Clear( &msg_write );
}


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

Centerprint to a single client.
Archived in MVD stream.
===============
*/
void PF_centerprintf (edict_t *ent, const char *fmt, ...)
{
	char		msg[MAX_STRING_CHARS];
	va_list		argptr;
	int			n;
	
	n = NUM_FOR_EDICT(ent);
	if (n < 1 || n > sv_maxclients->integer)
		return;

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

	MSG_WriteByte (svc_centerprint);
	MSG_WriteString (msg);

	PF_Unicast (ent, qtrue);
}


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

Abort the server with a game error
===============
*/
void PF_error (const char *fmt, ...) __attribute__(( noreturn ));

void PF_error (const char *fmt, ...)
{
	char		msg[MAXPRINTMSG];
	va_list		argptr;
	
	va_start (argptr,fmt);
	Q_vsnprintf( 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, const char *name)
{
	int		i;
	cmodel_t	*mod;

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

	i = SV_ModelIndex (name);
		
	ent->s.modelindex = i;

// if it is an inline model, get the size information for it
	if( name[0] == '*' ) {
		mod = CM_InlineModel (&sv.cm, name);
		VectorCopy (mod->mins, ent->mins);
		VectorCopy (mod->maxs, ent->maxs);
		SV_LinkEdict (ent);
	}

}

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

If game is actively running, broadcasts configstring change.
Archived in MVD stream.
===============
*/
void PF_Configstring( int index, const char *val ) {
	int length, maxlength;
	client_t *client;

	if( index < 0 || index >= MAX_CONFIGSTRINGS )
		Com_Error( ERR_DROP, "PF_Configstring: bad index %i\n", index );

	if( !val )
		val = "";

	length = strlen( val );
	maxlength = sizeof( sv.configstrings ) -
				sizeof( sv.configstrings[0] ) * index - 1;
	if( length > maxlength ) {
		Com_Error( ERR_DROP, "PF_Configstring: oversize configstring "
			"%d: %d > %d\n", index, length, maxlength );
	}

	if( !strcmp( sv.configstrings[index], val ) ) {
		return;
	}

	// change the string in sv
	strcpy( sv.configstrings[index], val );
	
	if( sv.state == ss_loading ) {
		return;
	}

	// dump to demofile
	MVD_RecConfigstring( index, val );

	// send the update to everyone
	MSG_WriteByte( svc_configstring );
	MSG_WriteShort( index );
	MSG_WriteString( val );

    FOR_EACH_CLIENT( client ) {
		if( client->state < cs_connected ) {
			continue;
		}
		SV_ClientAddMessage( client, MSG_RELIABLE );
	}

	SZ_Clear( &msg_write );
}

void PF_WriteFloat( float f ) {
	Com_Error( ERR_DROP, "PF_WriteFloat not implemented" );
}



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

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

	if( !sv.cm.cache ) {
		Com_Error( ERR_DROP, "PF_inPVS: no map loaded" );
	}

	leaf = CM_PointLeaf (&sv.cm, p1);
	cluster = CM_LeafCluster (leaf);
	area1 = CM_LeafArea (leaf);
	mask = CM_ClusterPVS (&sv.cm, cluster);

	leaf = CM_PointLeaf (&sv.cm, p2);
	cluster = CM_LeafCluster (leaf);
	area2 = CM_LeafArea (leaf);
	if ( !Q_IsBitSet( mask, cluster ) )
		return qfalse;
	if (!CM_AreasConnected (&sv.cm, area1, area2))
		return qfalse;		// a door blocks sight
	return qtrue;
}


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

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

	if( !sv.cm.cache ) {
		Com_Error( ERR_DROP, "PF_inPHS: no map loaded" );
	}

	leaf = CM_PointLeaf (&sv.cm, p1);
	cluster = CM_LeafCluster (leaf);
	area1 = CM_LeafArea (leaf);
	mask = CM_ClusterPHS (&sv.cm, cluster);

	leaf = CM_PointLeaf (&sv.cm, p2);
	cluster = CM_LeafCluster (leaf);
	area2 = CM_LeafArea (leaf);
	if( !Q_IsBitSet( mask, cluster ) )
		return qfalse;		// more than one bounce away
	if (!CM_AreasConnected (&sv.cm, area1, area2))
		return qfalse;		// a door blocks hearing

	return qtrue;
}

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);
}

void PF_Pmove( pmove_t *pm ) {
	if( !sv_client ) {
		//Pmove( pm ); // TODO
		return;
	}

	Pmove( pm, &sv_client->pmp );
}

cvar_t *PF_cvar( const char *name, const char *value, int flags ) {
	cvar_t *var;

	if( flags & CVAR_EXTENDED_MASK ) {
		Com_WPrintf( "Game DLL attemped to set extended flags on variable '%s', cleared.\n", name );
		flags &= ~CVAR_EXTENDED_MASK;
	}

	var = Cvar_Get( name, value, flags );
	if( !var->subsystem ) {
		var->subsystem = CVAR_SYSTEM_GAME;
	}

	return var;
}

void PF_AddCommandString( const char *string ) {
	Cbuf_AddTextEx( &cmd_buffer, string );
}

void PF_SetAreaPortalState( int portalnum, qboolean open ) {
	if( !sv.cm.cache ) {
		Com_Error( ERR_DROP, "PF_SetAreaPortalState: no map loaded" );
	}
	CM_SetAreaPortalState( &sv.cm, portalnum, open );
}

qboolean PF_AreasConnected( int area1, int area2 ) {
	if( !sv.cm.cache ) {
		Com_Error( ERR_DROP, "PF_AreasConnected: no map loaded" );
	}
	return CM_AreasConnected( &sv.cm, area1, area2 );
}

void *PF_TagMalloc( int size, memtag_t tag ) {
    void *ptr;

    if( !size ) {
        return NULL;
    }

    ptr = Z_TagMalloc( size, tag );
    memset( ptr, 0, size );

    return ptr;
}

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

/*
===============
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 ();
	if( ge != &mvd_ge ) {
		Sys_FreeGameLibrary();
	}
	ge = NULL;
}

/*
===============
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 = PF_bprintf;
	import.dprintf = PF_dprintf;
	import.cprintf = PF_cprintf;
	import.centerprintf = PF_centerprintf;
	import.error = PF_error;

	import.linkentity = SV_LinkEdict;
	import.unlinkentity = SV_UnlinkEdict;
	import.BoxEdicts = SV_AreaEdicts;
#ifdef _WIN32
#ifdef __GNUC__
	import.trace = ( sv_trace_t )SV_Trace_Old;
#else
	import.trace = SV_Trace;
#endif
#else /* _WIN32 */
	if( sv_oldgame_hack->integer ) {
		import.trace = ( sv_trace_t )SV_Trace_Old;
	} else {
		import.trace = SV_Trace;
	}
#endif /* !_WIN32 */
	import.pointcontents = SV_PointContents;
	import.setmodel = PF_setmodel;
	import.inPVS = PF_inPVS;
	import.inPHS = PF_inPHS;
	import.Pmove = PF_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 = MSG_WriteChar;
	import.WriteByte = MSG_WriteByte;
	import.WriteShort = MSG_WriteShort;
	import.WriteLong = MSG_WriteLong;
	import.WriteFloat = PF_WriteFloat;
	import.WriteString = MSG_WriteString;
	import.WritePosition = MSG_WritePos;
	import.WriteDir = MSG_WriteDir;
	import.WriteAngle = MSG_WriteAngle;

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

	import.cvar = PF_cvar;
	import.cvar_set = Cvar_UserSet;
	import.cvar_forceset = Cvar_Set;

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

	import.DebugGraph = SCR_DebugGraph;
	import.SetAreaPortalState = PF_SetAreaPortalState;
	import.AreasConnected = PF_AreasConnected;

	ge = (game_export_t *)Sys_LoadGameLibrary( &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 ();
}

