#include <base/system.h>
#include "e_network.h"
#include "e_network_internal.h"

struct NETCLIENT
{
	NETADDR server_addr;
	NETSOCKET socket;
	
	NETRECVINFO recv;
	NETCONNECTION conn;
};

NETCLIENT *netclient_open(NETADDR bindaddr, int flags)
{
	NETCLIENT *client = (NETCLIENT *)mem_alloc(sizeof(NETCLIENT), 1);
	mem_zero(client, sizeof(NETCLIENT));
	client->socket = net_udp_create(bindaddr);
	conn_init(&client->conn, client->socket);
	return client;
}

int netclient_close(NETCLIENT *c)
{
	/* TODO: implement me */
	return 0;
}


int netclient_disconnect(NETCLIENT *c, const char *reason)
{
	dbg_msg("netclient", "disconnected. reason=\"%s\"", reason);
	conn_disconnect(&c->conn, reason);
	return 0;
}

int netclient_update(NETCLIENT *c)
{
	conn_update(&c->conn);
	if(c->conn.state == NET_CONNSTATE_ERROR)
		netclient_disconnect(c, conn_error(&c->conn));
	return 0;
}

int netclient_connect(NETCLIENT *c, NETADDR *addr)
{
	conn_connect(&c->conn, addr);
	return 0;
}

int netclient_error_string_reset(NETCLIENT *c)
{
	mem_zero(c->conn.error_string, sizeof(c->conn.error_string));
	return 0;
}

int netclient_recv(NETCLIENT *c, NETCHUNK *chunk)
{
	while(1)
	{
		NETADDR addr;
		int bytes;
			
		/* check for a chunk */
		if(recvinfo_fetch_chunk(&c->recv, chunk))
			return 1;
		
		/* TODO: empty the recvinfo */
		bytes = net_udp_recv(c->socket, &addr, c->recv.buffer, NET_MAX_PACKETSIZE);

		/* no more packets for now */
		if(bytes <= 0)
			break;

		if(unpack_packet(c->recv.buffer, bytes, &c->recv.data) == 0)
		{
			if(c->recv.data.flags&NET_PACKETFLAG_CONNLESS)
			{
				chunk->flags = NETSENDFLAG_CONNLESS;
				chunk->client_id = -1;
				chunk->address = addr;
				chunk->data_size = c->recv.data.data_size;
				chunk->data = c->recv.data.chunk_data;
				return 1;
			}
			else
			{
				if(conn_feed(&c->conn, &c->recv.data, &addr))
					recvinfo_start(&c->recv, &addr, &c->conn, 0);
			}
		}
	}
	return 0;
}

int netclient_send(NETCLIENT *c, NETCHUNK *chunk)
{
	if(chunk->data_size >= NET_MAX_PAYLOAD)
	{
		dbg_msg("netclient", "chunk payload too big. %d. dropping chunk", chunk->data_size);
		return -1;
	}
	
	if(chunk->flags&NETSENDFLAG_CONNLESS)
	{
		/* send connectionless packet */
		send_packet_connless(c->socket, &chunk->address, chunk->data, chunk->data_size);
	}
	else
	{
		int f = 0;
		dbg_assert(chunk->client_id == 0, "errornous client id");
		
		if(chunk->flags&NETSENDFLAG_VITAL)
			f = NET_CHUNKFLAG_VITAL;
		
		conn_queue_chunk(&c->conn, f, chunk->data_size, chunk->data);

		if(chunk->flags&NETSENDFLAG_FLUSH)
			conn_flush(&c->conn);
	}
	return 0;
}

int netclient_state(NETCLIENT *c)
{
	if(c->conn.state == NET_CONNSTATE_ONLINE)
		return NETSTATE_ONLINE;
	if(c->conn.state == NET_CONNSTATE_OFFLINE)
		return NETSTATE_OFFLINE;
	return NETSTATE_CONNECTING;
}

int netclient_flush(NETCLIENT *c)
{
	return conn_flush(&c->conn);
}

int netclient_gotproblems(NETCLIENT *c)
{
	if(time_get() - c->conn.last_recv_time > time_freq())
		return 1;
	return 0;
}

void netclient_stats(NETCLIENT *c, NETSTATS *stats)
{
	*stats = c->conn.stats;
}

const char *netclient_error_string(NETCLIENT *c)
{
	return conn_error(&c->conn);
}
