#ifndef _SocketBase_H
#define _SocketBase_H

//--------------------------------------------------------
//  Includes / Compiler directives
//--------------------------------------------------------

#include "lib/posix.h"
#include "lib/types.h"
#include "ps/Pyrogenesis.h"
#include <string.h>
#include "ps/CStr.h"

//-------------------------------------------------
// Error Codes
//-------------------------------------------------

DECLARE_ERROR( CONNECT_TIMEOUT );
DECLARE_ERROR( CONNECT_REFUSED );
DECLARE_ERROR( NO_SUCH_HOST );
DECLARE_ERROR( NO_ROUTE_TO_HOST );
DECLARE_ERROR( CONNECTION_BROKEN );
DECLARE_ERROR( WAIT_ABORTED );
DECLARE_ERROR( PORT_IN_USE );
DECLARE_ERROR( INVALID_PORT );
DECLARE_ERROR( WAIT_LOOP_FAIL );
DECLARE_ERROR( CONNECT_IN_PROGRESS );
DECLARE_ERROR( INVALID_PROTOCOL );

//-------------------------------------------------
// Declarations
//-------------------------------------------------

class CSocketInternal;

/**
 * An enumeration of all supported protocols, and the special value UNSPEC,
 * which represents an invalid address.
 */
// Modifiers Note: Each value in the enum should correspond to a sockaddr_*
// struct and a PF_* value
enum ESocketProtocol
{
	// This should be a value that's invalid for most socket functions, so that
	// you don't accidentally use an UNSPEC SocketAddress
	// PF_UNSPEC does not work, since it is accepted by many implementations as
	// a "default" protocol family - whatever that may be
	UNSPEC=((sa_family_t)-1),
	IPv4=PF_INET,
	IPv6=PF_INET6,
	/* More protocols */
};

/**
 * A protocol-independent representation of a socket address. All protocols
 * in the ESocketProtocol enum should have a corresponding member in this union.
 */
// Modifiers Note: Each member must contain a first field, compatible with the
// sin_family field of sockaddr_in. The field contains the ESocketProtocol value
// for the address, and it is returned by GetProtocol()
struct CSocketAddress
{
	union
	{
		sa_family_t m_Family;
		sockaddr_in m_IPv4;
		sockaddr_in6 m_IPv6;
	} m_Union;

	inline ESocketProtocol GetProtocol() const
	{
		return (ESocketProtocol)m_Union.m_Family;
	}

	inline CSocketAddress()
	{
		memset(&m_Union, 0, sizeof(m_Union));
		m_Union.m_Family=UNSPEC;
	}

	/**
	 * Create a wildcard address for the specified protocol with a specified
	 * port.
	 *
	 * @param port The port number, in local byte order
	 * @param proto The protocol to use; default IPv4
	 */
	explicit CSocketAddress(int port, ESocketProtocol proto=IPv4);

	/**
	 * Create an address from a numerical IPv4 address and port, port in local
	 * byte order, IPv4 address as a byte array in written order. The Protocol
	 * of the resulting SocketAddress will be IPv4
	 *
	 * @param address An IPv4 address as a byte array (in written order)
	 * @param port A port number (0-65535) in local byte order.
	 */
	CSocketAddress(u8 address[4], int port);

	/**
	 * Resolve the name using the system name resolution service (i.e. DNS) and
	 * store the resulting address. When multiple addresses are found, the
	 * first result is returned.
	 *
	 * Note that this call will block until the name resolution attempt is
	 * either completed successfully or timed out.
	 *
	 * @param name The name to resolve
	 * @param addr A reference to the variable to hold the address
	 *
	 * @return The result of the operation
	 * @retval PS_OK The hostname was successfully retrieved
	 * @retval NO_SUCH_HOST The hostname was not found
	 */
	static PS_RESULT Resolve(const char *name, int port, CSocketAddress &addr);

	/**
	 * Returns the string representation of the address, i.e. the IP (v4 or v6)
	 * address. Note that the port is not included in the string (mostly due to
	 * the fact that the port representation differs wildly between address
	 * families, and that Resolve does not take port as part of the hostname)
	 */
	CStr GetString() const;

	/**
	 * Returns the port number part of the address
	 */
	int GetPort() const;

	/*
		Create an address pointing to the loopback, with the specified port and
		protocol. Use this with Bind to only listen on the loopback interface.
	*/
	static CSocketAddress Loopback(int port, ESocketProtocol proto=IPv4);
};

/**
 * An enumeration of socket states
 *
 * @see CSocketBase::GetState()
 */
enum ESocketState
{
	/**
	 * The socket is unconnected. Use GetError() to see if it is due to a
	 * failure, a clean close, or it was never connected.
	 *
	 * @see CSocketBase::GetError()
	 */
	SS_UNCONNECTED=0,
	/**
	 * A connect attempt has started on a non-blocking socket. The error state
	 * will be CONNECTION_BROKEN.
	 *
	 * @see CSocketBase::OnWrite()
	 */
	SS_CONNECT_STARTED,
	/**
	 * The socket is connected. The error state will be set to PS_OK.
	 */
	SS_CONNECTED,
	/**
	 * The connection has been closed on this end, but the other end might have
	 * sent data that we haven't received yet. The error state will be set to
	 * PS_OK.
	 */
	SS_CLOSED_LOCALLY
};

/**
 * Contains the basic socket I/O abstraction and event callback methods.
 * A CSocketBase can only be instantiated as a subclass, none of the functions
 * are meant to exist as anything other than helper functions for socket
 * classes
 *
 * Any CSocket subclass that can be Accept:ed by a CServerSocket should
 * provide a constructor that takes a CSocketInternal pointer, and hands it to
 * the base class constructor.
 */
class CSocketBase
{
private:
	CSocketInternal *m_pInternal;
	ESocketState m_State;
	PS_RESULT m_Error;
	ESocketProtocol m_Proto;
	bool m_NonBlocking;

	/**
	 * Loop forever, waiting for events and calling the callbacks on sockets,
	 * according to their Op mask. This loop may be aborted by calling
	 * AbortWaitLoop.
	 *
	 * The global lock must be held when calling this function, and will be held
	 * upon return from it.
	 */
	static void RunWaitLoop();

	/**
	 * The network thread entry point. Simply locks the global lock and calls
	 * RunWaitLoop.
	 */
	friend void *WaitLoopThreadMain(void *);

#if OS_WIN
	/**
	 * Used by the winsock AsyncSelect windowproc
	 */
	friend void WaitLoop_SocketUpdateProc(int fd, int error, uint eventmask);

#else
	// These are utility functions for the unix select loop. Dox can be found in
	// the source file.
	static bool ConnectError(CSocketBase *);
	static void SocketWritable(CSocketBase *);
	static void SocketReadable(CSocketBase *);
#endif
		
	/**
	 * Abort the call to RunWaitLoop(), if one is currently running.
	 */
	static void AbortWaitLoop();

	/**
	 * Tell the running wait loop to abort. This is the platform-dependent
	 * implementation of AbortWaitLoop()
	 */
	static void SendWaitLoopAbort();
	void SendWaitLoopUpdate();

protected:
	/**
	 * Initialize a CSocketBase from a CSocketInternal pointer. Use in OnAccept
	 * callbacks to create an object of your subclass. This constructor should
	 * be overloaded by any subclass that may be Accept:ed.
	 */
	CSocketBase(CSocketInternal *pInt);
	virtual ~CSocketBase();

	/**
	 * Get the op mask for the socket.
	 */
	uint GetOpMask();
	
	/**
	 * Set the op mask for the socket, specifying which callbacks should be
	 * called by the WaitLoop. The initial op mask is zero, which means that
	 * this method must be called explicitly for any callbacks to be called.
	 * Note that before the call to BeginConnect or Bind, any call to this
	 * method is a no-op.
	 *
	 * It is safe to call this function while a RunWaitLoop is running.
	 *
	 * The wait loop guarantees that the callbacks specified in ops will be 
	 * called when appropriate, but does not make the opposite guarantee for 
	 * unset bits; i.e. any callback may be called even with a zero op mask.
	 */
	void SetOpMask(uint ops);

public:
	/**
	 *	These values are bitwise or-ed to produce op masks
	 */
	enum Ops
	{
		/**
		 * Call OnRead() on a stream socket when there is data to read from the
		 * socket, or OnAccept() on a server socket when there are incoming
		 * connections pending
		 */
		READ=1,
		// Call OnWrite() when there is space available in the socket's output
		// buffer. Has no effect on server sockets.
		WRITE=2
	};

	/**
	 * Constructs a CSocketBase. The OS socket object is not created by the
	 * constructor, but by the protected Initialize method, which is called by
	 * Connect and Bind.
	 *
	 * @see Connect
	 * @see Bind
	 */
	CSocketBase();
	
	/**
	 * Forcibly shuts down the network wait loop. This should happen
	 * automatically as soon as all sockets are closed.
	 */
	static void Shutdown();
	
	/**
	 * Returns the protocol set by Initialize. All SocketAddresses used with
	 * the socket must have the same SocketProtocol
	 */
	inline ESocketProtocol GetProtocol() const
	{ return m_Proto; }

	/**
	 * Destroy the OS socket. If the socket is not cleanly closed before, it
	 * will be forcefully closed by calling this method.
	 */
	void Destroy();

	/**
	 * Close the socket. No more data can be sent over the socket, but any data
	 * pending from the remote host will still be received, and the OnRead
	 * callback called (if the socket's op mask has the READ bit set). Note
	 * that the socket isn't actually closed until the remote end calls
	 * Close on the corresponding remote socket, upon which the OnClose
	 * callback is called with a status code of PS_OK.
	 */
	void Close();

	/**
	 * Create the OS socket for the specified protocol type.
	 */
	PS_RESULT Initialize(ESocketProtocol proto=IPv4);

	/**
	 * Connect the socket to the specified address. The socket must be
	 * initialized for the protocol of the address.
	 *
	 * @param addr The address to connect to
	 * @see SocketAddress::Resolve
	 */
	PS_RESULT Connect(const CSocketAddress &addr);

	/** @name Functions for Server Sockets */
	//@{

	/**
	 * Bind the socket to the specified address and start listening for
	 * incoming connections. You must initialize the socket for the correct
	 * SocketProtocol before calling Bind.
	 *
	 * @param addr The address to bind to
	 * @see SocketAddress::SocketAddress(int,SocketProtocol)
	 */
	PS_RESULT Bind(const CSocketAddress &addr);

	/**
	 * Store the address of the next incoming connection in the SocketAddress
	 * pointed to by addr. You must then choose whether to accept or reject the
	 * connection by calling Accept or Reject
	 *
	 * @param addr A pointer to a SocketAddress
	 * @return PS_OK or an error code
	 * 
	 * @see Accept(SocketAddress&)
	 * @see Reject()
	 */
	PS_RESULT PreAccept(CSocketAddress &addr);

	/**
	 * Accept the next incoming connection. You must construct a suitable
	 * CSocketBase subclass using the passed CSocketInternal.
	 * May only be called after a successful PreAccept call
	 */
	CSocketInternal *Accept();
	/**
	 * Reject the next incoming connection.
	 *
	 * May only be called after a successful PreAccept call
	 */
	void Reject();

	//@}
	/** @name Status and Options */
	//@{

	/**
	 * Set or reset non-blocking operation. When non-blocking, all socket
	 * operations will return immediately, having done none or parts of
	 * the operation. The default state for a socket is non-blocking
	 *
	 * @see CSocketBase::Read
	 * @see CSocketBase::Write
	 * @see CSocketBase::Connect
	 */
	void SetNonBlocking(bool nonBlocking=true);

	/**
	 * Return the current non-blocking state of the socket.
	 *
	 * @see SetNonBlocking(bool)
	 */
	inline bool IsNonBlocking() const
	{ return m_NonBlocking; }

	/**
	 * Return the error state of the socket. This will be the same value that
	 * was returned by the IO function that failed.
	 *
	 * @see GetState()
	 */
	inline PS_RESULT GetErrorState() const
	{ return m_Error; }

	/**
	 * Return the connection state of the socket. If the connection status is
	 * "unconnected", use GetError() to see if it was disconnected due to an
	 * error, or cleanly closed.
	 *
	 * @see SocketState
	 * @see GetError()
	 */
	inline ESocketState GetState() const
	{ return m_State; }

	/**
	 * Disable Nagle's algorithm (enable no-delay working mode)
	 */
	void SetTcpNoDelay(bool tcpNoDelay=true);

	/**
	 * Get the address of the remote end to which the socket is connected.
	 * 
	 * @return A reference to the socket address
	 */
	const CSocketAddress &GetRemoteAddress();

	//@}
	/** @name Stream I/O */
	//@{

	/**
	 * Attempt to read data from the socket. Any data available without blocking
	 * will be returned. Note that a successful return does not mean that the
	 * whole buffer was filled.
	 *
	 * @param buf A pointer to the buffer where the data should be written
	 * @param len The amount of data that should be read.
	 * @param bytesRead The number of bytes read will be stored in the variable
	 * pointed to by bytesRead
	 *
	 * @retval PS_OK Some or all data was successfully read.
	 * @retval CONNECTION_BROKEN The socket is not connected or a server socket
	 */
	PS_RESULT Read(void *buf, uint len, uint *bytesRead);
	
	/**
	 * Attempt to write data to the socket. All data that can be sent without
	 * blocking will be buffered.
	 *
	 * @param buf A pointer to the data that should be written
	 * @param len The length of the buffer.
	 * @param bytesWritten The number of bytes written will be stored in the
	 * variable pointed to by bytesWritten
	 *
	 * @retval PS_OK Some or all data was successfully read.
	 * @retval CONNECTION_BROKEN The socket is not connected or a server socket
	 */	
	PS_RESULT Write(void *buf, uint len, uint *bytesWritten);

	//@}
	/** @name Callbacks */
	//@{

	/**
	 * Called by the Network Thread when data is available for reading. Use
	 * SetOpMask with the READ bit set to enable calling of this function.
	 *
	 * For server sockets, "data is available for reading" means "incoming
	 * connections are pending".
	 */
	virtual void OnRead()=0;
	/**
	 * Called by the Network Thread when data can be written to the socket.
	 * Will only be called when the WRITE bit is set in the Op Mask of the
	 * socket.
	 */
	virtual void OnWrite()=0;

	/**
	 * The socket has been closed. It is not certain that the error code
	 * provides meaningful diagnostics. CONNECTION_BROKEN is the generic catch-
	 * all for erroneous closures, PS_OK for clean closures.
	 *
	 * @param errorCode A result code describing the reason why the socket was
	 * closed
	 */	
	virtual void OnClose(PS_RESULT errorCode)=0;
};

#endif
