
/*
	Sockets Forwards
	v0.1
	by bugsy 
*/

#if defined _socketsforwards_included
	#endinput
#endif
#define _socketsforwards_included

#if !defined _sockets2_included
	#include <sockets2>
#endif

#if !defined _engine_included
	#include <engine>
#endif

#define Socket_Change socket_change
#define Socket_Recv socket_recv
#define Socket_Send socket_send
#define Socket_Send2 socket_send2
#define Socket_Is_Writable socket_is_writable
#define Socket_DataAvailable socket_dataavailable
#define Socket_GetPeerAddr socket_getpeeraddr

const SOCKET_ERROR = -1;

const MAX_SLOTS = 10;
const Float:THINK_INTERVAL = 0.02;
const Float:TIMEOUT_INTERVAL = 15.0;

enum SocketData
{
	SocketHandle,
	bool:IsListening,
	bool:IsConnected,
	Float:LastActivity
}

enum ForwardTypes
{
	Forward_Connected,
	Forward_Disconnected,
	Forward_ConnectionRequest,
	Forward_IncomingData,
	Forward_TimedOut,
}
enum ForwardInfo
{
	ForwardHandle,
	ForwardString[ 25 ]
}
stock Forwards[ ForwardTypes ][ ForwardInfo ] = 
{
	{ 0 , "Socket_Connected" },
	{ 0 , "Socket_Disconnected" },
	{ 0 , "Socket_ConnectionRequest" },
	{ 0 , "Socket_IncomingData" },
	{ 0 , "Socket_TimedOut" }
};

stock g_Socket[ MAX_SLOTS ][ SocketData ] , g_SocketEntity , g_iPluginID = INVALID_PLUGIN_ID , bool:g_bThinkRegistered;

stock Socket_Open( const szServer[] , iPort , Protocol=SOCKET_TCP , &iError )
{
	new iSlot;
	if ( ( iSlot = FindEmptySlot() ) == -1 )
	{
		iError = 1;
		log_amx( "No available slots" );
		return SOCKET_ERROR;
	}
	
	new iError;
	if ( ( ( g_Socket[ iSlot ][ SocketHandle ] = socket_open( szServer , iPort , Protocol , iError ) ) != SOCKET_ERROR ) && !iError )
	{
		g_Socket[ iSlot ][ IsListening ] = false;
		g_Socket[ iSlot ][ IsConnected ] = false;
		g_Socket[ iSlot ][ LastActivity ] = _:get_gametime();	
		SF_StartUp();
	}
	else
	{
		log_amx( "Error connecting [%d]" , iError );
		return SOCKET_ERROR;
	}

	return g_Socket[ iSlot ][ SocketHandle ];
}

stock Socket_Listen( const szServer[] , iPort , Protocol=SOCKET_TCP , &iError )
{
	new iSlot;
	if ( ( iSlot = FindEmptySlot() ) == -1 )
	{
		iError = 1;
		log_amx( "No available slots" );
		return SOCKET_ERROR;
	}
	
	new iError;
	if ( ( ( g_Socket[ iSlot ][ SocketHandle ] = socket_listen( szServer , iPort , Protocol , iError ) ) != SOCKET_ERROR ) && !iError )
	{
		g_Socket[ iSlot ][ IsListening ] = true;
		g_Socket[ iSlot ][ IsConnected ] = false;
		g_Socket[ iSlot ][ LastActivity ] = _:get_gametime();	
		SF_StartUp();
	}
	else
	{
		log_amx( "Error listening [%d]" , iError );
		return SOCKET_ERROR;
	}

	return g_Socket[ iSlot ][ SocketHandle ];
}

stock Socket_Accept( iSocket , &iError )
{
	new iSlot;
	if ( ( iSlot = FindEmptySlot() ) == -1 )
	{
		iError = 1;
		log_amx( "No available slots" );
		return SOCKET_ERROR;
	}

	if ( ( ( g_Socket[ iSlot ][ SocketHandle ] = socket_accept( iSocket , iError ) ) != SOCKET_ERROR ) && !iError )
	{
		g_Socket[ iSlot ][ IsListening ] = false;
		g_Socket[ iSlot ][ IsConnected ] = true;
		g_Socket[ iSlot ][ LastActivity ] = _:get_gametime();
		SF_StartUp();
	}
	else
	{
		log_amx( "Error accepting connection" );
		return SOCKET_ERROR;
	}

	return g_Socket[ iSlot ][ SocketHandle ];
}

stock Socket_Close( iSocket )
{
	new iSlot , iRetVal = -1;
	for ( iSlot = 0 ; iSlot < MAX_SLOTS ; iSlot++ )
	{
		if ( g_Socket[ iSlot ][ SocketHandle ] == iSocket )
		{
			g_Socket[ iSlot ][ SocketHandle ] = 0;
			g_Socket[ iSlot ][ IsListening ] = false;
			g_Socket[ iSlot ][ LastActivity ] = _:0.0;
			g_Socket[ iSlot ][ IsConnected ] = false;
			iRetVal = socket_close( iSocket );
			break;
		}
	}

	return iRetVal;
}

stock SF_StartUp()
{
	if ( g_iPluginID == INVALID_PLUGIN_ID )
	{
		new szFile[ 64 ] , szTmp[ 1 ];
		get_plugin( -1 , szFile , charsmax( szFile ) , szTmp , 0 , szTmp , 0 , szTmp , 0 , szTmp , 0 );
		g_iPluginID = find_plugin_byfile( szFile , 0 );
	}

	if ( !g_SocketEntity )
	{
		Forwards[ Forward_Connected ][ ForwardHandle ] = CreateOneForward( g_iPluginID , Forwards[ Forward_Connected ][ ForwardString ] , FP_CELL );
		Forwards[ Forward_Disconnected ][ ForwardHandle ] = CreateOneForward( g_iPluginID , Forwards[ Forward_Disconnected ][ ForwardString ] , FP_CELL );
		Forwards[ Forward_ConnectionRequest ][ ForwardHandle ] = CreateOneForward( g_iPluginID , Forwards[ Forward_ConnectionRequest ][ ForwardString ] , FP_CELL );
		Forwards[ Forward_IncomingData ][ ForwardHandle ] = CreateOneForward( g_iPluginID , Forwards[ Forward_IncomingData ][ ForwardString ] , FP_CELL , FP_CELL );
		Forwards[ Forward_TimedOut ][ ForwardHandle ] = CreateOneForward( g_iPluginID , Forwards[ Forward_TimedOut ][ ForwardString ] , FP_CELL );
		
		g_SocketEntity = create_entity( "info_target" );
		entity_set_string( g_SocketEntity , EV_SZ_classname , "socket_entity" );

		if ( !g_bThinkRegistered )
		{
			register_think( "socket_entity" , "_Socket_EntityThink" );
			g_bThinkRegistered = true;
		}
		
		entity_set_float( g_SocketEntity , EV_FL_nextthink , get_gametime() + THINK_INTERVAL );		
	}
}

stock FindEmptySlot()
{
	new iSlot;
	for ( iSlot = 0 ; iSlot < MAX_SLOTS ; iSlot++ )
	{
		if ( !g_Socket[ iSlot ][ SocketHandle ] )
			break;
		else if ( iSlot == ( MAX_SLOTS - 1 ) ) 
			return -1;
	}

	return iSlot;
}

public _Socket_EntityThink( iEntity )
{	
	if ( iEntity != g_SocketEntity )
		return;

	static iBytesAvailable , iSlot , iActiveSlots , iSocket , iReturnVal;
	
	iActiveSlots = 0;
	for ( iSlot = 0 ; iSlot < MAX_SLOTS ; iSlot++ )
	{
		if ( ( iSocket = g_Socket[ iSlot ][ SocketHandle ] ) )
		{
			iActiveSlots++;
			
			if ( !g_Socket[ iSlot ][ IsConnected ] && !g_Socket[ iSlot ][ IsListening ] && socket_is_writable( iSocket ) )
			{
				g_Socket[ iSlot ][ IsConnected ] = true;
				ExecuteForward( Forwards[ Forward_Connected ][ ForwardHandle ] , iReturnVal , iSocket );
			}
			
			if ( socket_change( iSocket ) )
			{	
				if ( g_Socket[ iSlot ][ IsListening ] )
				{
					ExecuteForward( Forwards[ Forward_ConnectionRequest ][ ForwardHandle ] , iReturnVal , iSocket );
				}
				else
				{
					if ( ( iBytesAvailable = socket_dataavailable( iSocket ) ) )
					{
						g_Socket[ iSlot ][ LastActivity ] = _:get_gametime();
						ExecuteForward( Forwards[ Forward_IncomingData ][ ForwardHandle ] , iReturnVal , iSocket , iBytesAvailable );
					}
					else
					{	
						iActiveSlots--;
						g_Socket[ iSlot ][ SocketHandle ] = 0;
						ExecuteForward( Forwards[ Forward_Disconnected ][ ForwardHandle ] , iReturnVal , iSocket );
					}
				}
			}
			else if ( !g_Socket[ iSlot ][ IsListening ] && ( get_gametime() - g_Socket[ iSlot ][ LastActivity ] ) >= TIMEOUT_INTERVAL )
			{
				iActiveSlots--;
				g_Socket[ iSlot ][ SocketHandle ] = 0;
				ExecuteForward( Forwards[ Forward_TimedOut ][ ForwardHandle ] , iReturnVal , iSocket );				
			}
		}
	}
	
	if ( iActiveSlots )
	{
		entity_set_float( g_SocketEntity , EV_FL_nextthink , get_gametime() + THINK_INTERVAL );
	}
	else
	{
		DestroyForward( Forwards[ Forward_Connected ][ ForwardHandle ] );
		DestroyForward( Forwards[ Forward_Disconnected ][ ForwardHandle ] );
		DestroyForward( Forwards[ Forward_ConnectionRequest ][ ForwardHandle ] );
		DestroyForward( Forwards[ Forward_IncomingData ][ ForwardHandle ] );
		DestroyForward( Forwards[ Forward_TimedOut ][ ForwardHandle ] );

		remove_entity( g_SocketEntity );
		g_SocketEntity = 0;
	}
}
