#include <fenix/fxdll.h>
#include <stdlib.h>
#include <SDL/SDL_net.h>

/* PluginVersion is used to identify the plugin structures against which
 * we're linking to prevent potential mismatches and segmentation faults
 */
unsigned int PluginVersion = FXDLL_VERSION;

#define MAX_TCP_CON 32
#define MAX_UDP_SOCKETS 32
#define MAX_UDP_PACKETS 20
#define MAX_SOCKETSETS 10

typedef struct{
	IPaddress address;
	TCPsocket socket;
}_con;
_con conexiones[MAX_TCP_CON];


UDPpacket *UDPacket[MAX_UDP_PACKETS];
UDPpacket **UDPacketV[MAX_UDP_PACKETS];
UDPsocket UDPsockets[MAX_UDP_SOCKETS];

SDLNet_SocketSet SocketSet[MAX_SOCKETSETS];

static int net_init(INSTANCE * my, int * params)
{	
	return SDLNet_Init();
}

static int net_quit(INSTANCE * my, int * params)
{
	SDLNet_Quit();
	return 0;
}


// TCP

static int net_resolvehost(INSTANCE * my, int * params)
{
	char *a=(char*)string_get(params[1]);
	if(*a=='0')a=NULL;
	return SDLNet_ResolveHost(&conexiones[params[0]].address, a, (unsigned short)params[2]);

}

static int net_resolvehost2(INSTANCE * my, int * params)
{
	IPaddress ip;
	ip.host=params[1];
	return SDLNet_ResolveHost(&conexiones[params[0]].address, SDLNet_ResolveIP(&ip), (unsigned short)params[2]);

}

static int net_tcp_open(INSTANCE * my, int * params)
{
	_con *t=&conexiones[params[0]];
	t->socket=SDLNet_TCP_Open(&t->address);
	return t->socket ? 0 : -1;
}

static int net_tcp_close(INSTANCE * my, int * params)
{
	SDLNet_TCP_Close(conexiones[params[0]].socket);
	return 0;
}

static int net_tcp_accept(INSTANCE * my, int * params)
{
	_con *t=&conexiones[params[0]];
	TCPsocket tt;
	tt=SDLNet_TCP_Accept(t->socket);
	if(tt){
		t->socket=tt;
		return 0;
	}else
		return -1;
}

static int net_tcp_getpeeraddress(INSTANCE * my, int * params)
{
	_con *t=&conexiones[params[0]];
	IPaddress *tt;
	tt=SDLNet_TCP_GetPeerAddress(t->socket);
	if(!tt)
		return -1;
	else{
		t->address=*tt;
		return 0;
	}
}

static int net_tcp_send(INSTANCE * my, int * params)
{
	_con *t=&conexiones[params[0]];
	return SDLNet_TCP_Send(t->socket,(void*)params[1],params[2]);
}

static int net_tcp_recv(INSTANCE * my, int * params)
{
	_con *t=&conexiones[params[0]];
	return SDLNet_TCP_Recv(t->socket,(void*)params[1],params[2]);
}

static int net_get_host(INSTANCE * my, int * params)
{
	return conexiones[params[0]].address.host;	
}

static int net_get_port(INSTANCE * my, int * params)
{
	return conexiones[params[0]].address.port;
}



// UDP Packets
static int net_udp_allocpacket(INSTANCE *my, int *params)
{
	return (UDPacket[params[0]]=SDLNet_AllocPacket(params[1])) ? 0 : -1;
}

static int net_udp_resizepacket(INSTANCE *my, int *params)
{
	return (SDLNet_ResizePacket(UDPacket[params[0]],params[1])==params[1]) ? 0: -1;
}

static int net_udp_freepacket(INSTANCE *my, int *params)
{
	SDLNet_FreePacket(UDPacket[params[0]]);
	return 0;
}

static int net_udp_getpacket(INSTANCE *my, int *params)
{
	return (int)UDPacket[params[0]];
}

static int net_udp_allocpacketv(INSTANCE *my, int *params)
{
	return (UDPacketV[params[0]]=SDLNet_AllocPacketV(params[1], params[2])) ? 0 : -1;
}

static int net_udp_freepacketv(INSTANCE *my, int *params)
{
	SDLNet_FreePacketV(UDPacketV[params[0]]);
	return 0;
}

static int net_udp_getpacketv(INSTANCE *my, int *params)
{
	return (int)UDPacketV[params[0]];
}


// UDP Sockets
static int net_udp_open(INSTANCE *my, int *params)
{
	return (UDPsockets[params[0]]=SDLNet_UDP_Open((Uint16)params[1])) ? 0 : -1;
}

static int net_udp_close(INSTANCE *my, int *params)
{
	SDLNet_UDP_Close(UDPsockets[params[0]]);
	return 0;
}

static int net_udp_bind(INSTANCE *my, int *params)
{
	IPaddress dir;
	char *a=(char*)string_get(params[2]);
	if(*a=='0')a=NULL;
	SDLNet_ResolveHost(&dir, a, (unsigned short)params[3]);
	return SDLNet_UDP_Bind(UDPsockets[params[0]], params[1], &dir);
}

static int net_udp_bind2(INSTANCE *my, int *params)
{
	IPaddress dir;
	dir.host=params[2];
	SDLNet_ResolveHost(&dir, SDLNet_ResolveIP(&dir), (unsigned short)params[3]);
	return SDLNet_UDP_Bind(UDPsockets[params[0]], params[1], &dir);
}

static int net_udp_unbind(INSTANCE *my, int *params)
{
	SDLNet_UDP_Unbind(UDPsockets[params[0]], params[1]);
	return 0;
}


static int net_udp_send(INSTANCE *my, int *params)
{
	return SDLNet_UDP_Send(UDPsockets[params[0]], params[1], UDPacket[params[2]]);
}

static int net_udp_recv(INSTANCE *my, int *params)
{
	return SDLNet_UDP_Recv(UDPsockets[params[0]], UDPacket[params[1]]);
}

static int net_udp_getpeeraddress(INSTANCE *my, int *params)
{
	return (int)SDLNet_UDP_GetPeerAddress(UDPsockets[params[0]], params[1]);
}

static int net_udp_sendv(INSTANCE *my, int *params)
{
	return SDLNet_UDP_SendV(UDPsockets[params[0]], UDPacketV[params[1]], params[2]);
}

static int net_udp_recvv(INSTANCE *my, int *params)
{
	return SDLNet_UDP_RecvV(UDPsockets[params[0]], UDPacketV[params[1]]);
}


// Socket Sets

static int net_socketset_alloc(INSTANCE *my, int *params)
{
	return (SocketSet[params[0]]=SDLNet_AllocSocketSet(params[1])) ? 0 : -1;
}

static int net_socketset_free(INSTANCE *my, int *params)
{
	SDLNet_FreeSocketSet(SocketSet[params[0]]);
	return 0;
}

static int net_socketset_tcp_add(INSTANCE *my, int *params)
{
	return SDLNet_TCP_AddSocket(SocketSet[params[0]],conexiones[params[1]].socket);
}

static int net_socketset_udp_add(INSTANCE *my, int *params)
{
	return SDLNet_UDP_AddSocket(SocketSet[params[0]],UDPsockets[params[1]]);
}

static int net_socketset_tcp_del(INSTANCE *my, int *params)
{
	return SDLNet_TCP_DelSocket(SocketSet[params[0]],conexiones[params[1]].socket);
}

static int net_socketset_udp_del(INSTANCE *my, int *params)
{
	return SDLNet_UDP_DelSocket(SocketSet[params[0]],UDPsockets[params[1]]);
}

static int net_socketset_check(INSTANCE *my, int *params)
{
	return SDLNet_CheckSockets(SocketSet[params[0]], params[1]);
}

static int net_socketset_tcp_ready(INSTANCE *my, int *params)
{
	return SDLNet_SocketReady(conexiones[params[0]].socket);
}

static int net_socketset_udp_ready(INSTANCE *my, int *params)
{
	return SDLNet_SocketReady(UDPsockets[params[0]]);
}


FENIX_MainDLL RegisterFunctions (COMMON_PARAMS)
{
    FENIX_DLLImport

	// Inicio, salida
	FENIX_export ("NET_INIT", "", TYPE_DWORD, net_init);
	FENIX_export ("NET_QUIT", "", TYPE_DWORD, net_quit);

	// TCP
	FENIX_export ("NET_RESOLVEHOST", "ISI", TYPE_DWORD, net_resolvehost);	// numero_de_conexion, host, puerto
	FENIX_export ("NET_RESOLVEHOST2", "III", TYPE_DWORD, net_resolvehost2);	// numero_de_conexion, ip, puerto
	FENIX_export ("NET_TCP_OPEN", "I", TYPE_DWORD, net_tcp_open);	// numero_de_conexion
	FENIX_export ("NET_TCP_CLOSE", "I", TYPE_DWORD, net_tcp_close);	// numero_de_conexion
	FENIX_export ("NET_TCP_ACCEPT", "I", TYPE_DWORD, net_tcp_accept);	// numero_de_conexion
	FENIX_export ("NET_TCP_GETPEERADDRESS", "I", TYPE_DWORD, net_tcp_getpeeraddress);	// numero_de_conexion
	FENIX_export ("NET_TCP_SEND", "IPI", TYPE_DWORD, net_tcp_send);	// numero_de_conexion, puntero a los datos, longitud
	FENIX_export ("NET_TCP_RECV", "IPI", TYPE_DWORD, net_tcp_recv);	// numero_de_conexion, puntero a los datos, longitud maxima
	FENIX_export ("NET_GET_HOST", "I", TYPE_DWORD, net_get_host); // numero de conexion
	FENIX_export ("NET_GET_PORT", "I", TYPE_DWORD, net_get_port); // numero de conexion

	// UDP packets
	FENIX_export ("NET_UDP_ALLOCPACKET", "II", TYPE_DWORD, net_udp_allocpacket); // número de paquete, tamaño
	FENIX_export ("NET_UDP_RESIZEPACKET", "II", TYPE_DWORD, net_udp_resizepacket); // número de paquete, tamaño nuevo
	FENIX_export ("NET_UDP_FREEPACKET", "I", TYPE_DWORD, net_udp_freepacket); // número de paquete
	FENIX_export ("NET_UDP_ALLOCPACKETV", "III", TYPE_DWORD, net_udp_allocpacketv); // numero de vector, número de paquetes, tamaño
	FENIX_export ("NET_UDP_FREEPACKETV", "I", TYPE_DWORD, net_udp_freepacketv); // numero de vector
	FENIX_export ("NET_UDP_GETPACKET" , "I" , TYPE_POINTER, net_udp_getpacket); // numero de paquete
	FENIX_export ("NET_UDP_GETPACKETV" , "I" , TYPE_POINTER, net_udp_getpacketv); // numero de vector

	// UDP sockets
	FENIX_export ("NET_UDP_OPEN" ,  "II", TYPE_DWORD, net_udp_open); // numero de socket, puerto
	FENIX_export ("NET_UDP_CLOSE", "I", TYPE_DWORD, net_udp_close); // numero de socket
	FENIX_export ("NET_UDP_BIND" , "IISI", TYPE_DWORD, net_udp_bind); // numero de socket, canal, ip, puerto
	FENIX_export ("NET_UDP_BIND2" , "IIII", TYPE_DWORD, net_udp_bind2); // numero de socket, canal, int ip, puerto
	FENIX_export ("NET_UDP_UNBIND" , "II", TYPE_DWORD, net_udp_unbind); // numero de socket, canal
	FENIX_export ("NET_UDP_SEND" , "III" , TYPE_DWORD, net_udp_send); // numero de socket, canal, numero paquete
	FENIX_export ("NET_UDP_RECV" , "II"  , TYPE_DWORD, net_udp_recv); //numero de socket , numero de paquete
	FENIX_export ("NET_UDP_SENDV" , "III" , TYPE_DWORD, net_udp_sendv); // numero de socket, vector de paquetes, numero de paquetes
	FENIX_export ("NET_UDP_RECVV" , "II"  , TYPE_DWORD, net_udp_recvv); //numero de socket , numero de vector
	FENIX_export ("NET_UDP_GETPEERADDRESS", "II", TYPE_POINTER, net_udp_getpeeraddress); //numero de socket, canal

	// Socket Sets
	FENIX_export ("NET_SOCKETSET_ALLOC", "II", TYPE_DWORD, net_socketset_alloc); //numero de socketset, maxsockets
	FENIX_export ("NET_SOCKETSET_FREE", "I", TYPE_DWORD, net_socketset_free); //numero de socketset
	FENIX_export ("NET_SOCKETSET_TCP_ADD","II", TYPE_DWORD, net_socketset_tcp_add); // numero de socketset, numero de tcp socket
	FENIX_export ("NET_SOCKETSET_UDP_ADD","II", TYPE_DWORD, net_socketset_udp_add); // numero de socketset, numero de udp socket
	FENIX_export ("NET_SOCKETSET_TCP_DEL","II", TYPE_DWORD, net_socketset_tcp_del); // numero de socketset, numero de tcp socket
	FENIX_export ("NET_SOCKETSET_UDP_DEL","II", TYPE_DWORD, net_socketset_udp_del); // numero de socketset, numero de udp socket
	FENIX_export ("NET_SOCKETSET_CHECK", "II", TYPE_DWORD, net_socketset_check); // numero de socketset, timeout
	FENIX_export ("NET_SOCKETSET_TCP_READY", "I", TYPE_DWORD, net_socketset_tcp_ready); // numero de tcp socket
	FENIX_export ("NET_SOCKETSET_UDP_READY", "I", TYPE_DWORD, net_socketset_udp_ready); // numero de udp socket
}