/*
   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 "tv_local.h"

#include "tv_upstream_parse.h"

#include "tv_upstream.h"
#include "tv_upstream_svcmd.h"
#include "tv_downstream_clcmd.h"

//================
//TV_Upstream_ParseFrame
//================
static void TV_Upstream_ParseFrame( upstream_t *upstream, msg_t *msg )
{
	int length = MSG_ReadShort( msg );

	upstream->serverTime = MSG_ReadLong( msg );

	MSG_SkipData( msg, length - 4 );

	// getting a valid frame message ends the upstream process
	if( upstream->state != CA_ACTIVE )
	{
		upstream->state = CA_ACTIVE;
	}
}

/*
   ==================
   TV_Upstream_ParseServerData
   ==================
 */
static void TV_Upstream_ParseServerData( upstream_t *upstream, msg_t *msg )
{
	int i, numpure;
	purelist_t *new;

	TV_Upstream_ClearState( upstream );

	upstream->state = CA_CONNECTED;

	// parse protocol version number
	i = MSG_ReadLong( msg );

	if( i != APP_PROTOCOL_VERSION )
		TV_Upstream_Error( upstream, "Server returned version %i, not %i", i, APP_PROTOCOL_VERSION );

	upstream->servercount = MSG_ReadLong( msg );
	upstream->snapFrameTime = (unsigned int) MSG_ReadShort( msg );
	/*upstream->baseServerTime = (unsigned int)*/ MSG_ReadLong( msg );

	Q_strncpyz( upstream->basegame, MSG_ReadString( msg ), sizeof( upstream->basegame ) );
	Q_strncpyz( upstream->game, MSG_ReadString( msg ), sizeof( upstream->game ) );

	// parse player entity number
	upstream->playernum = MSG_ReadShort( msg );

	// get the full level name
	Q_strncpyz( upstream->levelname, MSG_ReadString( msg ), sizeof( upstream->levelname ) );

	upstream->sv_bitflags = MSG_ReadByte( msg );
	upstream->reliable = ( ( upstream->sv_bitflags & SV_BITFLAGS_RELIABLE ) ? qtrue : qfalse );

	// pure list

	// clean old, if necessary
	while( upstream->purelist )
	{
		new = upstream->purelist->next;
		Mem_Free( upstream->purelist->filename );
		Mem_Free( upstream->purelist );
		upstream->purelist = new;
	}

	// add new
	numpure = MSG_ReadShort( msg );
	while( numpure > 0 )
	{
		new = Mem_Alloc( upstream->mempool, sizeof( purelist_t ) );
		new->filename = TV_Upstream_CopyString( upstream, MSG_ReadString( msg ) );
		new->checksum = MSG_ReadLong( msg );
		new->next = upstream->purelist;
		upstream->purelist = new;

		numpure--;
	}

	TV_Upstream_AddReliableCommand( upstream, va( "configstrings %i 0", upstream->servercount ) );
}

/*
   ==================
   TV_Upstream_ParseBaseline
   ==================
 */
static void TV_Upstream_ParseBaseline( upstream_t *upstream, msg_t *msg )
{
	unsigned bits;
	int newnum;
	entity_state_t nullstate, tmp;

	memset( &nullstate, 0, sizeof( nullstate ) );
	newnum = CL_ParseEntityBits( msg, &bits );
	CL_ParseDelta( msg, &nullstate, &tmp, newnum, bits );
}

//================
//TV_Upstream_ParseServerMessage
//================
void TV_Upstream_ParseServerMessage( upstream_t *upstream, msg_t *msg )
{
	int cmd;

	assert( upstream && upstream->state >= CA_HANDSHAKE );
	assert( msg );

	// parse the message
	while( upstream->state >= CA_HANDSHAKE )
	{
		if( msg->readcount > msg->cursize )
			TV_Upstream_Error( upstream, "Bad server message" );

		cmd = MSG_ReadByte( msg );

		if( cmd == -1 )
			break;

		// other commands
		switch( cmd )
		{
		default:
			TV_Upstream_Error( upstream, "Illegible server message" );

		case svc_nop:
			break;

		case svc_servercmd:
			if( !upstream->reliable )
			{
				int cmdNum = MSG_ReadLong( msg );
				if( cmdNum < 0 )
					TV_Upstream_Error( upstream, "Invalid cmdNum value" );
				if( cmdNum <= upstream->lastExecutedServerCommand )
				{
					MSG_ReadString( msg ); // read but ignore
					break;
				}
				upstream->lastExecutedServerCommand = cmdNum;
			}
			// fall trough
		case svc_servercs: // configstrings from demo files. they don't have acknowledge
			TV_Upstream_ParseServerCommand( upstream, msg );
			break;

		case svc_serverdata:
			if( upstream->state == CA_HANDSHAKE )
			{
				Cbuf_Execute(); // make sure any stuffed commands are done
				TV_Upstream_ParseServerData( upstream, msg );
			}
			else
			{
				return; // ignore rest of the packet (serverdata is always sent alone)
			}
			break;

		case svc_spawnbaseline:
			TV_Upstream_ParseBaseline( upstream, msg );
			break;

		case svc_download:
			//CL_ParseDownload( msg );
			break;

		case svc_clcack:
			if( upstream->reliable )
				TV_Upstream_Error( upstream, "clack message while reliable" );
			upstream->reliableAcknowledge = (unsigned)MSG_ReadLong( msg );
			MSG_ReadLong( msg ); // ucmdAcknowledged
			break;

		case svc_frame:
			TV_Upstream_ParseFrame( upstream, msg );
			break;

		case svc_sound:
		case svc_playerinfo:
		case svc_packetentities:
		case svc_match:
			TV_Upstream_Error( upstream, "Out of place frame data" );
		}
	}
}
