// Emacs style mode select	 -*- C++ -*- 
//-----------------------------------------------------------------------------
//
// $Id:$
//
// Copyright (C) 1993-1996 by id Software, Inc.
//
// This source is available for distribution and/or modification
// only under the terms of the DOOM Source Code License as
// published by id Software. All rights reserved.
//
// The source is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
// for more details.
//
// $Log:$
//
// DESCRIPTION:
//		DOOM Network game communication and protocol,
//		all OS independent parts.
//
//-----------------------------------------------------------------------------

#include <stddef.h>

#include "version.h"
#include "m_alloc.h"
#include "m_menu.h"
#include "m_random.h"
#include "i_system.h"
#include "i_video.h"
#include "i_net.h"
#include "g_game.h"
#include "doomdef.h"
#include "doomstat.h"
#include "c_console.h"
#include "d_netinf.h"
#include "cmdlib.h"
#include "s_sound.h"
#include "m_cheat.h"
#include "p_effect.h"
#include "p_local.h"
#include "c_dispatch.h"
#include "gi.h"
#include "cl_main.h"

#define NCMD_EXIT				0x80000000
#define NCMD_RETRANSMIT 		0x40000000
#define NCMD_SETUP				0x20000000
#define NCMD_KILL				0x10000000		// kill game
#define NCMD_CHECKSUM			0x0fffffff

extern byte		*demo_p;		// [RH] Special "ticcmds" get recorded in demos

doomcom_t*		doomcom;		
doomdata_t* 	netbuffer;		// points inside doomcom


//
// NETWORKING
//
// gametic is the tic about to (or currently being) run
// maketic is the tick that hasn't had control made for it yet
// nettics[] has the maketics for all players 
//
// a gametic cannot be run until nettics[] > gametic for all players
//
#define RESENDCOUNT 	10
#define PL_DRONE		0x80	// bit flag in doomdata->player


FDynamicBuffer	NetSpecs[MAXPLAYERS][BACKUPTICS];
ticcmd_t		netcmds[MAXPLAYERS][BACKUPTICS];
int 			nettics[MAXNETNODES];
BOOL 			nodeingame[MAXNETNODES];				// set false as nodes leave game
BOOL	 		remoteresend[MAXNETNODES];				// set when local needs tics
int 			resendto[MAXNETNODES];					// set when remote needs tics
int 			resendcount[MAXNETNODES];

unsigned int	lastrecvtime[MAXPLAYERS];				// [RH] Used for pings
unsigned int	currrecvtime[MAXPLAYERS];

int 			nodeforplayer[MAXPLAYERS];

int 			maketic;
int 			lastnettic;
int 			skiptics;
int 			maxsend;

void D_ProcessEvents (void); 
void G_BuildTiccmd (ticcmd_t *cmd); 
void D_DoAdvanceDemo (void);
 
BOOL	 		reboundpacket;
doomdata_t		reboundstore;

// [RH] Special "ticcmds" get stored in here
static struct TicSpecial
{
	byte *streams[BACKUPTICS];
	int	  used[BACKUPTICS];
	byte *streamptr;
	int	  streamoffs;
	int   specialsize;
	int	  lastmaketic;
	BOOL  okay;

	TicSpecial ()
	{
		int i;

		lastmaketic = -1;
		specialsize = 256;

		for (i = 0; i < BACKUPTICS; i++)
			streams[i] = NULL;

		for (i = 0; i < BACKUPTICS; i++)
		{
			streams[i] = (byte *)Malloc (256);
			used[i] = 0;
		}
		okay = true;
	}

	~TicSpecial ()
	{
		int i;

		for (i = 0; i < BACKUPTICS; i++)
		{
			if (streams[i])
			{
				free (streams[i]);
				streams[i] = NULL;
				used[i] = 0;
			}
		}
		okay = false;
	}

	// Make more room for special commands.
	void GetMoreSpace ()
	{
		int i;

		specialsize <<= 1;

		DPrintf ("Expanding special size to %d\n", specialsize);

		for (i = 0; i < BACKUPTICS; i++)
			streams[i] = (byte *)Realloc (streams[i], specialsize);

		streamptr = streams[maketic%BACKUPTICS] + streamoffs;
	}

	void CheckSpace (int needed)
	{
		if (streamoffs >= specialsize - needed)
			GetMoreSpace ();

		streamoffs += needed;
	}

	void NewMakeTic ()
	{
		if (lastmaketic != -1)
			used[lastmaketic%BACKUPTICS] = streamoffs;

		lastmaketic = maketic;
		streamptr = streams[maketic%BACKUPTICS];
		streamoffs = 0;
	}

	TicSpecial &operator << (byte it)
	{
		if (streamptr)
		{
			CheckSpace (1);
			WriteByte (it, &streamptr);
		}
		return *this;
	}

	TicSpecial &operator << (short it)
	{
		if (streamptr)
		{
			CheckSpace (2);
			WriteWord (it, &streamptr);
		}
		return *this;
	}

	TicSpecial &operator << (int it)
	{
		if (streamptr)
		{
			CheckSpace (4);
			WriteLong (it, &streamptr);
		}
		return *this;
	}

	TicSpecial &operator << (const char *it)
	{
		if (streamptr)
		{
			CheckSpace (strlen (it) + 1);
			WriteString (it, &streamptr);
		}
		return *this;
	}

} specials;


//
// NetUpdate
// Builds ticcmds for console player,
// sends out a packet
//
int gametime;

void NetUpdate (void)
{
    int     nowtime;
    int     newtics;
    int     i;

    // check time
    nowtime = I_GetTime ();
    newtics = nowtime - gametime;
    gametime = nowtime;

    if (newtics <= 0)       // nothing new to update
       return;

    for (i = 0; i < newtics; i++)
    {
        I_StartTic ();
        D_ProcessEvents ();
        G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
        maketic++;
    }

}



//
// CheckAbort
//
BOOL CheckAbort (void)
{
	return false;
}


//
// D_CheckNetGame
// Works out player numbers among the net participants
//
extern int viewangleoffset;

void D_CheckNetGame (void)
{
    CL_InitNetwork ();

    displayplayer = consoleplayer = 0;
    D_SetupUserInfo();
}


//
// TryRunTics
//
int 	frameon;
int 	frameskip[MAXPLAYERS];
int 	oldnettics;

extern	BOOL	 advancedemo;

void TryRunTics (void)
{
    int             entertic;
    static int      oldentertics;
    int             realtics;

    // get real tics
    entertic = I_WaitForTic (oldentertics);
    realtics = entertic - oldentertics;
    oldentertics = entertic;

	DObject::BeginFrame ();

    // run the realtics tics
    while (realtics--)
    {
        NetUpdate ();  // check for new console commands

        C_Ticker ();
        M_Ticker ();
        G_Ticker ();
        gametic++;
    }

	DObject::EndFrame ();
}

void Net_NewMakeTic (void)
{
	specials.NewMakeTic ();
}

void Net_WriteByte (byte it)
{
	specials << it;
}

void Net_WriteWord (short it)
{
	specials << it;
}

void Net_WriteLong (int it)
{
	specials << it;
}

void Net_WriteString (const char *it)
{
	specials << it;
}

//==========================================================================
//
// Dynamic buffer interface
//
//==========================================================================

FDynamicBuffer::FDynamicBuffer ()
{
	m_Data = NULL;
	m_Len = m_BufferLen = 0;
}

FDynamicBuffer::~FDynamicBuffer ()
{
	if (m_Data)
	{
		free (m_Data);
		m_Data = NULL;
	}
	m_Len = m_BufferLen = 0;
}

void FDynamicBuffer::SetData (const byte *data, int len)
{
	if (len > m_BufferLen)
	{
		m_BufferLen = (len + 255) & ~255;
		m_Data = (byte *)Realloc (m_Data, m_BufferLen);
	}
	if (data)
	{
		m_Len = len;
		memcpy (m_Data, data, len);
	}
	else
	{
		len = 0;
	}
}

byte *FDynamicBuffer::GetData (int *len)
{
	if (len)
		*len = m_Len;
	return m_Len ? m_Data : NULL;
}


// [RH] Execute a special "ticcmd". The type byte should
//		have already been read, and the stream is positioned
//		at the beginning of the command's actual data.
void Net_DoCommand (int type, byte **stream, int player)
{
	char *s = NULL;

	switch (type)
	{
	case DEM_SAY:
		{
			byte who = ReadByte (stream);

			s = ReadString (stream);
			if ((who == 0) || players[player].userinfo.team[0] == 0) {
				// Said to everyone
				Printf (PRINT_CHAT, "%s: %s\n", players[player].userinfo.netname, s);
				S_Sound (CHAN_VOICE, gameinfo.chatSound, 1, ATTN_NONE);
			} else if (!stricmp (players[player].userinfo.team,
								 players[consoleplayer].userinfo.team)) {
				// Said only to members of the player's team
				Printf (PRINT_TEAMCHAT, "(%s): %s\n", players[player].userinfo.netname, s);
				S_Sound (CHAN_VOICE, gameinfo.chatSound, 1, ATTN_NONE);
			}
		}
		break;

	case DEM_MUSICCHANGE:
		s = ReadString (stream);
		S_ChangeMusic (s, true);
		break;

	case DEM_PRINT:
		s = ReadString (stream);
		Printf (PRINT_HIGH, s);
		break;

	case DEM_CENTERPRINT:
		s = ReadString (stream);
		C_MidPrint (s);
		break;

	case DEM_UINFCHANGED:
		D_ReadUserInfoStrings (player, stream, true);
		break;

	case DEM_SINFCHANGED:
		D_DoServerInfoChange (stream);
		break;

	case DEM_GIVECHEAT:
		cht_Give (&players[player], s = ReadString (stream));
		break;

	case DEM_GENERICCHEAT:
		cht_DoCheat (&players[player], ReadByte (stream));
		break;

	case DEM_CHANGEMAP:
		// Change to another map without disconnecting other players
		s = ReadString (stream);
		strncpy (level.nextmap, s, 8);
		// Using LEVEL_NOINTERMISSION tends to throw the game out of sync.
		level.flags |= LEVEL_CHANGEMAPCHEAT;
		G_ExitLevel (0);
		break;

	case DEM_SUICIDE:
		cht_Suicide (&players[player]);
		break;

	case DEM_ADDBOT:
		break;

	case DEM_KILLBOTS:
		break;

	default:
		I_Error ("Unknown net command: %d", type);
		break;
	}

	if (s)
		delete[] s;
}

void Net_SkipCommand (int type, byte **stream)
{
	char *s = NULL;

	switch (type)
	{
		case DEM_SAY:
		case DEM_ADDBOT:
			ReadByte (stream);
			s = ReadString (stream);
			break;

		case DEM_MUSICCHANGE:
		case DEM_PRINT:
		case DEM_CENTERPRINT:
		case DEM_UINFCHANGED:
		case DEM_SINFCHANGED:
		case DEM_GIVECHEAT:
		case DEM_CHANGEMAP:
			s = ReadString (stream);
			break;

		case DEM_GENERICCHEAT:
		case DEM_DROPPLAYER:
			ReadByte (stream);
			break;

		default:
			break;
	}

	if (s)
		delete[] s;
}
