/*
 * 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_main.c -- server main program */

#include "server.h"

/*
 *
 * ============================================================================
 * =
 *
 * Com_Printf redirection
 *
 * =============================================================================
 */

char		sv_outputbuf[SV_OUTPUTBUF_LENGTH];

void
SV_FlushRedirect(int sv_redirected, char *outputbuf)
{
	if (sv_redirected == RD_PACKET) {
		Netchan_OutOfBandPrint(NS_SERVER, net_from, "print\n%s", outputbuf);
	} else if (sv_redirected == RD_CLIENT) {
		MSG_WriteByte(&sv_client->netchan.message, svc_print);
		MSG_WriteByte(&sv_client->netchan.message, PRINT_HIGH);
		MSG_WriteString(&sv_client->netchan.message, outputbuf);
	}
}


/*
 *
 * ===========================================================================
 *
 * EVENT MESSAGES
 *
 * ===========================================================================
 */

/*
 * =================
 * SV_ClientPrintf
 *
 * Sends text across to be displayed if the level passes.
 * =================
 */
void
SV_ClientPrintf(client_t * cl, int level, char *fmt,...)
{
	va_list		argptr;
	char		string[1024];

	if (level < cl->messagelevel)
		return;

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

	MSG_WriteByte(&cl->netchan.message, svc_print);
	MSG_WriteByte(&cl->netchan.message, level);
	MSG_WriteString(&cl->netchan.message, string);
}

/*
 * =================
 * SV_BroadcastPrintf
 *
 * Sends text to all active clients.
 * =================
 */
void
SV_BroadcastPrintf(int level, char *fmt,...)
{
	va_list		argptr;
	char		string[2048];
	client_t       *cl;
	int		i;

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

	/* echo to console */
	if (dedicated->value) {
		char		copy[1024];

		/* mask off high bits */
		for (i = 0; i < 1023 && string[i]; i++)
			copy[i] = string[i] & 127;
		copy[i] = 0;
		Com_Printf("%s", copy);
	}
	for (i = 0, cl = svs.clients; i < maxclients->value; i++, cl++) {
		if (level < cl->messagelevel)
			continue;
		if (cl->state != cs_spawned)
			continue;
		MSG_WriteByte(&cl->netchan.message, svc_print);
		MSG_WriteByte(&cl->netchan.message, level);
		MSG_WriteString(&cl->netchan.message, string);
	}
}

/*
 * =================
 * SV_BroadcastCommand
 *
 * Sends text to all active clients.
 * =================
 */
void
SV_BroadcastCommand(char *fmt,...)
{
	va_list		argptr;
	char		string[1024];

	if (!sv.state)
		return;
	va_start(argptr, fmt);
	vsnprintf(string, sizeof(string), fmt, argptr);
	va_end(argptr);

	MSG_WriteByte(&sv.multicast, svc_stufftext);
	MSG_WriteString(&sv.multicast, string);
	SV_Multicast(NULL, MULTICAST_ALL_R);
}


/*
 * =================
 * SV_Multicast
 *
 * Sends the contents of sv.multicast to a subset of the clients, then clears
 * sv.multicast.
 *
 * MULTICAST_ALL	same as broadcast (origin can be NULL).
 * MULTICAST_PVS	send to clients potentially visible from org.
 * MULTICAST_PHS	send toclients potentially hearable from org.
 * =================
 */
void
SV_Multicast(vec3_t origin, multicast_t to)
{
	client_t       *client;
	byte           *mask;
	int		leafnum, cluster;
	int		j;
	qboolean	reliable;
	int		area1, area2;

	reliable = false;

	if (to != MULTICAST_ALL_R && to != MULTICAST_ALL) {
		leafnum = CM_PointLeafnum(origin);
		area1 = CM_LeafArea(leafnum);
	} else {
		leafnum = 0;	/* just to avoid compiler warnings */
		area1 = 0;
	}

	/* if doing a serverrecord, store everything */
	if (svs.demofile)
		SZ_Write(&svs.demo_multicast, sv.multicast.data, sv.multicast.cursize);

	switch (to) {
	case MULTICAST_ALL_R:
		reliable = true;/* intentional fallthrough */
	case MULTICAST_ALL:
		leafnum = 0;
		mask = NULL;
		break;

	case MULTICAST_PHS_R:
		reliable = true;/* intentional fallthrough */
	case MULTICAST_PHS:
		leafnum = CM_PointLeafnum(origin);
		cluster = CM_LeafCluster(leafnum);
		mask = CM_ClusterPHS(cluster);
		break;

	case MULTICAST_PVS_R:
		reliable = true;/* intentional fallthrough */
	case MULTICAST_PVS:
		leafnum = CM_PointLeafnum(origin);
		cluster = CM_LeafCluster(leafnum);
		mask = CM_ClusterPVS(cluster);
		break;

	default:
		mask = NULL;
		Com_Error(ERR_FATAL, "SV_Multicast: bad to:%i", to);
	}

	/* send the data to all relevent clients */
	for (j = 0, client = svs.clients; j < maxclients->value; j++, client++) {
		if (client->state == cs_free || client->state == cs_zombie)
			continue;
		if (client->state != cs_spawned && !reliable)
			continue;

		if (mask) {
			leafnum = CM_PointLeafnum(client->edict->s.origin);
			cluster = CM_LeafCluster(leafnum);
			area2 = CM_LeafArea(leafnum);
			if (!CM_AreasConnected(area1, area2))
				continue;
			if (mask && (!(mask[cluster >> 3] & (1 << (cluster & 7)))))
				continue;
		}
		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);
}


/*
 * ================== SV_StartSound
 *
 * Each entity can have eight independant sound sources, like voice, weapon,
 * feet, etc.
 *
 * If cahnnel & 8, the sound will be sent to everyone, not just things in the
 * PHS.
 *
 * FIXME: if entity isn't in PHS, they must be forced to be sent or have the
 * origin explicitly sent.
 *
 * Channel 0 is an auto-allocate channel, the others override anything already
 * running on that entity/channel pair.
 *
 * An attenuation of 0 will play full volume everywhere in the level. Larger
 * attenuations will drop off.  (max 4 attenuation)
 *
 * Timeofs can range from 0.0 to 0.1 to cause sounds to be started later in the
 * frame than they normally would.
 *
 * If origin is NULL, the origin is determined from the entity origin or the
 * midpoint of the entity box for bmodels. ==================
 */
void
SV_StartSound(vec3_t origin, edict_t * entity, int channel,
    int soundindex, float volume,
    float attenuation, float timeofs)
{
	int		sendchan;
	int		flags;
	int		i;
	int		ent;
	vec3_t		origin_v;
	qboolean	use_phs;

	if (volume < 0 || volume > 1.0)
		Com_Error(ERR_FATAL, "SV_StartSound: volume = %f", volume);

	if (attenuation < 0 || attenuation > 4)
		Com_Error(ERR_FATAL, "SV_StartSound: attenuation = %f", attenuation);

	/* if (channel < 0 || channel > 15) */
	/* Com_Error (ERR_FATAL, "SV_StartSound: channel = %i", channel); */

	if (timeofs < 0 || timeofs > 0.255)
		Com_Error(ERR_FATAL, "SV_StartSound: timeofs = %f", timeofs);

	ent = NUM_FOR_EDICT(entity);

	if (channel & 8) {	/* no PHS flag */
		use_phs = false;
		channel &= 7;
	} else
		use_phs = true;

	sendchan = (ent << 3) | (channel & 7);

	flags = 0;
	if (volume != DEFAULT_SOUND_PACKET_VOLUME)
		flags |= SND_VOLUME;
	if (attenuation != DEFAULT_SOUND_PACKET_ATTENUATION)
		flags |= SND_ATTENUATION;

	/* the client doesn't know that bmodels have weird origins */
	/* the origin can also be explicitly set */
	if ((entity->svflags & SVF_NOCLIENT)
	    || (entity->solid == SOLID_BSP)
	    || origin)
		flags |= SND_POS;

	/* always send the entity number for channel overrides */
	flags |= SND_ENT;

	if (timeofs)
		flags |= SND_OFFSET;

	/*
	 * use the entity origin unless it is a bmodel or explicitly
	 * specified
	 */
	if (!origin) {
		origin = origin_v;
		if (entity->solid == SOLID_BSP) {
			for (i = 0; i < 3; i++)
				origin_v[i] = entity->s.origin[i] + 0.5 * (entity->mins[i] + entity->maxs[i]);
		} else {
			VectorCopy(entity->s.origin, origin_v);
		}
	}
	MSG_WriteByte(&sv.multicast, svc_sound);
	MSG_WriteByte(&sv.multicast, flags);
	MSG_WriteByte(&sv.multicast, soundindex);

	if (flags & SND_VOLUME)
		MSG_WriteByte(&sv.multicast, volume * 255);
	if (flags & SND_ATTENUATION)
		MSG_WriteByte(&sv.multicast, attenuation * 64);
	if (flags & SND_OFFSET)
		MSG_WriteByte(&sv.multicast, timeofs * 1000);

	if (flags & SND_ENT)
		MSG_WriteShort(&sv.multicast, sendchan);

	if (flags & SND_POS)
		MSG_WritePos(&sv.multicast, origin);

	/* if the sound doesn't attenuate,send it to everyone */
	/* (global radio chatter, voiceovers, etc) */
	if (attenuation == ATTN_NONE)
		use_phs = false;

	if (channel & CHAN_RELIABLE) {
		if (use_phs)
			SV_Multicast(origin, MULTICAST_PHS_R);
		else
			SV_Multicast(origin, MULTICAST_ALL_R);
	} else {
		if (use_phs)
			SV_Multicast(origin, MULTICAST_PHS);
		else
			SV_Multicast(origin, MULTICAST_ALL);
	}
}


/*
 *
 * ============================================================================
 * ===
 *
 * FRAME UPDATES
 *
 * =============
 * =================================================================
 */



/*
 * ======================= SV_SendClientDatagram =======================
 */
qboolean
SV_SendClientDatagram(client_t * client)
{
	byte		msg_buf[MAX_MSGLEN];
	sizebuf_t	msg;

	SV_BuildClientFrame(client);

	SZ_Init(&msg, msg_buf, sizeof(msg_buf));
	msg.allowoverflow = true;

	/* send over all the relevant entity_state_t */
	/* and the player_state_t */
	SV_WriteFrameToClient(client, &msg);

	/* copy the accumulated multicast datagram */
	/* for this client out to the message */
	/* it is necessary for this to be after the WriteEntities */
	/* so that entity references will be current */
	if (client->datagram.overflowed)
		Com_Printf("WARNING: datagram overflowed for %s\n", client->name);
	else
		SZ_Write(&msg, client->datagram.data, client->datagram.cursize);
	SZ_Clear(&client->datagram);

	if (msg.overflowed) {	/* must have room left for the packet header */
		Com_Printf("WARNING: msg overflowed for %s\n", client->name);
		SZ_Clear(&msg);
	}
	/* send the datagram */
	Netchan_Transmit(&client->netchan, msg.cursize, msg.data);

	/* record the size for rate estimation */
	client->message_size[sv.framenum % RATE_MESSAGES] = msg.cursize;

	return true;
}


/*
 * ================== SV_DemoCompleted ==================
 */
void
SV_DemoCompleted(void)
{
	if (sv.demofile) {
		FS_FCloseFile(sv.demofile);
	}
	SV_Nextserver();
}


/*
 * ======================= SV_RateDrop
 *
 * Returns true if the client is over its current bandwidth estimation and
 * should not be sent another packet =======================
 */
qboolean
SV_RateDrop(client_t * c)
{
	int		total;
	int		i;

	/* never drop over the loopback */
	if (c->netchan.remote_address.type == NA_LOOPBACK)
		return false;

	total = 0;

	for (i = 0; i < RATE_MESSAGES; i++) {
		total += c->message_size[i];
	}

	if (total > c->rate) {
		c->surpressCount++;
		c->message_size[sv.framenum % RATE_MESSAGES] = 0;
		return true;
	}
	return false;
}

/*
 * ======================= SV_SendClientMessages =======================
 */
void
SV_SendClientMessages(void)
{
	int		i;
	client_t       *c;
	int		msglen;
	byte		msgbuf[MAX_MSGLEN];
	int		r;

	msglen = 0;

	/* read the next demo message if needed */
	if (sv.state == ss_demo && sv.demofile) {
		if (sv_paused->value)
			msglen = 0;
		else {
			/* get the next message */
			r = FS_FRead(&msglen, 4, 1, sv.demofile);
			if (r != 4) {	/* was 1 */
				SV_DemoCompleted();
				return;
			}
			msglen = LittleLong(msglen);
			if (msglen == -1) {
				SV_DemoCompleted();
				return;
			}
			if (msglen > MAX_MSGLEN)
				Com_Error(ERR_DROP, "SV_SendClientMessages: msglen > MAX_MSGLEN");
			r = FS_FRead(msgbuf, msglen, 1, sv.demofile);
			if (r != msglen) {	/* was 1 */
				SV_DemoCompleted();
				return;
			}
		}
	}
	/* send a message to each connected client */
	for (i = 0, c = svs.clients; i < maxclients->value; i++, c++) {
		if (!c->state)
			continue;
		/* if the reliable message overflowed, */
		/* drop the client */
		if (c->netchan.message.overflowed) {
			SZ_Clear(&c->netchan.message);
			SZ_Clear(&c->datagram);
			SV_BroadcastPrintf(PRINT_HIGH, "%s overflowed\n", c->name);
			SV_DropClient(c);
		}
		if (sv.state == ss_cinematic
		    || sv.state == ss_demo
		    || sv.state == ss_pic
		    )
			Netchan_Transmit(&c->netchan, msglen, msgbuf);
		else if (c->state == cs_spawned) {
			/* don't overrun bandwidth */
			if (SV_RateDrop(c))
				continue;

			SV_SendClientDatagram(c);
		} else {
			/* just update reliable	if needed */
			if (c->netchan.message.cursize || curtime - c->netchan.last_sent > 1000)
				Netchan_Transmit(&c->netchan, 0, NULL);
		}
	}
}
