#ifndef _NETWORKER_H__
#define _NETWORKER_H__

#include <vector>
#include <list>
#include <map>
#include <string>
#include "SDL.h"
#include "SDL_net.h"

enum NetTypeEnum{NW_TCP, NW_UDP};
enum NetEventEnum{NW_ERROR, NW_ACCEPT, NW_CLOSE, NW_RECEIVE};

class NetIP
{
	public:
	NetIP()
	{}
	
	virtual std::string toString() const = 0;
};

class NetIPv4 : public NetIP
{
	public:
	unsigned char a, b, c, d;
	
	NetIPv4();
	NetIPv4(unsigned char a, unsigned char b, unsigned char c, unsigned char d);
	NetIPv4(const std::string& ip_address);
	
	virtual std::string toString() const;
};

/*class NetIPv6 : public NetIP
{
	public:
	Uint64 network;
	Uint64 host;
	
	NetIPv6()
		: network(0), host(0)
	{}
	NetIPv6(Uint64 network, Uint64 host)
		: network(network), host(host)
	{}
	
	virtual std::string toString() const
	{
		char buff[16];
		// FIXME
		//sprintf(buff, "%x:%x:%x::%x:%x:%x", network, host);
		return buff;
	}
};*/

class NetWorker;

class NetData
{
	friend void countAndAlloc(NetData&, const char*, char*);
	friend void packIt(NetData&, const char*, char*);
	friend class NetWorker;
	friend int PumpNetworkEvents(void*);
	
	private:
	unsigned int bytes;
	unsigned char* data;
	
	public:
	
	NetData();
	NetData(const NetData& src);
	NetData(const char* format, ...);
	NetData(unsigned int bytes, unsigned char* data);
	~NetData();
	
	void pack(const char* format, ...);
	bool unpack(const char* format, ...);
	
	unsigned int size() const;
	
	NetData copy() const;
	void free();
};

template<typename T>
class NetStack
{
	private:
	std::list<T> data;
	public:
	
	NetStack()
	{}
	~NetStack()
	{}
	
	T pop()
	{
		T datum = data.back();
		data.pop_back();
		return datum;
	}
	
	void push(const T& datum)
	{
		data.push_back(datum);
	}
	
	unsigned int size() const
	{
		return data.size();
	}
	
	bool empty() const
	{
		return (size() == 0);
	}
	
	void clear()
	{
		data.clear();
	}
};

template<typename T>
class NetQueue
{
	private:
	std::list<T> data;
	public:
	
	NetQueue()
	{}
	~NetQueue()
	{}
	
	T pop()
	{
		T datum = data.front();
		data.pop_front();
		return datum;
	}
	
	void push(const T& datum)
	{
		data.push_back(datum);
	}
	
	unsigned int size() const
	{
		return data.size();
	}
	
	bool empty() const
	{
		return (size() == 0);
	}
	
	void clear()
	{
		data.clear();
	}
};


class NetConnection
{
	friend class NetEvent;
	friend class NetWorker;
	friend int sendError(const std::string& err, int socket);
	friend int PumpNetworkEvents(void *nothing);
	
	private:
	
	NetConnection(int id);
	
	int id;
	
	std::string hostname;
	int port;
	
	void setID(int id);
	
	public:
	
	NetConnection();
	NetConnection(const NetConnection& copy);
	NetConnection(const std::string& hostname, unsigned int port = 9999);
	NetConnection(const NetIPv4& ip_address, unsigned int port = 9999);
	
	bool connect();
	bool connect(NetTypeEnum type);
	bool accept();
	void deny();
	bool bad() const;
	void close();
	
	bool send(const NetData& data) const;
	
	bool isServer() const;
	bool isConnected() const;
	std::string getHost() const;
	int getPort() const;
	int getID() const;
	
	void setHost(const std::string& hostname);
	void setPort(int port);
	
	
	bool operator==(const NetConnection& A) const;
	bool operator!=(const NetConnection& A) const;
};

class NetEvent
{
	public:
	NetEventEnum type;
	
	std::string message;
	NetConnection connection;
	NetData data;
	
	NetEvent()
		: type(NW_ERROR)
	{}
	NetEvent(NetEventEnum type, const NetConnection& connection)
		: type(type), connection(connection)
	{}
	NetEvent(NetEventEnum type, const std::string& message)
		: type(type), message(message)
	{}
	NetEvent(NetEventEnum type, const std::string& message, const NetConnection& connection)
		: type(type), message(message), connection(connection)
	{}
	NetEvent(const NetData& data, const NetConnection& connection)
		: type(NW_RECEIVE), connection(connection), data(data)
	{}
};

class NetSocket
{
	friend class NetWorker;
	private:
	int port;
	int socket;
	NetTypeEnum type;
	
	public:
	
	NetSocket()
		: port(-1), socket(-1), type(NW_TCP)
	{}
	NetSocket(unsigned int port, int socket, NetTypeEnum type)
		: port(port), socket(socket), type(type)
	{}
	
	void setPort(int port)
	{
		this->port = port;
	}
	
	int getPort() const
	{
		return port;
	}
	
	int getID() const
	{
		// FIXME: I think it'd be better to return NetConnection(socket)
		return socket;
	}
	
	void setType(NetTypeEnum type)
	{
		this->type = type;
	}
	
	NetTypeEnum getType() const
	{
		return type;
	}
};

class NetWorker
{
	friend class NetConnection;
	friend int sendError(const std::string& err, int socket);
	friend int PumpNetworkEvents(void *nothing);
	private:
	
	static NetWorker Instance;
	
	class ConnectionInfo
	{
		public:
		static int idCounter;
		
		std::string hostname;
		int port;
		NetTypeEnum type;
		bool remoteIsServer;  // true if the remote computer is the server
		int socket;
		
		bool accepted;
		int acceptingIndex;
		
		IPaddress ip;
		
		ConnectionInfo();
		ConnectionInfo(const ConnectionInfo& copy);
		ConnectionInfo(const std::string& hostname, unsigned int port = 9999);
		ConnectionInfo(const NetIPv4& ip_address, unsigned int port = 9999);
		
		public:
		
		
		int connect();
		int connect(NetTypeEnum type);
		int accept();
		void deny();
		void close();
		
		bool isServer() const;
		bool isConnected() const;
		
		bool send(const NetData& data) const;
	};
	
	// Mutexes
	static SDL_mutex* eventMutex;
	static SDL_mutex* pendingMutex;
	static SDL_mutex* connectionMutex;
	
	
	// Connections indexed by a unique integer (the socket index)
	static std::map<int, ConnectionInfo> connections;
	
	// Connections which have not been accepted yet, indexed by a unique integer (counting)
	static std::map<int, ConnectionInfo> pendingConnections;
	
	// Ports that are being listened to for new connections
	static std::vector<NetSocket> listening;
	
	// Event queue
	static NetQueue<NetEvent> events;
	
	static unsigned int maxSizeUDP;
	
	
	
	public:
	
	static bool init();
	static void quit();
	
	// Can be used as a monostate singleton
	NetWorker()
	{}
	NetWorker(const NetWorker& n)
	{}
	
	static std::vector<NetConnection> getConnections();
	static unsigned int numConnections();
	
	/*NetConnection newConnection(const std::string& hostname, unsigned int port = 9999);
	NetConnection newConnection(const NetIPv4& ip_address, unsigned int port = 9999);
	
	// Closes a connection, removes it from the connection list, and frees the memory.
	void freeConnection(const NetConnection& connection);*/
	
	// If enabled, the NetWorker starts putting new incoming connections into pendingConnections.  If disabled, incoming connections are rejected.
	static void listen(bool enable, unsigned int port, NetTypeEnum type = NW_TCP);
	static bool isListening();
	static bool isListening(unsigned int port);
	static std::vector<NetSocket> getListening();
	
	static bool popEvent(NetEvent& event);
	static void pushEvent(const NetEvent& event);
	
	
	static std::string getError();
	
	static unsigned int getUDPMax();
	static void setUDPMax(unsigned int maxBytes);
	
	
};





#endif