// Asura_ReliableSocket.h
// Thomas Young - 15.04.09
// Copyright (C) 2009 Rebellion, All Rights Reserved.

// F I L E   W R A P P E R ///////////////////////////////////////////////////

#ifdef ASURA_USE_PRAGMA_ONCE
#pragma once
#endif

#ifndef ___ASURA_RELIABLESOCKET_H___
#define ___ASURA_RELIABLESOCKET_H___ 1

#ifdef ASURA_SUPPORT_EXTERNAL_NETWORK

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Network/Asura_Platform_NetSocket.h" // for Asura_NetAddress
#include "Debugging/Asura_Assert.h" // for Asura_Assertf

// C O N S T A N T S /////////////////////////////////////////////////////////

static const u_short usASURA_DEFAULT_VPORT = 3658; // only needed for PS3 - SCE_NP_PORT

// E N U M E R A T I O N S ///////////////////////////////////////////////////

// Platforms should return whichever of these most closely matches their state when queried
// They do not necessarily need to store a variable of this type

enum ASURA_RELIABLE_SOCKET_CLIENT_STATE
{
	ASURA_RS_CLIENT_STATE_INACTIVE		= 0,
	ASURA_RS_CLIENT_STATE_CONNECTING	,
	ASURA_RS_CLIENT_STATE_CONNECTED
};


enum ASURA_RELIABLE_SOCKET_LISTENER_STATE
{
	ASURA_RS_LISTENER_STATE_INACTIVE	= 0,
	ASURA_RS_LISTENER_STATE_LISTENING	,
};


enum ASURA_RELIABLE_SOCKET_DISCONNECT_REASON
{
	ASURA_RS_DISCONNECT_NOT_DISCONNECTED	= 0,
	ASURA_RS_DISCONNECT_UNKNOWN				,
	ASURA_RS_DISCONNECT_SHUTDOWN			,
	ASURA_RS_DISCONNECT_REJECTED			,
	ASURA_RS_DISCONNECT_TIMEOUT
};

// F O R W A R D   D E C L A R A T I O N S ///////////////////////////////////

class Asura_NetPort;
class Asura_ReliableConnectionID;

// T Y P E D E F S ///////////////////////////////////////////////////////////

typedef bool( *Asura_RS_Listener_Callback_Connection	) ( const Asura_ReliableConnectionID& xID, u_short usConnectDataSize, const char* pcConnectData );
typedef void( *Asura_RS_Listener_Callback_Disconnection	) ( const Asura_ReliableConnectionID& xID, ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason );
typedef void( *Asura_RS_Listener_Callback_Message		) ( const Asura_ReliableConnectionID& xID, u_short usMsgID, u_short usMsgSize, const char* pcMsgBody );

typedef void( *Asura_RS_Client_Callback_Disconnection	) ( ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason );
typedef void( *Asura_RS_Client_Callback_Message			) ( u_short usMsgID, u_short usMsgSize, const char* pcMsgBody );

// F U N C T I O N S /////////////////////////////////////////////////////////
template<typename T> const T* Asura_ReliableSocket_CheckAndExtractData( const void* pData, const u_int uDataSize )
{
	bool bSizeOK = ( sizeof(T) == uDataSize );
	Asura_Assertf( bSizeOK, ( "Invalid message payload. Size of data (%d) does not match type (%d)", uDataSize, sizeof( T ) ) );
	return ( bSizeOK ) ? reinterpret_cast<const T*>( pData ) : NULL;
}

#ifdef _DEBUG
void Asura_ReliableSocket_UnitTest();
#endif
// C L A S S   D E C L A R A T I O N S ///////////////////////////////////////

//__________________________ Asura_ReliableSocket_Client _________________________________

class Asura_ReliableSocket_Client
{
	// NOTE: The functions are stubbed out at the moment rather than being declared pure since
	// we may want to implement some platforms before others and we don't want compile errors.
	// Once all platforms have an implementation the public functions here should be made pure

public:
	virtual ~Asura_ReliableSocket_Client()	{}

	virtual bool Connect( const Asura_NetAddress& xAddress, Asura_NetPort& xPort, const void* pConnectData, const u_short usConnectDataSize ) { return false; }
	virtual void Disconnect( const bool bImmediate, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason ) {}

	virtual void Update() {}
	
	virtual bool SendMessage( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody ) { return false; }

	virtual ASURA_RELIABLE_SOCKET_CLIENT_STATE GetState () { return ASURA_RS_CLIENT_STATE_INACTIVE; }

	// TODO - these could be virtual and remove GetState().
	bool IsActive() { return GetState() != ASURA_RS_CLIENT_STATE_INACTIVE; }
	bool IsConnected() { return GetState() == ASURA_RS_CLIENT_STATE_CONNECTED; }
	
	void SetCallback_Disconnection( Asura_RS_Client_Callback_Disconnection pfnCallback ) { m_pfnCallback_Disconnection = pfnCallback; }
	void SetCallback_Message( Asura_RS_Client_Callback_Message pfnCallback ) { m_pfnCallback_Message = pfnCallback; }

	//Create the appropriate reliable socket client for the current environment
	static Asura_ReliableSocket_Client* Create();
	//Returns the appropriate address to connect to for local loop-back for the current environment
	static Asura_NetAddress GetLoopbackAddress();

protected:
	Asura_ReliableSocket_Client()	
	{
		m_pfnCallback_Disconnection	= 0;
		m_pfnCallback_Message		= 0;
	}

	Asura_RS_Client_Callback_Disconnection	m_pfnCallback_Disconnection;
	Asura_RS_Client_Callback_Message		m_pfnCallback_Message;
	
};

//__________________________ Asura_ReliableSocket_Listener _________________________________

class Asura_ReliableSocket_Listener
{
	// NOTE: The functions are stubbed out at the moment rather than being declared pure since
	// we may want to implement some platforms before others and we don't want compile errors.
	// Once all platforms have an implementation the public functions here should be made pure

public:
	virtual ~Asura_ReliableSocket_Listener	( )	{}

	virtual bool Listen( const Asura_NetPort& xPort ) { return false; }
	virtual bool Listen( const Asura_NetAddress& xAddress, const Asura_NetPort& xPort ) { return false; }
	virtual void Close() {}
	virtual void Disconnect( const Asura_ReliableConnectionID& xConnectionID, const ASURA_RELIABLE_SOCKET_DISCONNECT_REASON eReason ) {}

	virtual void Update() {}

	virtual bool SendMessage( const Asura_ReliableConnectionID& xConnectionID, const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody ) { return false; }
	virtual bool SendMessageToAll( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody ) { return false; }
	virtual bool SendMessageToAllExcept( const u_short usMsgID, const u_short usMsgBodySize, const void* pMsgBody, const Asura_ReliableConnectionID& xException ) { return false; }

	virtual u_short GetListenPort() const { return 0; }
	virtual u_int GetNumConnections() const { return 0; }

	virtual ASURA_RELIABLE_SOCKET_LISTENER_STATE GetState() { return ASURA_RS_LISTENER_STATE_INACTIVE; }

	// TODO - this could be virtual and remove GetState().
	bool IsActive() { return GetState() != ASURA_RS_LISTENER_STATE_INACTIVE; }

	void SetCallback_Connection( Asura_RS_Listener_Callback_Connection pfnCallback ) { m_pfnCallback_Connection	= pfnCallback; }
	void SetCallback_Disconnection( Asura_RS_Listener_Callback_Disconnection pfnCallback ) { m_pfnCallback_Disconnection = pfnCallback; }
	void SetCallback_Message( Asura_RS_Listener_Callback_Message pfnCallback ) { m_pfnCallback_Message = pfnCallback; }

	//Create the appropriate reliable socket listener for the current environment
	static Asura_ReliableSocket_Listener* Create();

protected:
	Asura_ReliableSocket_Listener ( )
	{
		m_pfnCallback_Connection	= 0;
		m_pfnCallback_Disconnection	= 0;
		m_pfnCallback_Message		= 0;
	}

	Asura_RS_Listener_Callback_Connection		m_pfnCallback_Connection;
	Asura_RS_Listener_Callback_Disconnection 	m_pfnCallback_Disconnection;
	Asura_RS_Listener_Callback_Message			m_pfnCallback_Message;
};


// Asura_NetPort is used for specifying ports for reliable sockets. For most
// platforms only the main port is used, but on PS3 the vport is used to get
// the signalling (NAT) api to work.
class Asura_NetPort
{
public:

	Asura_NetPort()
	{
		m_usPort = 0;
		m_usVPort = usASURA_DEFAULT_VPORT;
	}
	explicit Asura_NetPort( u_short usPort )
	{
		m_usPort = usPort;
		m_usVPort = usASURA_DEFAULT_VPORT;
	}

	u_short m_usPort;
	u_short m_usVPort;
};

const u_int uASURA_RELIABLESOCKET_INVALID_CONNECTION_ID = 0;

// Asura_ReliableConnectionID is a wrapper for a u_int but ensures that you can't 
// accidentally use in a u_int or other wrong value instead.
class Asura_ReliableConnectionID
{
public:

	Asura_ReliableConnectionID()
	{
		m_uValue = uASURA_RELIABLESOCKET_INVALID_CONNECTION_ID;
	}
	explicit Asura_ReliableConnectionID( u_int uInitialValue )
	{
		m_uValue = uInitialValue;
	}
	int AsInt() const { return static_cast<int>(m_uValue); }
	bool IsValid() const { return m_uValue != uASURA_RELIABLESOCKET_INVALID_CONNECTION_ID; }
	void Invalidate() { m_uValue = uASURA_RELIABLESOCKET_INVALID_CONNECTION_ID; }
	Asura_ReliableConnectionID& operator=( const Asura_ReliableConnectionID& xOther)
	{
		m_uValue = xOther.m_uValue;
		return *this;
	}
	bool operator==( const Asura_ReliableConnectionID& xOther) const
	{
		return m_uValue == xOther.m_uValue;
	}
	bool operator!=( const Asura_ReliableConnectionID& xOther) const
	{
		return m_uValue != xOther.m_uValue;
	}
	Asura_ReliableConnectionID& operator++()
	{
		++m_uValue;
		return (*this);
	}
	Asura_ReliableConnectionID operator++(int)
	{
		Asura_ReliableConnectionID xReturn(*this);
		operator++();
		return xReturn;
	}

private:
	u_int m_uValue;
};

#endif	// ASURA_SUPPORT_EXTERNAL_NETWORK

#endif	// ___ASURA_RELIABLESOCKET_H___

//eof
