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

/*
 *
 * ============================================================================
 * =
 *
 * Encode a client frame onto the network channel
 *
 * =============================================================================
 */

/*
 * ============= SV_EmitPacketEntities
 *
 * Writes a delta update of an entity_state_t list to the message. =============
 */
void
SV_EmitPacketEntities(client_frame_t * from, client_frame_t * to, sizebuf_t * msg)
{
	entity_state_t *oldent, *newent;
	int		oldindex, newindex;
	int		oldnum, newnum;
	int		from_num_entities;
	int		bits;

	MSG_WriteByte(msg, svc_packetentities);

	if (!from)
		from_num_entities = 0;
	else
		from_num_entities = from->num_entities;

	newindex = 0;
	oldindex = 0;
	newent = NULL;
	oldent = NULL;
	while (newindex < to->num_entities || oldindex < from_num_entities) {
		if (newindex >= to->num_entities)
			newnum = 9999;
		else {
			newent = &svs.client_entities[(to->first_entity + newindex) % svs.num_client_entities];
			newnum = newent->number;
		}

		if (oldindex >= from_num_entities)
			oldnum = 9999;
		else {
			oldent = &svs.client_entities[(from->first_entity + oldindex) % svs.num_client_entities];
			oldnum = oldent->number;
		}

		if (newnum == oldnum) {	/* delta update from old position */

			/*
			 * because the force parm is false, this will not
			 * result
			 */

			/*
			 * in any bytes being emited if the entity has not
			 * changed at all
			 */

			/*
			 * note that players are always 'newentities', this
			 * updates their oldorigin always
			 */
			/* and prevents warping */
			MSG_WriteDeltaEntity(oldent, newent, msg, false, newent->number <= maxclients->value);
			oldindex++;
			newindex++;
			continue;
		}
		if (newnum < oldnum) {	/* this is a new entity, send it from
					 * the baseline */
			MSG_WriteDeltaEntity(&sv.baselines[newnum], newent, msg, true, true);
			newindex++;
			continue;
		}
		if (newnum > oldnum) {	/* the old entity isn't present in
					 * the new message */
			bits = U_REMOVE;
			if (oldnum >= 256)
				bits |= U_NUMBER16 | U_MOREBITS1;

			MSG_WriteByte(msg, bits & 255);
			if (bits & 0x0000ff00)
				MSG_WriteByte(msg, (bits >> 8) & 255);

			if (bits & U_NUMBER16)
				MSG_WriteShort(msg, oldnum);
			else
				MSG_WriteByte(msg, oldnum);

			oldindex++;
			continue;
		}
	}

	MSG_WriteShort(msg, 0);	/* end of packetentities */

}



/*
 * ============= SV_WritePlayerstateToClient
 *
 * =============
 */
void
SV_WritePlayerstateToClient(client_frame_t * from, client_frame_t * to, sizebuf_t * msg)
{
	int		i;
	int		pflags;
	player_state_t *ps, *ops;
	player_state_t	dummy;
	int		statbits;

	ps = &to->ps;
	if (!from) {
		memset(&dummy, 0, sizeof(dummy));
		ops = &dummy;
	} else
		ops = &from->ps;

	//
	/* determine what needs to be sent */
	    //
	    pflags = 0;

	if (ps->pmove.pm_type != ops->pmove.pm_type)
		pflags |= PS_M_TYPE;

	if (ps->pmove.origin[0] != ops->pmove.origin[0]
	    || ps->pmove.origin[1] != ops->pmove.origin[1]
	    || ps->pmove.origin[2] != ops->pmove.origin[2])
		pflags |= PS_M_ORIGIN;

	if (ps->pmove.velocity[0] != ops->pmove.velocity[0]
	    || ps->pmove.velocity[1] != ops->pmove.velocity[1]
	    || ps->pmove.velocity[2] != ops->pmove.velocity[2])
		pflags |= PS_M_VELOCITY;

	if (ps->pmove.pm_time != ops->pmove.pm_time)
		pflags |= PS_M_TIME;

	if (ps->pmove.pm_flags != ops->pmove.pm_flags)
		pflags |= PS_M_FLAGS;

	if (ps->pmove.gravity != ops->pmove.gravity)
		pflags |= PS_M_GRAVITY;

	if (ps->pmove.delta_angles[0] != ops->pmove.delta_angles[0]
	    || ps->pmove.delta_angles[1] != ops->pmove.delta_angles[1]
	    || ps->pmove.delta_angles[2] != ops->pmove.delta_angles[2])
		pflags |= PS_M_DELTA_ANGLES;


	if (ps->viewoffset[0] != ops->viewoffset[0]
	    || ps->viewoffset[1] != ops->viewoffset[1]
	    || ps->viewoffset[2] != ops->viewoffset[2])
		pflags |= PS_VIEWOFFSET;

	if (ps->viewangles[0] != ops->viewangles[0]
	    || ps->viewangles[1] != ops->viewangles[1]
	    || ps->viewangles[2] != ops->viewangles[2])
		pflags |= PS_VIEWANGLES;

	if (ps->kick_angles[0] != ops->kick_angles[0]
	    || ps->kick_angles[1] != ops->kick_angles[1]
	    || ps->kick_angles[2] != ops->kick_angles[2])
		pflags |= PS_KICKANGLES;

	if (ps->blend[0] != ops->blend[0]
	    || ps->blend[1] != ops->blend[1]
	    || ps->blend[2] != ops->blend[2]
	    || ps->blend[3] != ops->blend[3])
		pflags |= PS_BLEND;

	if (ps->fov != ops->fov)
		pflags |= PS_FOV;

	if (ps->rdflags != ops->rdflags)
		pflags |= PS_RDFLAGS;

	if (ps->gunframe != ops->gunframe)
		pflags |= PS_WEAPONFRAME;

	pflags |= PS_WEAPONINDEX;

	/* write it */
	MSG_WriteByte(msg, svc_playerinfo);
	MSG_WriteShort(msg, pflags);

	/* write the pmove_state_t */
	if (pflags & PS_M_TYPE)
		MSG_WriteByte(msg, ps->pmove.pm_type);

	if (pflags & PS_M_ORIGIN) {
		MSG_WriteShort(msg, ps->pmove.origin[0]);
		MSG_WriteShort(msg, ps->pmove.origin[1]);
		MSG_WriteShort(msg, ps->pmove.origin[2]);
	}
	if (pflags & PS_M_VELOCITY) {
		MSG_WriteShort(msg, ps->pmove.velocity[0]);
		MSG_WriteShort(msg, ps->pmove.velocity[1]);
		MSG_WriteShort(msg, ps->pmove.velocity[2]);
	}
	if (pflags & PS_M_TIME)
		MSG_WriteByte(msg, ps->pmove.pm_time);

	if (pflags & PS_M_FLAGS)
		MSG_WriteByte(msg, ps->pmove.pm_flags);

	if (pflags & PS_M_GRAVITY)
		MSG_WriteShort(msg, ps->pmove.gravity);

	if (pflags & PS_M_DELTA_ANGLES) {
		MSG_WriteShort(msg, ps->pmove.delta_angles[0]);
		MSG_WriteShort(msg, ps->pmove.delta_angles[1]);
		MSG_WriteShort(msg, ps->pmove.delta_angles[2]);
	}
	
	/* write the rest of the player_state_t */
	if (pflags & PS_VIEWOFFSET) {
		MSG_WriteChar(msg, ps->viewoffset[0] * 4);
		MSG_WriteChar(msg, ps->viewoffset[1] * 4);
		MSG_WriteChar(msg, ps->viewoffset[2] * 4);
	}
	if (pflags & PS_VIEWANGLES) {
		MSG_WriteAngle16(msg, ps->viewangles[0]);
		MSG_WriteAngle16(msg, ps->viewangles[1]);
		MSG_WriteAngle16(msg, ps->viewangles[2]);
	}
	if (pflags & PS_KICKANGLES) {
		MSG_WriteChar(msg, ps->kick_angles[0] * 4);
		MSG_WriteChar(msg, ps->kick_angles[1] * 4);
		MSG_WriteChar(msg, ps->kick_angles[2] * 4);
	}
	if (pflags & PS_WEAPONINDEX) {
		MSG_WriteByte(msg, ps->gunindex);
	}
	if (pflags & PS_WEAPONFRAME) {
		MSG_WriteByte(msg, ps->gunframe);
		MSG_WriteChar(msg, ps->gunoffset[0] * 4);
		MSG_WriteChar(msg, ps->gunoffset[1] * 4);
		MSG_WriteChar(msg, ps->gunoffset[2] * 4);
		MSG_WriteChar(msg, ps->gunangles[0] * 4);
		MSG_WriteChar(msg, ps->gunangles[1] * 4);
		MSG_WriteChar(msg, ps->gunangles[2] * 4);
	}
	if (pflags & PS_BLEND) {
		MSG_WriteByte(msg, ps->blend[0] * 255);
		MSG_WriteByte(msg, ps->blend[1] * 255);
		MSG_WriteByte(msg, ps->blend[2] * 255);
		MSG_WriteByte(msg, ps->blend[3] * 255);
	}
	if (pflags & PS_FOV)
		MSG_WriteByte(msg, ps->fov);
	if (pflags & PS_RDFLAGS)
		MSG_WriteByte(msg, ps->rdflags);

	/* send stats */
	statbits = 0;
	for (i = 0; i < MAX_STATS; i++)
		if (ps->stats[i] != ops->stats[i])
			statbits |= 1 << i;
	MSG_WriteLong(msg, statbits);
	for (i = 0; i < MAX_STATS; i++)
		if (statbits & (1 << i))
			MSG_WriteShort(msg, ps->stats[i]);
}


/*
 * ================== SV_WriteFrameToClient ==================
 */
void
SV_WriteFrameToClient(client_t * client, sizebuf_t * msg)
{
	client_frame_t *frame, *oldframe;
	int		lastframe;

	/* Com_Printf ("%i -> %i\n", client->lastframe, sv.framenum); */
	/* this is the frame we are creating */
	frame = &client->frames[sv.framenum & UPDATE_MASK];

	if (client->lastframe <= 0) {	/* client is asking for a retransmit */
		oldframe = NULL;
		lastframe = -1;
	} else if (sv.framenum - client->lastframe >= (UPDATE_BACKUP - 3)) {	/* client hasn't gotten
										 * a good message
										 * through in a long
										 * time */

		/*
		 * Com_Printf ("%s: Delta request from out-of-date
		 * packet.\n", client->name);
		 */
		oldframe = NULL;
		lastframe = -1;
	} else {		/* we have a valid message to delta from */
		oldframe = &client->frames[client->lastframe & UPDATE_MASK];
		lastframe = client->lastframe;
	}

	MSG_WriteByte(msg, svc_frame);
	MSG_WriteLong(msg, sv.framenum);
	MSG_WriteLong(msg, lastframe);	/* what we are delta'ing from */
	MSG_WriteByte(msg, client->surpressCount);	/* rate dropped packets */
	client->surpressCount = 0;

	/* send over the areabits */
	MSG_WriteByte(msg, frame->areabytes);
	SZ_Write(msg, frame->areabits, frame->areabytes);

	/* delta encode the playerstate */
	SV_WritePlayerstateToClient(oldframe, frame, msg);

	/* delta encode the entities */
	SV_EmitPacketEntities(oldframe, frame, msg);
}


/*
 *
 * ============================================================================
 * =
 *
 * Build a client frame structure
 *
 * =============================================================================
 */

byte		fatpvs[65536 / 8];	/* 32767 is MAX_MAP_LEAFS */

/*
 * ============ SV_FatPVS
 *
 * The client will interpolate the view position, so we can't use a single PVS
 * point ===========
 */
void
SV_FatPVS(vec3_t org)
{
	int		leafs[64];
	int		i, j, count;
	int		longs;
	byte           *src;
	vec3_t		mins, maxs;

	for (i = 0; i < 3; i++) {
		mins[i] = org[i] - 8;
		maxs[i] = org[i] + 8;
	}

	count = CM_BoxLeafnums(mins, maxs, leafs, 64, NULL);
	if (count < 1)
		Com_Error(ERR_FATAL, "SV_FatPVS: count < 1");
	longs = (CM_NumClusters() + 31) >> 5;

	/* convert leafs to clusters */
	for (i = 0; i < count; i++)
		leafs[i] = CM_LeafCluster(leafs[i]);

	memcpy(fatpvs, CM_ClusterPVS(leafs[0]), longs << 2);
	/* or in all the other leaf bits */
	for (i = 1; i < count; i++) {
		for (j = 0; j < i; j++)
			if (leafs[i] == leafs[j])
				break;
		if (j != i)
			continue;	/* already have the cluster we want */
		src = CM_ClusterPVS(leafs[i]);
		for (j = 0; j < longs; j++)
			((long *)fatpvs)[j] |= ((long *)src)[j];
	}
}


/*
 * ============= SV_BuildClientFrame
 *
 * Decides which entities are going to be visible to the client, and copies off
 * the playerstat and areabits. =============
 */
void
SV_BuildClientFrame(client_t * client)
{
	int		e, i;
	vec3_t		org;
	edict_t        *ent;
	edict_t        *clent;
	client_frame_t *frame;
	entity_state_t *state;
	int		l;
	int		clientarea, clientcluster;
	int		leafnum;
	int		c_fullsend;
	byte           *clientphs;
	byte           *bitvector;

	clent = client->edict;
	if (!clent->client)
		return;		/* not in game yet */

	/* this is the frame we are creating */
	frame = &client->frames[sv.framenum & UPDATE_MASK];

	frame->senttime = svs.realtime;	/* save it for ping calc later */

	/* find the client's PVS */
	for (i = 0; i < 3; i++)
		org[i] = clent->client->ps.pmove.origin[i] * 0.125 + clent->client->ps.viewoffset[i];

	leafnum = CM_PointLeafnum(org);
	clientarea = CM_LeafArea(leafnum);
	clientcluster = CM_LeafCluster(leafnum);

	/* calculate the visible areas */
	frame->areabytes = CM_WriteAreaBits(frame->areabits, clientarea);

	/* grab the current player_state_t */
	frame->ps = clent->client->ps;


	SV_FatPVS(org);
	clientphs = CM_ClusterPHS(clientcluster);

	/* build up the list of visible entities */
	frame->num_entities = 0;
	frame->first_entity = svs.next_client_entities;

	c_fullsend = 0;

	for (e = 1; e < ge->num_edicts; e++) {
		ent = EDICT_NUM(e);

		/* ignore ents without visible models */
		if (ent->svflags & SVF_NOCLIENT)
			continue;

		/*
		 * ignore ents without visible models unless they have an
		 * effect
		 */
		if (!ent->s.modelindex && !ent->s.effects && !ent->s.sound
		    && !ent->s.event)
			continue;

		/* ignore if not touching a PV leaf */
		if (ent != clent) {
			/* check area */
			if (!CM_AreasConnected(clientarea, ent->areanum)) {	/* doors can legally
										 * straddle two areas,
										 * so */
				/* we may need to check another one */
				if (!ent->areanum2
				    || !CM_AreasConnected(clientarea, ent->areanum2))
					continue;	/* blocked by a door */
			}
			/* beams just check one point for PHS */
			if (ent->s.renderfx & RF_BEAM) {
				l = ent->clusternums[0];
				if (!(clientphs[l >> 3] & (1 << (l & 7))))
					continue;
			} else {

				/*
				 * FIXME: if an ent has a model and a sound,
				 * but isn't
				 */
				/* in the PVS, only the PHS, clear the model */
				if (ent->s.sound) {
					bitvector = fatpvs;	/* clientphs; */
				} else
					bitvector = fatpvs;

				if (ent->num_clusters == -1) {	/* too many leafs for
								 * individual check, go
								 * by headnode */
					if (!CM_HeadnodeVisible(ent->headnode, bitvector))
						continue;
					c_fullsend++;
				} else {	/* check individual leafs */
					for (i = 0; i < ent->num_clusters; i++) {
						l = ent->clusternums[i];
						if (bitvector[l >> 3] & (1 << (l & 7)))
							break;
					}
					if (i == ent->num_clusters)
						continue;	/* not visible */
				}

				if (!ent->s.modelindex) {	/* don't send sounds if
								 * they will be
								 * attenuated away */
					vec3_t		delta;
					float		len;

					VectorSubtract(org, ent->s.origin, delta);
					len = VectorLength(delta);
					if (len > 400)
						continue;
				}
			}
		}

		/* add it to the circular client_entities array */
		state = &svs.client_entities[svs.next_client_entities % svs.num_client_entities];
		if (ent->s.number != e) {
			Com_DPrintf("FIXING ENT->S.NUMBER!!!\n");
			ent->s.number = e;
		}
		*state = ent->s;

		/* don't mark players missiles as solid */
		if (ent->owner == client->edict)
			state->solid = 0;

		svs.next_client_entities++;
		frame->num_entities++;
	}
}


/*
 * ================== SV_RecordDemoMessage
 *
 * Save everything in the world out without deltas. Used for recording footage
 * for merged or assembled demos ==================
 */
void
SV_RecordDemoMessage(void)
{
	int		e;
	edict_t        *ent;
	entity_state_t	nostate;
	sizebuf_t	buf;
	byte		buf_data[32768];
	int		len;

	if (!svs.demofile)
		return;

	memset(&nostate, 0, sizeof(nostate));
	SZ_Init(&buf, buf_data, sizeof(buf_data));

	/* write a frame message that doesn't contain a player_state_t */
	MSG_WriteByte(&buf, svc_frame);
	MSG_WriteLong(&buf, sv.framenum);

	MSG_WriteByte(&buf, svc_packetentities);

	e = 1;
	ent = EDICT_NUM(e);
	while (e < ge->num_edicts) {

		/*
		 * ignore ents without visible models unless they have an
		 * effect
		 */
		if (ent->inuse &&
		    ent->s.number &&
		    (ent->s.modelindex || ent->s.effects || ent->s.sound || ent->s.event) &&
		    !(ent->svflags & SVF_NOCLIENT))
			MSG_WriteDeltaEntity(&nostate, &ent->s, &buf, false, true);

		e++;
		ent = EDICT_NUM(e);
	}

	MSG_WriteShort(&buf, 0);/* end of packetentities */

	/* now add the accumulated multicast information */
	SZ_Write(&buf, svs.demo_multicast.data, svs.demo_multicast.cursize);
	SZ_Clear(&svs.demo_multicast);

	/* now write the entire message to the file, prefixed by the length */
	len = LittleLong(buf.cursize);
	fwrite(&len, 4, 1, svs.demofile);
	fwrite(buf.data, buf.cursize, 1, svs.demofile);
}
