/*  
    EQ2Emulator:  Everquest II Server Emulator
    Copyright (C) 2007  EQ2EMulator Development Team (http://www.eq2emulator.net)

    This file is part of EQ2Emulator.

    EQ2Emulator 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.

    EQ2Emulator 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 EQ2Emulator.  If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TCP_CONNECTION_H
#define TCP_CONNECTION_H
/*
  Parent classes for interserver TCP Communication.
  -Quagmire
*/

#ifdef WIN32
	#define snprintf	_snprintf
	#define vsnprintf	_vsnprintf
	#define strncasecmp	_strnicmp
	#define strcasecmp  _stricmp

	#include <process.h>
#else
	#include <pthread.h>
	#include <sys/socket.h>
	#include <netinet/in.h>
	#include <arpa/inet.h>
	#include <netdb.h>
	#include <unistd.h>
	#include <errno.h>
	#include <fcntl.h>
	#define INVALID_SOCKET -1
	#define SOCKET_ERROR -1
	#include "unix.h"

#endif

#include "types.h"
#include "Mutex.h"
#include "linked_list.h"
#include "queue.h"
#include "servertalk.h"
#include "timer.h"
#include "MiscFunctions.h"

class TCPServer;

#define TCPConnection_ErrorBufferSize	1024
#define MaxTCPReceiveBufferSize		524288

#define TCPS_Ready			0
#define TCPS_Connecting		1
#define TCPS_Connected		100
#define TCPS_Disconnecting	200
#define TCPS_Disconnected	201
#define TCPS_Closing		250
#define TCPS_Error			255

#ifndef DEF_eConnectionType
#define DEF_eConnectionType
enum eConnectionType {Incomming, Outgoing};
#endif

#ifdef WIN32
	void TCPServerLoop(void* tmp);
	void TCPConnectionLoop(void* tmp);
#else
	void* TCPServerLoop(void* tmp);
	void* TCPConnectionLoop(void* tmp);
#endif

enum eTCPMode { modeConsole, modeTransition, modePacket };
class TCPConnection {
public:
#pragma pack(1)
	struct TCPNetPacket_Struct {
		int32	size;
		struct {
			int8
				compressed : 1,
				destination : 1,
				flag3 : 1,
				flag4 : 1,
				flag5 : 1,
				flag6 : 1,
				flag7 : 1,
				flag8 : 1;
		} flags;
		int16	opcode;
		uchar	buffer[0];
	};
#pragma pack()

	static TCPNetPacket_Struct* MakePacket(ServerPacket* pack, int32 iDestination = 0);

	TCPConnection(TCPServer* iServer, SOCKET iSock, int32 irIP, int16 irPort, bool iOldFormat = false);
	TCPConnection(bool iOldFormat = false, TCPServer* iRelayServer = 0, eTCPMode iMode = modePacket);	// for outgoing connections
	TCPConnection(TCPServer* iServer, TCPConnection* iRelayLink, int32 iRemoteID, int32 irIP, int16 irPort);				// for relay connections
	virtual ~TCPConnection();

	// Functions for outgoing connections
	bool			Connect(char* irAddress, int16 irPort, char* errbuf = 0);
	bool			Connect(int32 irIP, int16 irPort, char* errbuf = 0);
	void			AsyncConnect(char* irAddress, int16 irPort);
	void			AsyncConnect(int32 irIP, int16 irPort);
	virtual void	Disconnect(bool iSendRelayDisconnect = true);

	virtual bool	SendPacket(ServerPacket* pack, int32 iDestination = 0);
	virtual bool	SendPacket(TCPNetPacket_Struct* tnps);
	bool			Send(const uchar* data, sint32 size);

	char*			PopLine();
	ServerPacket*	PopPacket(); // OutQueuePop()
	inline int32	GetrIP()			{ return rIP; }
	inline int16	GetrPort()			{ return rPort; }
	virtual int8	GetState();
	eTCPMode		GetMode()			{ return TCPMode; }
	inline bool		Connected()		{ return (GetState() == TCPS_Connected); }
	inline bool		ConnectReady()	{ return (bool) (GetState() == TCPS_Ready && ConnectionType == Outgoing); }
	void			Free();		// Inform TCPServer that this connection object is no longer referanced

	inline int32	GetID()			{ return id; }
	inline bool		IsRelayServer() { return RelayServer; }
	inline int32	GetRemoteID()	{ return RemoteID; }
	inline TCPConnection* GetRelayLink()	{ return RelayLink; }

	bool			GetEcho();
	void			SetEcho(bool iValue);
protected:
	friend class TCPServer;
	virtual bool	Process();
	void			SetState(int8 iState);
	inline bool		IsFree() { return pFree; }
	bool			CheckNetActive();

#ifdef WIN32
	friend void TCPConnectionLoop(void* tmp);
#else
	friend void* TCPConnectionLoop(void* tmp);
#endif
	SOCKET			sock;
	bool			RunLoop();
	Mutex			MLoopRunning;
	Mutex	MAsyncConnect;
	bool	GetAsyncConnect();
	bool	SetAsyncConnect(bool iValue);
	char*	charAsyncConnect;
	
#ifdef WIN32
	friend class TCPConnection;
#endif
	void	OutQueuePush(ServerPacket* pack);
	void	RemoveRelay(TCPConnection* relay, bool iSendRelayDisconnect);
private:
	void	ProcessNetworkLayerPacket(ServerPacket* pack);
	void	SendNetErrorPacket(const char* reason = 0);
	TCPServer*		Server;
	TCPConnection*	RelayLink;
	int32			RemoteID;
	sint32			RelayCount;

	bool pOldFormat;
	bool SendData(char* errbuf = 0);
	bool RecvData(char* errbuf = 0);
	bool ProcessReceivedData(char* errbuf = 0);
	bool ProcessReceivedDataAsPackets(char* errbuf = 0);
	bool ProcessReceivedDataAsOldPackets(char* errbuf = 0);
	void ClearBuffers();

	bool	pAsyncConnect;

	eConnectionType	ConnectionType;
	eTCPMode		TCPMode;
	bool	RelayServer;
	Mutex	MRunLoop;
	bool	pRunLoop;

	SOCKET	connection_socket;
	int32	id;
	int32	rIP;
	int16	rPort; // host byte order
	bool	pFree;

	Mutex	MState;
	int8	pState;

	void	LineOutQueuePush(char* line);
	MyQueue<char> LineOutQueue;
	MyQueue<ServerPacket> OutQueue;
	Mutex	MOutQueueLock;

	Timer*	keepalive_timer;
	Timer*	timeout_timer;

	uchar*	recvbuf;
	sint32	recvbuf_size;
	sint32	recvbuf_used;

	sint32	recvbuf_echo;
	bool	pEcho;
	Mutex	MEcho;

	void	InModeQueuePush(TCPNetPacket_Struct* tnps);
	MyQueue<TCPNetPacket_Struct> InModeQueue;
	Mutex	MSendQueue;
	uchar*	sendbuf;
	sint32	sendbuf_size;
	sint32	sendbuf_used;
	bool	ServerSendQueuePop(uchar** data, sint32* size);
	void	ServerSendQueuePushEnd(const uchar* data, sint32 size);
	void	ServerSendQueuePushEnd(uchar** data, sint32 size);
	void	ServerSendQueuePushFront(uchar* data, sint32 size);
};

class TCPServer {
public:
	TCPServer(int16 iPort = 0, bool iOldFormat = false);
	virtual ~TCPServer();

	bool	Open(int16 iPort = 0, char* errbuf = 0);			// opens the port
	void	Close();						// closes the port
	bool	IsOpen();
	inline int16	GetPort()		{ return pPort; }

	TCPConnection* NewQueuePop();

	void	SendPacket(ServerPacket* pack);
	void	SendPacket(TCPConnection::TCPNetPacket_Struct** tnps);
protected:
#ifdef WIN32
	friend void TCPServerLoop(void* tmp);
#else
	friend void* TCPServerLoop(void* tmp);
#endif
	void	Process();
	bool	RunLoop();
	Mutex	MLoopRunning;

	friend class TCPConnection;
	inline int32	GetNextID() { return NextID++; }
	void			AddConnection(TCPConnection* con);
	TCPConnection*	GetConnection(int32 iID);
private:
	void	ListenNewConnections();

	int32	NextID;
	bool	pOldFormat;

	Mutex	MRunLoop;
	bool	pRunLoop;

	Mutex	MSock;
	SOCKET	sock;
	int16	pPort;

	Mutex	MNewQueue;
	MyQueue<TCPConnection>		NewQueue;

	void	CheckInQueue();
	Mutex	MInQueue;
	TCPConnection::TCPNetPacket_Struct*	InQueuePop();
	MyQueue<TCPConnection::TCPNetPacket_Struct>	InQueue;

	LinkedList<TCPConnection*>*	list;
};
#endif
