/* copyright (c) 2007 magnus auvinen, see licence.txt for more info */
#include <string.h>
#include <stdio.h>

#include "e_system.h"
#include "e_config.h"
#include "e_network.h"

/*
	header (6 bytes)
		unsigned char flags;		1
		unsigned char seq_ack[3];	4
		unsigned char token[2];		6
*/

enum
{
	NETWORK_VERSION = 1,
	
	NETWORK_HEADER_SIZE = 6,
	NETWORK_MAX_PAYLOAD = 1024,
	NETWORK_MAX_PACKET_SIZE = NETWORK_HEADER_SIZE+NETWORK_MAX_PAYLOAD,
	NETWORK_MAX_CLIENTS = 16,
	
	NETWORK_CONNSTATE_OFFLINE=0,
	NETWORK_CONNSTATE_CONNECT=1,
	NETWORK_CONNSTATE_CONNECTACCEPTED=2,
	NETWORK_CONNSTATE_ONLINE=3,
	NETWORK_CONNSTATE_ERROR=4,
	
	NETWORK_PACKETFLAG_CONNECT=0x01,
	NETWORK_PACKETFLAG_ACCEPT=0x02,
	NETWORK_PACKETFLAG_CLOSE=0x04,
	NETWORK_PACKETFLAG_VITAL=0x08,
	NETWORK_PACKETFLAG_RESEND=0x10,
	NETWORK_PACKETFLAG_CONNLESS=0x20,
	
	NETWORK_MAX_SEQACK=0x1000
};

static int current_token = 1;

typedef struct
{
	unsigned char ID[2];
	unsigned char version;
	unsigned char flags;
	unsigned short seq;
	unsigned short ack;
	unsigned crc;
	int token;
	unsigned data_size;
	int64 first_send_time;
	unsigned char *data;
} NETPACKETDATA;

static void send_packet(NETSOCKET socket, NETADDR4 *addr, NETPACKETDATA *packet)
{
	unsigned char buffer[NETWORK_MAX_PACKET_SIZE];
	int send_size = NETWORK_HEADER_SIZE+packet->data_size;

	buffer[0] = packet->flags;
	buffer[1] = ((packet->seq>>4)&0xf0) | ((packet->ack>>8)&0x0f);
	buffer[2] = packet->seq;
	buffer[3] = packet->ack;
	buffer[4] = packet->token>>8;
	buffer[5] = packet->token&0xff;
	mem_copy(buffer+NETWORK_HEADER_SIZE, packet->data, packet->data_size);
	net_udp4_send(socket, addr, buffer, send_size);
}

typedef struct RINGBUFFER_ITEM_t
{
	struct RINGBUFFER_ITEM_t *next;
	struct RINGBUFFER_ITEM_t *prev;
	int size;
} RINGBUFFER_ITEM;

typedef struct
{
	RINGBUFFER_ITEM *first;
	RINGBUFFER_ITEM *last;
	unsigned buffer_size;
} RINGBUFFER;

static void rb_init(RINGBUFFER *rb)
{
	rb->first = 0;
	rb->last = 0;
	rb->buffer_size = 0;
}

static void *rb_item_data(RINGBUFFER_ITEM *item)
{
	return (void*)(item+1);
}

static void *rb_alloc(RINGBUFFER *rb, int size)
{
	RINGBUFFER_ITEM *item = (RINGBUFFER_ITEM*)mem_alloc(sizeof(RINGBUFFER_ITEM)+size, 1);
	item->size = size;
	
	item->prev = rb->last;
	item->next = 0;
	if(rb->last)
		rb->last->next = item;
	else
		rb->first = item;
	rb->last = item;
	
	rb->buffer_size += size;
	return rb_item_data(item);
}

static void rb_pop_first(RINGBUFFER *rb)
{
	if(rb->first)
	{
		RINGBUFFER_ITEM *next = rb->first->next;
		rb->buffer_size -= rb->first->size;
		mem_free(rb->first);
		rb->first = next;
		if(rb->first)
			rb->first->prev = NULL;
		else
			rb->last = NULL;
	}
}

static void rb_clear(RINGBUFFER *rb)
{
	while(rb->first)
		rb_pop_first(rb);
}


typedef struct
{
	unsigned short seq;
	unsigned short ack;
	unsigned state;
	
	int token;
	
	int remote_closed;
	
	int connected;
	int disconnected;
	
	RINGBUFFER buffer;
	
	int64 last_update_time;
	int64 last_recv_time;
	int64 last_send_time;
	
	char error_string[256];
	
	NETADDR4 peeraddr;
	NETSOCKET socket;
	NETSTATS stats;
} NETCONNECTION;

typedef struct
{
	NETCONNECTION conn;
} NETSLOT;

struct NETSERVER_t
{
	NETSOCKET socket;
	NETSLOT slots[NETWORK_MAX_CLIENTS];
	int max_clients;
	NETFUNC_NEWCLIENT new_client;
	NETFUNC_NEWCLIENT del_client;
	void *user_ptr;
	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
} ;

struct NETCLIENT_t
{
	NETADDR4 server_addr;
	NETSOCKET socket;
	unsigned char recv_buffer[NETWORK_MAX_PACKET_SIZE];
	
	NETCONNECTION conn;
};

static void conn_reset_stats(NETCONNECTION *conn)
{
	mem_zero(&conn->stats, sizeof(conn->stats));
}

static void conn_reset(NETCONNECTION *conn)
{
	conn->seq = 0;
	conn->ack = 0;
	conn->remote_closed = 0;
	
	if(conn->state == NETWORK_CONNSTATE_ONLINE ||
		conn->state == NETWORK_CONNSTATE_ERROR)
	{
		conn->disconnected++;
	}
		
	conn->state = NETWORK_CONNSTATE_OFFLINE;
	conn->last_send_time = 0;
	conn->last_recv_time = 0;
	conn->last_update_time = 0;
	conn->token = -1;
	mem_zero(&conn->peeraddr, sizeof(conn->peeraddr));
	
	rb_clear(&conn->buffer);
}


static const char *conn_error(NETCONNECTION *conn)
{
	return conn->error_string;
}

static void conn_set_error(NETCONNECTION *conn, const char *str)
{
	str_copy(conn->error_string, str, sizeof(conn->error_string));
}

/*
static int conn_state(NETCONNECTION *conn)
{
	return conn->state;
}*/

static void conn_init(NETCONNECTION *conn, NETSOCKET socket)
{
	conn_reset(conn);
	conn_reset_stats(conn);
	conn->socket = socket;
	conn->connected = 0;
	conn->disconnected = 0;
	rb_init(&conn->buffer);
	mem_zero(conn->error_string, sizeof(conn->error_string));
}

static void conn_ack(NETCONNECTION *conn, int ack)
{
	while(1)
	{
		RINGBUFFER_ITEM *item = conn->buffer.first;
		NETPACKETDATA *resend;
		if(!item)
			break;
			
		resend = (NETPACKETDATA *)rb_item_data(item);
		if(resend->seq <= ack || (ack < NETWORK_MAX_SEQACK/3 && resend->seq > NETWORK_MAX_SEQACK/2))
			rb_pop_first(&conn->buffer);
		else
			break;
	}
}

static void conn_send_raw(NETCONNECTION *conn, NETPACKETDATA *data)
{
	conn->last_send_time = time_get();
	conn->stats.send_packets++;
	conn->stats.send_bytes += data->data_size + NETWORK_HEADER_SIZE;
	send_packet(conn->socket, &conn->peeraddr, data);
}

static void conn_resend(NETCONNECTION *conn)
{
	RINGBUFFER_ITEM *item = conn->buffer.first;
	while(item)
	{
		NETPACKETDATA *resend = (NETPACKETDATA *)rb_item_data(item);
		conn->stats.resend_packets++;
		conn->stats.resend_bytes += resend->data_size + NETWORK_HEADER_SIZE;
		conn_send_raw(conn, resend);
		item = item->next;
	}
}

static void conn_send(NETCONNECTION *conn, int flags, int data_size, const void *data)
{
	NETPACKETDATA p;

	if(flags&NETWORK_PACKETFLAG_VITAL)
		conn->seq = (conn->seq+1)%NETWORK_MAX_SEQACK;

	p.ID[0] = 'T';
	p.ID[1] = 'W';
	p.version = NETWORK_VERSION;
	p.flags = flags;
	p.seq = conn->seq;
	p.ack = conn->ack;
	p.crc = 0;
	p.token = conn->token;
	p.data_size = data_size;
	p.data = (unsigned char *)data;
	p.first_send_time = time_get();

	if(flags&NETWORK_PACKETFLAG_VITAL)
	{
		/* save packet if we need to resend */
		NETPACKETDATA *resend = (NETPACKETDATA *)rb_alloc(&conn->buffer, sizeof(NETPACKETDATA)+data_size);
		*resend = p;
		resend->data = (unsigned char *)(resend+1);
		mem_copy(resend->data, p.data, p.data_size);
	}
	
	/* TODO: calc crc */
	conn_send_raw(conn, &p);
}

static int conn_connect(NETCONNECTION *conn, NETADDR4 *addr)
{
	if(conn->state != NETWORK_CONNSTATE_OFFLINE)
		return -1;
	
	/* init connection */
	conn_reset(conn);
	conn->peeraddr = *addr;
	conn->token = current_token++;
	mem_zero(conn->error_string, sizeof(conn->error_string));
	conn->state = NETWORK_CONNSTATE_CONNECT;
	conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
	return 0;
}

static void conn_disconnect(NETCONNECTION *conn, const char *reason)
{
	if(conn->remote_closed == 0)
	{
		if(reason)
			conn_send(conn, NETWORK_PACKETFLAG_CLOSE, strlen(reason)+1, reason);
		else
			conn_send(conn, NETWORK_PACKETFLAG_CLOSE, 0, 0);

		conn->error_string[0] = 0;
		if(reason)
			str_copy(conn->error_string, reason, sizeof(conn->error_string));
	}
	
	conn_reset(conn);
}

static int conn_feed(NETCONNECTION *conn, NETPACKETDATA *p, NETADDR4 *addr)
{
	int64 now = time_get();
	conn->last_recv_time = now;
	conn->stats.recv_packets++;
	conn->stats.recv_bytes += p->data_size + NETWORK_HEADER_SIZE;
	
	if(p->flags&NETWORK_PACKETFLAG_CLOSE)
	{
		conn->state = NETWORK_CONNSTATE_ERROR;
		conn->remote_closed = 1;
		
		if(p->data_size)
		{
			/* make sure to sanitize the error string form the other party*/
			char str[128];
			if(p->data_size < 128)
				str_copy(str, (char *)p->data, p->data_size);
			else
				str_copy(str, (char *)p->data, 128);
			str_sanitize_strong(str);
			
			/* set the error string */
			conn_set_error(conn, str);
		}
		else
			conn_set_error(conn, "no reason given");
		if(config.debug)
			dbg_msg("conn", "closed reason='%s'", conn_error(conn));
		return 0;
	}
	
	if(conn->state == NETWORK_CONNSTATE_OFFLINE)
	{
		if(p->flags == NETWORK_PACKETFLAG_CONNECT)
		{
			/* send response and init connection */
			conn_reset(conn);
			conn->state = NETWORK_CONNSTATE_ONLINE;
			conn->connected++;
			conn->peeraddr = *addr;
			conn->token = p->token;
			conn->last_send_time = now;
			conn->last_recv_time = now;
			conn->last_update_time = now;
			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
			if(config.debug)
				dbg_msg("connection", "got connection, sending connect+accept");
		}
	}
	else if(net_addr4_cmp(&conn->peeraddr, addr) == 0)
	{
		if(p->token != conn->token)
			return 0;

		if(conn->state == NETWORK_CONNSTATE_ONLINE)
		{
			/* remove packages that are acked */
			conn_ack(conn, p->ack);
			
			/* check if resend is requested */
			if(p->flags&NETWORK_PACKETFLAG_RESEND)
				conn_resend(conn);
				
			if(p->flags&NETWORK_PACKETFLAG_VITAL)
			{
				if(p->seq == (conn->ack+1)%NETWORK_MAX_SEQACK)
				{
					/* in sequence */
					conn->ack = (conn->ack+1)%NETWORK_MAX_SEQACK;
				}
				else
				{
					/* out of sequence, request resend */
					if(config.debug)
						dbg_msg("conn", "asking for resend %d %d", p->seq, (conn->ack+1)%NETWORK_MAX_SEQACK);
					conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0);
					return 0;
				}
			}
			else
			{
				if(p->seq > conn->ack)
					conn_send(conn, NETWORK_PACKETFLAG_RESEND, 0, 0);
			}
			
			if(p->data_size == 0)
				return 0;
				
			return 1;
		}
		else if(conn->state == NETWORK_CONNSTATE_CONNECT)
		{
			/* connection made */
			if(p->flags == (NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT))
			{
				conn_send(conn, NETWORK_PACKETFLAG_ACCEPT, 0, 0);
				conn->state = NETWORK_CONNSTATE_ONLINE;
				conn->connected++;
				if(config.debug)
					dbg_msg("connection", "got connect+accept, sending accept. connection online");
			}
		}
		/*
		else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
		{
			// connection made
			if(p->flags == NETWORK_PACKETFLAG_ACCEPT)
			{
				conn->state = NETWORK_CONNSTATE_ONLINE;
				dbg_msg("connection", "got accept. connection online");
			}
		}*/
		else
		{
			/* strange packet, wrong state */
			conn->state = NETWORK_CONNSTATE_ERROR;
			conn_set_error(conn, "strange state and packet");
		}
	}
	else
	{
		/* strange packet, not ment for me */
	}
	
	return 0;
}



static int conn_update(NETCONNECTION *conn)
{
	int64 now = time_get();

	if(conn->state == NETWORK_CONNSTATE_OFFLINE || conn->state == NETWORK_CONNSTATE_ERROR)
		return 0;

	/* watch out for major hitches */
	{
		int64 delta = now-conn->last_update_time;
		if(conn->last_update_time && delta > time_freq()/2)
		{
			RINGBUFFER_ITEM *item = conn->buffer.first;
	
			dbg_msg("conn", "hitch %d", (int)((delta*1000)/time_freq()));
			conn->last_recv_time += delta;
	
			while(item)
			{
				NETPACKETDATA *resend = (NETPACKETDATA *)rb_item_data(item);
				resend->first_send_time += delta;
				item = item->next;
			}
		}

		conn->last_update_time = now;
	}
		
	
	/* check for timeout */
	if(conn->state != NETWORK_CONNSTATE_OFFLINE &&
		conn->state != NETWORK_CONNSTATE_CONNECT &&
		(now-conn->last_recv_time) > time_freq()*10)
	{
		conn->state = NETWORK_CONNSTATE_ERROR;
		conn_set_error(conn, "timeout");
	}
	
	/* check for large buffer errors */
	if(conn->buffer.buffer_size > 1024*64)
	{
		conn->state = NETWORK_CONNSTATE_ERROR;
		conn_set_error(conn, "too weak connection (out of buffer)");
	}
	
	if(conn->buffer.first)
	{
		NETPACKETDATA *resend = (NETPACKETDATA *)(conn->buffer.first+1);
		if(now-resend->first_send_time > time_freq()*10)
		{
			conn->state = NETWORK_CONNSTATE_ERROR;
			conn_set_error(conn, "too weak connection (not acked for 10 seconds)");
		}
	}
	
	/* send keep alives if nothing has happend for 250ms */
	if(conn->state == NETWORK_CONNSTATE_ONLINE)
	{
		if(time_get()-conn->last_send_time> time_freq()/4)
			conn_send(conn, NETWORK_PACKETFLAG_VITAL, 0, 0);
	}
	else if(conn->state == NETWORK_CONNSTATE_CONNECT)
	{
		if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect every 500ms */
			conn_send(conn, NETWORK_PACKETFLAG_CONNECT, 0, 0);
	}
	else if(conn->state == NETWORK_CONNSTATE_CONNECTACCEPTED)
	{
		if(time_get()-conn->last_send_time > time_freq()/2) /* send a new connect/accept every 500ms */
			conn_send(conn, NETWORK_PACKETFLAG_CONNECT|NETWORK_PACKETFLAG_ACCEPT, 0, 0);
	}
	
	return 0;
}


static int check_packet(unsigned char *buffer, int size, NETPACKETDATA *packet)
{
	/* check the size */
	if(size < NETWORK_HEADER_SIZE || size > NETWORK_MAX_PACKET_SIZE)
		return -1;
	
	/* read the packet */
	packet->ID[0] = 'T';
	packet->ID[1] = 'W';
	packet->version = NETWORK_VERSION;
	packet->flags = buffer[0];
	packet->seq = ((buffer[1]&0xf0)<<4)|buffer[2];
	packet->ack = ((buffer[1]&0x0f)<<8)|buffer[3];
	packet->crc = 0;
	packet->token = (buffer[4]<<8)|buffer[5];
	packet->data_size = size - NETWORK_HEADER_SIZE;
	packet->data = buffer+NETWORK_HEADER_SIZE;
	
	/* check the packet */
	if(packet->ID[0] != 'T' || packet->ID[1] != 'W')
		return 1;
	
	if(packet->version != NETWORK_VERSION)
		return 1;

	/* TODO: perform crc check */
	
	/* return success */
	return 0;
}

NETSERVER *netserver_open(NETADDR4 bindaddr, int max_clients, int flags)
{
	int i;
	NETSERVER *server;
	NETSOCKET socket = net_udp4_create(bindaddr);
	if(socket == NETSOCKET_INVALID)
		return 0;
	
	server = (NETSERVER *)mem_alloc(sizeof(NETSERVER), 1);
	mem_zero(server, sizeof(NETSERVER));
	server->socket = socket;
	server->max_clients = max_clients;
	if(server->max_clients > NETWORK_MAX_CLIENTS)
		server->max_clients = NETWORK_MAX_CLIENTS;
	if(server->max_clients < 1)
		server->max_clients = 1;
	
	for(i = 0; i < NETWORK_MAX_CLIENTS; i++)
		conn_init(&server->slots[i].conn, server->socket);

	return server;
}

int netserver_set_callbacks(NETSERVER *s, NETFUNC_NEWCLIENT new_client, NETFUNC_DELCLIENT del_client, void *user)
{
	s->new_client = new_client;
	s->del_client = del_client;
	s->user_ptr = user;
	return 0;
}

int netserver_max_clients(NETSERVER *s)
{
	return s->max_clients;
}

int netserver_close(NETSERVER *s)
{
	/* TODO: implement me */
	return 0;
}

int netserver_drop(NETSERVER *s, int client_id, const char *reason)
{
	/* TODO: insert lots of checks here */
	dbg_msg("net_server", "client dropped. cid=%d reason=\"%s\"", client_id, reason);
	conn_disconnect(&s->slots[client_id].conn, reason);

	if(s->del_client)
		s->del_client(client_id, s->user_ptr);
		
	return 0;
}

int netserver_update(NETSERVER *s)
{
	int i;
	for(i = 0; i < s->max_clients; i++)
	{
		conn_update(&s->slots[i].conn);
		if(s->slots[i].conn.state == NETWORK_CONNSTATE_ERROR)
			netserver_drop(s, i, conn_error(&s->slots[i].conn));
	}
	return 0;
}

int netserver_recv(NETSERVER *s, NETPACKET *packet)
{
	NETPACKETDATA data;
	int i, r, bytes, found;
	NETADDR4 addr;
	
	while(1)
	{
		bytes = net_udp4_recv(s->socket, &addr, s->recv_buffer, NETWORK_MAX_PACKET_SIZE);

		/* no more packets for now */
		if(bytes <= 0)
			break;
		
		r = check_packet(s->recv_buffer, bytes, &data);
		if(r == 0)
		{
			if(data.flags&NETWORK_PACKETFLAG_CONNLESS)
			{
				/* connection less packets */
				packet->client_id = -1;
				packet->address = addr;
				packet->flags = PACKETFLAG_CONNLESS;
				packet->data_size = data.data_size;
				packet->data = data.data;
				return 1;		
			}
			else
			{
				/* ok packet, process it */
				if(data.flags == NETWORK_PACKETFLAG_CONNECT)
				{
					found = 0;
					
					/* check if we already got this client */
					for(i = 0; i < s->max_clients; i++)
					{
						if(s->slots[i].conn.state != NETWORK_CONNSTATE_OFFLINE &&
							net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
						{
							found = 1; /* silent ignore.. we got this client already */
							break;
						}
					}
					
					/* client that wants to connect */
					if(!found)
					{
						for(i = 0; i < s->max_clients; i++)
						{
							if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE)
							{
								found = 1;
								conn_feed(&s->slots[i].conn, &data, &addr);
								if(s->new_client)
									s->new_client(i, s->user_ptr);
								break;
							}
						}
					}
					
					if(!found)
					{
						/* send connectionless packet */
						const char errstring[] = "server full";
						NETPACKETDATA p;
						p.ID[0] = 'T';
						p.ID[1] = 'W';
						p.version = NETWORK_VERSION;
						p.flags = NETWORK_PACKETFLAG_CLOSE;
						p.seq = 0;
						p.ack = 0;
						p.crc = 0;
						p.token = data.token;
						p.data_size = sizeof(errstring);
						p.data = (unsigned char *)errstring;
						send_packet(s->socket, &addr, &p);
					}
				}
				else
				{
					/* find matching slot */
					for(i = 0; i < s->max_clients; i++)
					{
						/* must be in some sort of online state */
						if(s->slots[i].conn.state == NETWORK_CONNSTATE_OFFLINE)
							continue;
							
						if(net_addr4_cmp(&s->slots[i].conn.peeraddr, &addr) == 0)
						{
							if(conn_feed(&s->slots[i].conn, &data, &addr))
							{
								if(data.data_size)
								{
									packet->client_id = i;	
									packet->address = addr;
									packet->flags = 0;
									packet->data_size = data.data_size;
									packet->data = data.data;
									return 1;
								}
							}
						}
					}
				}
			}
		}
		else
		{
			/* errornous packet, drop it */
			/* dbg_msg("server", "crazy packet"); */
		}
		
		/* read header */
		/* do checksum */
	}	
	
	return 0;
}

int netserver_send(NETSERVER *s, NETPACKET *packet)
{
	if(packet->data_size >= NETWORK_MAX_PAYLOAD)
	{
		dbg_msg("netserver", "packet payload too big. %d. dropping packet", packet->data_size);
		return -1;
	}
	
	if(packet->flags&PACKETFLAG_CONNLESS)
	{
		/* send connectionless packet */
		NETPACKETDATA p;
		p.ID[0] = 'T';
		p.ID[1] = 'W';
		p.version = NETWORK_VERSION;
		p.flags = NETWORK_PACKETFLAG_CONNLESS;
		p.seq = 0;
		p.ack = 0;
		p.crc = 0;
		p.data_size = packet->data_size;
		p.data = (unsigned char *)packet->data;
		send_packet(s->socket, &packet->address, &p);
	}
	else
	{
		int flags  = 0;
		dbg_assert(packet->client_id >= 0, "errornous client id");
		dbg_assert(packet->client_id < s->max_clients, "errornous client id");
		if(packet->flags&PACKETFLAG_VITAL)
			flags |= NETWORK_PACKETFLAG_VITAL;
		conn_send(&s->slots[packet->client_id].conn, flags, packet->data_size, packet->data);
	}
	return 0;
}

void netserver_stats(NETSERVER *s, NETSTATS *stats)
{
	int num_stats = sizeof(NETSTATS)/sizeof(int);
	int *istats = (int *)stats;
	int c, i;

	mem_zero(stats, sizeof(NETSTATS));
	
	for(c = 0; c < s->max_clients; c++)
	{
		if(s->slots[c].conn.state != NETWORK_CONNSTATE_OFFLINE)
		{
			int *sstats = (int *)(&(s->slots[c].conn.stats));
			for(i = 0; i < num_stats; i++)
				istats[i] += sstats[i];
		}
	}
}

NETSOCKET netserver_socket(NETSERVER *s)
{
	return s->socket;
}

int netserver_client_addr(NETSERVER *s, int client_id, NETADDR4 *addr)
{
	*addr = s->slots[client_id].conn.peeraddr;
	return 1;
}

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

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

int netclient_update(NETCLIENT *c)
{
	conn_update(&c->conn);
	if(c->conn.state == NETWORK_CONNSTATE_ERROR)
		netclient_disconnect(c, conn_error(&c->conn));
	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_connect(NETCLIENT *c, NETADDR4 *addr)
{
	conn_connect(&c->conn, addr);
	return 0;
}

int netclient_recv(NETCLIENT *c, NETPACKET *packet)
{
	while(1)
	{
		NETADDR4 addr;
		NETPACKETDATA data;
		int r;
		int bytes = net_udp4_recv(c->socket, &addr, c->recv_buffer, NETWORK_MAX_PACKET_SIZE);

		/* no more packets for now */
		if(bytes <= 0)
			break;
		
		r = check_packet(c->recv_buffer, bytes, &data);
		
		if(r == 0)
		{
			if(data.flags&NETWORK_PACKETFLAG_CONNLESS)
			{
				/* connection less packets */
				packet->client_id = -1;
				packet->address = addr;
				packet->flags = PACKETFLAG_CONNLESS;
				packet->data_size = data.data_size;
				packet->data = data.data;
				return 1;		
			}
			else
			{
				if(conn_feed(&c->conn, &data, &addr))
				{
					/* fill in packet */
					packet->client_id = 0;
					packet->address = addr;
					packet->flags = 0;
					packet->data_size = data.data_size;
					packet->data = data.data;
					return 1;
				}
				else
				{
					/* errornous packet, drop it */
				}
			}			
		}
	}
	
	return 0;
}

int netclient_send(NETCLIENT *c, NETPACKET *packet)
{
	if(packet->data_size >= NETWORK_MAX_PAYLOAD)
	{
		dbg_msg("netclient", "packet payload too big. %d. dropping packet", packet->data_size);
		return -1;
	}
	
	if(packet->flags&PACKETFLAG_CONNLESS)
	{
		/* send connectionless packet */
		NETPACKETDATA p;
		p.ID[0] = 'T';
		p.ID[1] = 'W';
		p.version = NETWORK_VERSION;
		p.flags = NETWORK_PACKETFLAG_CONNLESS;
		p.seq = 0;
		p.ack = 0;
		p.crc = 0;
		p.token = 0;
		p.data_size = packet->data_size;
		p.data = (unsigned char *)packet->data;
		send_packet(c->socket, &packet->address, &p);
	}
	else
	{
		int flags = 0;		
		dbg_assert(packet->client_id == 0, "errornous client id");
		if(packet->flags&PACKETFLAG_VITAL)
			flags |= NETWORK_PACKETFLAG_VITAL;
		conn_send(&c->conn, flags, packet->data_size, packet->data);
	}
	return 0;
}

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

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);
}
