#ifndef __SOCKET_H_INCLUDED__
#define __SOCKET_H_INCLUDED__

#include <string>            // For string
#include <exception>         // For exception class
#include <stdint.h>

using namespace std;

//	Signals a problem with the execution of a socket call.
class SocketException : public exception {
public:

	//	Construct a SocketException with a explanatory message.
	//	message explanatory message
	//	incSysMsg true if system message (from strerror(errno))
	//	should be appended to the user provided message

	SocketException(const string &message, bool inclSysMsg = false) throw();

	//	Provided just to guarantee that no exceptions are thrown.
	~SocketException() throw();

	//	Get the exception message
	//	Returns exception message
	const char *what() const throw();

	private:
		string userMessage;  // Exception message
};

//	Base class representing basic communication endpoint

class Socket {
public:

	//	Close and deallocate this socket
	~Socket();

	//	Get the local address
	//	Returns the local address of socket
	//	SocketException thrown if fetch fails
	string getLocalAddress() throw(SocketException);

	//	Get the local port
	//	returns local port of socket
	//	SocketException thrown if fetch fails
	uint16_t getLocalPort() throw(SocketException);

	//	Set the local port to the specified port and the local address
	//	to any interface
	//	SocketException thrown if setting local port fails/
	void setLocalPort(uint16_t localPort) throw(SocketException);

	//	Set the local port to the specified port and the local address
	//	to the specified address.  If you omit the port, a random port
	//	will be selected.
	//	SocketException thrown if setting local port or address fails
	void setLocalAddressAndPort(const string &localAddress,
			uint16_t localPort = 0) throw(SocketException);

	// Not used on Linux
	static void cleanUp() throw(SocketException);

	//	Resolve the specified service for the specified protocol to the
	//	corresponding port number in host byte order
	static unsigned short resolveService(const string &service = "http",
									   const string &protocol = "tcp");
protected:
	int32_t sockDesc;              // Socket descriptor
	Socket(int32_t type, int32_t protocol) throw(SocketException);
	Socket(int32_t sockDesc);

private:
	// Prevent the user from trying to use value semantics on this object
	Socket(const Socket &sock);
	void operator=(const Socket &sock);
};

//	Socket which is able to connect, send, and receive
class CommunicatingSocket : public Socket {
public:

	//	Establish a socket connection with the given foreign
	//	address and port
	//	ocketException thrown if unable to establish connection
	void connect(const string &foreignAddress, uint16_t foreignPort) throw(SocketException);

	//	Write the given buffer to this socket.  Call connect() before
	//	calling send()
	//	buffer, buffer to be written
	//	bufferLen, number of bytes from buffer to be written
	//	SocketException thrown if unable to send data
	void send(const void *buffer, uint32_t bufferLen) throw(SocketException);


	//	Read into the given buffer up to bufferLen bytes data from this
	//	socket.  Call connect() before calling recv()
	//	buffer, buffer to receive the data
	//	bufferLen, maximum number of bytes to read into buffer
	//	Returns number of bytes read, 0 for EOF, and -1 for error
	//  SocketException thrown if unable to receive data
	int32_t recv(void *buffer, uint32_t bufferLen) throw(SocketException);

	//   Get the foreign address.  Call connect() before calling recv()
	//   Returns foreign address
	//   SocketException thrown if unable to fetch foreign address
	string getForeignAddress() throw(SocketException);

	//   Get the foreign port.  Call connect() before calling recv()
	//   Returns foreign port
	//   SocketException thrown if unable to fetch foreign port
	unsigned short getForeignPort() throw(SocketException);

protected:
	CommunicatingSocket(int32_t type, int32_t protocol) throw(SocketException);
	CommunicatingSocket(int32_t newConnSD);
};

//	TCP socket for communication with other TCP sockets
class TCP_Socket : public CommunicatingSocket {
public:

	//	Construct a TCP socket with no connection
	//	SocketException thrown if unable to create TCP socket
	TCP_Socket() throw(SocketException);

	//	Construct a TCP socket with a connection to the given foreign address
	//	and port
	//	foreignAddress, foreign address (IP address or name)
	//	foreignPort, foreign port
	//	SocketException thrown if unable to create TCP socket
	TCP_Socket(const string &foreignAddress, uint16_t foreignPort) throw(SocketException);

private:
	// Access for TCPServerSocket::accept() connection creation
	friend class TCP_ServerSocket;
	TCP_Socket(int32_t newConnSD);
};


//	TCP socket class for servers

class TCP_ServerSocket : public Socket {
public:

	//	Construct a TCP socket for use with a server, accepting connections
	//	on the specified port on any interface
	//	localPort, local port of server socket, a value of zero will
	//                   give a system-assigned unused port
	//	queueLen, maximum queue length for outstanding connection requests (default 5)
	//	SocketException thrown if unable to create TCP server socket
	TCP_ServerSocket(uint16_t localPort, int32_t queueLen = 5)
	  throw(SocketException);

	//	Construct a TCP socket for use with a server, accepting connections
	//	on the specified port on the interface specified by the given address
	//	localAddress, local interface (address) of server socket
	//	localPort, local port of server socket
	//	queueLen, maximum queue length for outstanding connection requests (default 5)
	//	SocketException thrown if unable to create TCP server socket
	TCP_ServerSocket(const string &localAddress, uint16_t localPort,
	  int32_t queueLen = 5) throw(SocketException);

	//	Blocks until a new connection is established on this socket or error
	//	Returns a new connection socket
	//	SocketException thrown if attempt to accept a new connection fails
	TCP_Socket *accept() throw(SocketException);

private:
	void setListen(int32_t queueLen) throw(SocketException);
};


//	UDP socket class

class UDP_Socket : public CommunicatingSocket {
public:

	//	Construct a UDP socket
	//	SocketException thrown if unable to create UDP socket
	UDP_Socket() throw(SocketException);

	//	Construct a UDP socket with the given local port
	//	localPort, local port
	//	ocketException thrown if unable to create UDP socket
	UDP_Socket(uint16_t localPort) throw(SocketException);

	//  Construct a UDP socket with the given local port and address
	//	SocketException thrown if unable to create UDP socket
	UDP_Socket(const string &localAddress, uint16_t localPort)
	  throw(SocketException);

	//	Clear foreign address and port
	//	Returns true, if successful
	//	SocketException thrown if unable to disconnect UDP socket
	void disconnect() throw(SocketException);


	//  Send the given buffer as a UDP datagram to the
	//  specified address/port
	//	buffer, buffer to be written
	//	bufferLen, number of bytes to write
	//	foreignAddress, address (IP address or name) to send to
	//	foreignPort, port number to send to
	//	Returns, true if send is successful
	//	SocketException thrown if unable to send datagram

	void sendTo(const void *buffer, uint32_t bufferLen, const string &foreignAddress,
			uint16_t foreignPort) throw(SocketException);

	//	Read read up to bufferLen bytes data from this socket.  The given buffer
	//	is where the data will be placed
	//	buffer, buffer to receive data
	//	bufferLen, maximum number of bytes to receive
	//	sourceAddress, address of datagram source
	//	sourcePort, port of data source
	//	Returns number of bytes received and -1 for error
	//	SocketException thrown if unable to receive datagram
	int32_t recvFrom(void *buffer, uint32_t bufferLen, string &sourceAddress,
			   uint16_t &sourcePort) throw(SocketException);

	//	Set the multicast TTL
	//	multicastTTL, multicast TTL
	//	SocketException thrown if unable to set TTL
	void setMulticastTTL(uint8_t* multicastTTL) throw(SocketException);


	//	Join the specified multicast group
	//	multicastGroup, multicast group address to join
	//	SocketException thrown if unable to join group
	void joinGroup(const string &multicastGroup) throw(SocketException);

	//	Leave the specified multicast group
	//	multicastGroup, multicast group address to leave
	//	SocketException thrown if unable to leave group
	void leaveGroup(const string &multicastGroup) throw(SocketException);

private:
	void setBroadcast();
};
#endif
