/*****************************************************************************
The Dark Mod GPL Source Code

This file is part of the The Dark Mod Source Code, originally based
on the Doom 3 GPL Source Code as published in 2011.

The Dark Mod Source Code 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 3 of the License,
or (at your option) any later version. For details, see LICENSE.TXT.

Project: The Dark Mod (http://www.thedarkmod.com/)

******************************************************************************/

#ifndef __ASYNCSERVER_H__
#define __ASYNCSERVER_H__

/*
===============================================================================

  Network Server for asynchronous networking.

===============================================================================
*/

// MAX_CHALLENGES is made large to prevent a denial of service attack that could cycle
// all of them out before legitimate users connected
const int MAX_CHALLENGES				= 1024;

// if we don't hear from authorize server, assume it is down
const int AUTHORIZE_TIMEOUT				= 5000;

// states for the server's authorization process
typedef enum {
	CDK_WAIT = 0,	// we are waiting for a confirm/deny from auth
					// this is subject to timeout if we don't hear from auth
					// or a permanent wait if auth said so
	CDK_OK,
	CDK_ONLYLAN,
	CDK_MAXSTATES
} authState_t;

// states from the auth server, while the client is in CDK_WAIT
typedef enum {
	AUTH_NONE = 0,	// no reply yet
	AUTH_OK,		// this client is good
	AUTH_WAIT,		// wait - keep sending me srvAuth though
	AUTH_DENY,		// denied - don't send me anything about this client anymore
	AUTH_MAXSTATES
} authReply_t;

// message from auth to be forwarded back to the client
// some are locally hardcoded to save space, auth has the possibility to send a custom reply
typedef enum {
	AUTH_REPLY_WAITING = 0,	// waiting on an initial reply from auth
	AUTH_REPLY_UNKNOWN,		// client unknown to auth
	AUTH_REPLY_DENIED,		// access denied
	AUTH_REPLY_PRINT,		// custom message
	AUTH_REPLY_SRVWAIT,		// auth server replied and tells us he's working on it
	AUTH_REPLY_MAXSTATES
} authReplyMsg_t;

typedef struct challenge_s {
	netadr_t			address;		// client address
	int					clientId;		// client identification
	int					challenge;		// challenge code
	int					time;			// time the challenge was created
	int					pingTime;		// time the challenge response was sent to client
	bool				connected;		// true if the client is connected
	authState_t			authState;		// local state regarding the client
	authReply_t			authReply;		// cd key check replies
	authReplyMsg_t		authReplyMsg;	// default auth messages
	idStr				authReplyPrint;	// custom msg
	char				guid[12];		// guid
	int					OS;
} challenge_t;

typedef enum {
	SCS_FREE,			// can be reused for a new connection
	SCS_ZOMBIE,			// client has been disconnected, but don't reuse connection for a couple seconds
	SCS_CHILL,		// client is chilling
	SCS_CONNECTED,		// client is connected
	SCS_INGAME			// client is in the game
} serverClientState_t;

typedef struct serverClient_s {
	int					OS;
	int					clientId;
	serverClientState_t	clientState;
	int					clientPrediction;
	int					clientAheadTime;
	int					clientRate;
	int					clientPing;

	int					gameInitSequence;
	int					gameFrame;
	int					gameTime;

	idMsgChannel		channel;
	int					lastConnectTime;
	int					lastEmptyTime;
	int					lastPingTime;
	int					lastSnapshotTime;
	int					lastPacketTime;
	int					lastInputTime;
	int					snapshotSequence;
	int					acknowledgeSnapshotSequence;
	int					numDuplicatedUsercmds;

	char				guid[12];  // Even Balance - M. Quinn

} serverClient_t;


class idAsyncServer {
public:
						idAsyncServer();

	bool				InitPort( void );
	void				ClosePort( void );
	void				Spawn( void );
	void				Kill( void );
	void				ExecuteMapChange( void );

	int					GetPort( void ) const;
	netadr_t			GetBoundAdr( void ) const;
	bool				IsActive( void ) const { return active; }
	int					GetDelay( void ) const { return gameTimeResidual; }
	int					GetOutgoingRate( void ) const;
	int					GetIncomingRate( void ) const;
	bool				IsClientInGame( int clientNum ) const;
	int					GetClientPing( int clientNum ) const;
	int					GetClientPrediction( int clientNum ) const;
	int					GetClientTimeSinceLastPacket( int clientNum ) const;
	int					GetClientTimeSinceLastInput( int clientNum ) const;
	int					GetClientOutgoingRate( int clientNum ) const;
	int					GetClientIncomingRate( int clientNum ) const;
	float				GetClientOutgoingCompression( int clientNum ) const;
	float				GetClientIncomingCompression( int clientNum ) const;
	float				GetClientIncomingPacketLoss( int clientNum ) const;
	int					GetNumClients( void ) const;
	int					GetNumIdleClients( void ) const;
	int					GetLocalClientNum( void ) const { return localClientNum; }

	void				RunFrame( void );
	void				ProcessConnectionLessMessages( void );
	void				RemoteConsoleOutput( const char *string );
	void				SendReliableGameMessage( int clientNum, const idBitMsg &msg );
	void				SendReliableGameMessageExcluding( int clientNum, const idBitMsg &msg );
	void				LocalClientSendReliableMessage( const idBitMsg &msg );

	void				MasterHeartbeat( bool force = false );
	void				DropClient( int clientNum, const char *reason );

	void				PacifierUpdate( void );

	void				UpdateUI( int clientNum );

	void				UpdateAsyncStatsAvg( void );
	void				GetAsyncStatsAvgMsg( idStr &msg );

	void				PrintLocalServerInfo( void );

private:
	bool				active;						// true if server is active
	int					realTime;					// absolute time

	int					serverTime;					// local server time
	idPort				serverPort;					// UDP port
	int					serverId;					// server identification
	int					serverDataChecksum;			// checksum of the data used by the server
	int					localClientNum;				// local client on listen server

	challenge_t			challenges[MAX_CHALLENGES];	// to prevent invalid IPs from connecting
	serverClient_t		clients[MAX_ASYNC_CLIENTS];	// clients
	usercmd_t			userCmds[MAX_USERCMD_BACKUP][MAX_ASYNC_CLIENTS];

	int					gameInitId;					// game initialization identification
	int					gameFrame;					// local game frame
	int					gameTime;					// local game time
	int					gameTimeResidual;			// left over time from previous frame

	netadr_t			rconAddress;
	
	int					nextHeartbeatTime;
	int					nextAsyncStatsTime;

	bool				serverReloadingEngine;		// flip-flop to not loop over when net_serverReloadEngine is on

	bool				noRconOutput;				// for default rcon response when command is silent

	int					lastAuthTime;				// global for auth server timeout

	// track the max outgoing rate over the last few secs to watch for spikes
	// dependent on net_serverSnapshotDelay. 50ms, for a 3 seconds backlog -> 60 samples
	static const int	stats_numsamples = 60;
	int					stats_outrate[ stats_numsamples ];
	int					stats_current;
	int					stats_average_sum;
	int					stats_max;
	int					stats_max_index;

	void				PrintOOB( const netadr_t to, int opcode, const char *string );
	void				DuplicateUsercmds( int frame, int time );
	void				ClearClient( int clientNum );
	void				InitClient( int clientNum, int clientId, int clientRate );
	void				InitLocalClient( int clientNum );
	void				BeginLocalClient( void );
	void				LocalClientInput( void );
	void				CheckClientTimeouts( void );
	void				SendPrintBroadcast( const char *string );
	void				SendPrintToClient( int clientNum, const char *string );
	void				SendUserInfoBroadcast( int userInfoNum, const idDict &info, bool sendToAll = false );
	void				SendUserInfoToClient( int clientNum, int userInfoNum, const idDict &info );
	void				SendSyncedCvarsBroadcast( const idDict &cvars );
	void				SendSyncedCvarsToClient( int clientNum, const idDict &cvars );
	void				SendApplySnapshotToClient( int clientNum, int sequence );
	bool				SendEmptyToClient( int clientNum, bool force = false );
	bool				SendPingToClient( int clientNum );
	void				SendGameInitToClient( int clientNum );
	bool				SendSnapshotToClient( int clientNum );
	void				ProcessUnreliableClientMessage( int clientNum, const idBitMsg &msg );
	void				ProcessReliableClientMessages( int clientNum );
	void				ProcessChallengeMessage( const netadr_t from, const idBitMsg &msg );
	void				ProcessConnectMessage( const netadr_t from, const idBitMsg &msg );
	void				ProcessRemoteConsoleMessage( const netadr_t from, const idBitMsg &msg );
	void				ProcessGetInfoMessage( const netadr_t from, const idBitMsg &msg );
	bool				ConnectionlessMessage( const netadr_t from, const idBitMsg &msg );
	bool				ProcessMessage( const netadr_t from, idBitMsg &msg );
	void				ProcessAuthMessage( const idBitMsg &msg );
	int					ValidateChallenge( const netadr_t from, int challenge, int clientId );	// returns -1 if validate failed
	bool				VerifyChecksumMessage( int clientNum, const netadr_t *from, const idBitMsg &msg, idStr &reply, int OS ); // if from is NULL, clientNum is used for error messages
	void				SendReliableMessage( int clientNum, const idBitMsg &msg );				// checks for overflow and disconnects the faulty client
	int					UpdateTime( int clamp );
	void				SendEnterGameToClient( int clientNum );
	void				ProcessDownloadRequestMessage( const netadr_t from, const idBitMsg &msg );
};

#endif /* !__ASYNCSERVER_H__ */
