/*
	net_chan.c

	(description)

	Copyright (C) 1996-1997  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:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include <stdarg.h>
#include <time.h>

#include "QF/cvar.h"
#include "QF/dstring.h"
#include "QF/msg.h"
#include "QF/sys.h"

#include "compat.h"
#include "netchan.h"

#include "../qw/include/client.h"

#define	PACKET_HEADER	8

int         net_nochoke;
int         net_blocksend;
double     *net_realtime;
cvar_t     *showpackets;
cvar_t     *showdrop;
cvar_t     *qport;


void
Netchan_Init (void)
{
	int         port;

	// pick a port value that should be nice and random
	port = Sys_TimeID ();

	Cvar_SetValue (qport, port);
}

void
Netchan_Init_Cvars (void)
{
	showpackets = Cvar_Get ("showpackets", "0", CVAR_NONE, NULL,
							"Show all network packets");
	showdrop = Cvar_Get ("showdrop", "0", CVAR_NONE, NULL, "Toggle the "
						 "display of how many packets you are dropping");
	qport = Cvar_Get ("qport", "0", CVAR_NONE, NULL, "The internal port "
					  "number for the game networking code. Useful for "
					  "clients who use multiple connections through one "
					  "IP address (NAT/IP-MASQ) because default port is "
					  "random.");
}

/*
	Netchan_OutOfBand

	Sends an out-of-band datagram
*/
void
Netchan_OutOfBand (netadr_t adr, int length, byte * data)
{
	byte        send_buf[MAX_MSGLEN + PACKET_HEADER];
	sizebuf_t   send;

	// write the packet header
	send.data = send_buf;
	send.maxsize = sizeof (send_buf);
	send.cursize = 0;

	MSG_WriteLong (&send, -1);			// -1 sequence means out of band
	SZ_Write (&send, data, length);

	// send the datagram
	// zoid, no input in demo playback mode
	if (!net_blocksend)
		Netchan_SendPacket (send.cursize, send.data, adr);
}

/*
	Netchan_OutOfBandPrint

	Sends a text message in an out-of-band datagram
*/
void
Netchan_OutOfBandPrint (netadr_t adr, const char *format, ...)
{
	static dstring_t *string;
	va_list     argptr;

	if (!string)
		string = dstring_new ();

	va_start (argptr, format);
	dvsprintf (string, format, argptr);
	va_end (argptr);

	Netchan_OutOfBand (adr, strlen (string->str), (byte *) string->str);
}

/*
	Netchan_Setup

	called to open a channel to a remote system
*/
void
Netchan_Setup (netchan_t *chan, netadr_t adr, int qport, ncqport_e flags)
{
	memset (chan, 0, sizeof (*chan));

	chan->remote_address = adr;
	chan->last_received = *net_realtime;
	chan->incoming_sequence = -1;

	chan->message.data = chan->message_buf;
	chan->message.allowoverflow = true;
	chan->message.maxsize = sizeof (chan->message_buf);

	chan->qport = qport;
	chan->flags = flags;

	chan->rate = 1.0 / 2500.0;
}

#define	MAX_BACKUP	200

/*
	Netchan_CanPacket

	Returns true if the bandwidth choke isn't active
*/
qboolean
Netchan_CanPacket (netchan_t *chan)
{
	if (chan->cleartime < *net_realtime + MAX_BACKUP * chan->rate)
		return true;
	return false;
}

/*
	Netchan_CanReliable

	Returns true if the bandwidth choke isn't
*/
qboolean
Netchan_CanReliable (netchan_t *chan)
{
	if (chan->reliable_length)
		return false;					// waiting for ack
	return Netchan_CanPacket (chan);
}

void
Netchan_Transmit (netchan_t *chan, int length, byte *data)
{
	byte        send_buf[MAX_MSGLEN + PACKET_HEADER];
	int         i;
	unsigned int w1, w2;
	qboolean    send_reliable;
	sizebuf_t   send;

	// check for message overflow
	if (chan->message.overflowed) {
		chan->fatal_error = true;
		Sys_Printf ("%s:Outgoing message overflow\n",
					NET_AdrToString (chan->remote_address));
		return;
	}
	// if the remote side dropped the last reliable message, resend it
	send_reliable = false;

	if (chan->incoming_acknowledged > chan->last_reliable_sequence
		&& chan->incoming_reliable_acknowledged != chan->reliable_sequence)
		send_reliable = true;

	// if the reliable transmit buffer is empty, copy the current message out
	if (!chan->reliable_length && chan->message.cursize) {
		memcpy (chan->reliable_buf, chan->message_buf, chan->message.cursize);
		chan->reliable_length = chan->message.cursize;
		chan->message.cursize = 0;
		chan->reliable_sequence ^= 1;
		send_reliable = true;
	}
	// write the packet header
	send.data = send_buf;
	send.maxsize = sizeof (send_buf);
	send.cursize = 0;

	w1 = chan->outgoing_sequence | (send_reliable << 31);
	w2 = chan->incoming_sequence | (chan->incoming_reliable_sequence << 31);

	chan->outgoing_sequence++;

	MSG_WriteLong (&send, w1);
	MSG_WriteLong (&send, w2);

	/// Send the qport if appropriate (we are a client).
	if (chan->flags & NC_QPORT_SEND)
		MSG_WriteShort (&send, chan->qport);

	/// First copy the reliable message to the packet.
	if (send_reliable) {
		SZ_Write (&send, chan->reliable_buf, chan->reliable_length);
		chan->last_reliable_sequence = chan->outgoing_sequence;
	}
	/// Then add the unreliable part if space is available.
	if (send.maxsize - send.cursize >= length)
		SZ_Write (&send, data, length);

	/// Send the datagram if not blocked (in demo playback mode)
	i = chan->outgoing_sequence & (MAX_LATENT - 1);
	chan->outgoing_size[i] = send.cursize;
	chan->outgoing_time[i] = *net_realtime;

	// zoid, no input in demo playback mode
	if (!net_blocksend)
		Netchan_SendPacket (send.cursize, send.data, chan->remote_address);

	if (chan->cleartime < *net_realtime)
		chan->cleartime = *net_realtime + send.cursize * chan->rate;
	else
		chan->cleartime += send.cursize * chan->rate;
	if (net_nochoke)
		chan->cleartime = *net_realtime;

	if (showpackets->int_val & 1)
		Sys_Printf ("--> s=%i(%i) a=%i(%i) %-4i %i\n",
					chan->outgoing_sequence, send_reliable,
					chan->incoming_sequence, chan->incoming_reliable_sequence,
					send.cursize,
					chan->outgoing_sequence - chan->incoming_sequence);
}

qboolean
Netchan_Process (netchan_t *chan)
{
	unsigned int reliable_ack, reliable_message, sequence, sequence_ack;

	if (!net_blocksend)
		if (!NET_CompareAdr (net_from, chan->remote_address))
			return false;

	/// Get the sequence numbers.
	MSG_BeginReading (net_message);
	sequence = MSG_ReadLong (net_message);
	sequence_ack = MSG_ReadLong (net_message);

	/// Read the qport if appropriate (we are a server), but ignore it.
	if (chan->flags & NC_QPORT_READ)
		MSG_ReadShort (net_message);

	reliable_message = sequence >> 31;
	reliable_ack = sequence_ack >> 31;

	sequence &= ~(1 << 31);
	sequence_ack &= ~(1 << 31);

	if (showpackets->int_val & 2)
		Sys_Printf ("<-- s=%i(%i) a=%i(%i) %i\n", sequence, reliable_message,
					sequence_ack, reliable_ack, net_message->message->cursize);

	// get a rate estimation
#if 0 // FIXME: Dead code
	if (chan->outgoing_sequence - sequence_ack < MAX_LATENT) {
		int         i;
		double      time, rate;

		i = sequence_ack & (MAX_LATENT - 1);
		time = *net_realtime - chan->outgoing_time[i];
		time -= 0.1;					// subtract 100 ms
		if (time <= 0) {				// gotta be a digital link for <100
										// ms ping
			if (chan->rate > 1.0 / 5000)
				chan->rate = 1.0 / 5000;
		} else {
			if (chan->outgoing_size[i] < 512) {	// deal with only small
												// messages
				rate = chan->outgoing_size[i] / time;
				if (rate > 5000)
					rate = 5000;
				rate = 1.0 / rate;
				if (chan->rate > rate)
					chan->rate = rate;
			}
		}
	}
#endif

	/// Discard stale or duplicated packets.
	if (sequence < (unsigned int) chan->incoming_sequence + 1) {
		if (showdrop->int_val)
			Sys_Printf ("%s:Out of order packet %i at %i\n",
						NET_AdrToString (chan->remote_address), sequence,
						chan->incoming_sequence);
		return false;
	}

	/// Dropped packets don't keep the message from being used.
	chan->net_drop = sequence - (chan->incoming_sequence + 1);
	if (chan->net_drop > 0) {
		chan->drop_count += 1;

		if (showdrop->int_val)
			Sys_Printf ("%s:Dropped %i packets at %i\n",
						NET_AdrToString (chan->remote_address),
						sequence - (chan->incoming_sequence + 1), sequence);
	}

	/// If the current outgoing reliable message has been acknowledged,
	/// clear the buffer to make way for the next.
	if (reliable_ack == (unsigned int) chan->reliable_sequence)
		chan->reliable_length = 0;		// it has been received

	/// If this message contains a reliable message, bump
	/// incoming_reliable_sequence
	chan->incoming_sequence = sequence;
	chan->incoming_acknowledged = sequence_ack;
	chan->incoming_reliable_acknowledged = reliable_ack;
	if (reliable_message)
		chan->incoming_reliable_sequence ^= 1;

	// The message can now be read from the current message pointer.
	/// Update statistics counters.
	chan->frame_latency = chan->frame_latency * OLD_AVG
		+ (chan->outgoing_sequence - sequence_ack) * (1.0 - OLD_AVG);
	chan->frame_rate = chan->frame_rate * OLD_AVG
		+ (*net_realtime - chan->last_received) * (1.0 - OLD_AVG);
	chan->good_count += 1;

	chan->last_received = *net_realtime;

	return true;
}

void
Netchan_SendPacket (int length, const void *data, netadr_t to)
{
#if 0
	if (net_packetlog->int_val)
		Log_Outgoing_Packet (data, length, 1);
#endif
	NET_SendPacket (length, data, to);
}
