/**
 * @file
 * @brief Socket, descriptors and sockets.
 */
#ifndef UI_UTIL_SOCKET_HPP
#define UI_UTIL_SOCKET_HPP

// STDC++
#include <string>
#include <iostream>
#include <vector>

// STDC
#include <ctime>
#include <cstring>

// C++ libraries
#include <ui-utilcpp/Exception.hpp>
#include <ui-utilcpp/Sys.hpp>
#include <ui-utilcpp/File.hpp>

namespace UI {
namespace Util {

/**
 * @example EchoServer.cpp
 * Example program for socket / socket streams implementation.
 * Should be installed as ui-utilcpp-echoserver along with the library.
 */

/** @brief Socket abstraction. */
class Socket
#ifndef WIN32
	: public FileDescriptor
#endif
{
public:
	/** @brief Construct socket. */
	Socket(int fd=-1, bool closeFd=false);
#ifdef WIN32
	~Socket();

	/** @brief Exceptions for this class. */
	typedef CodeException<File::ErrorCode> Exception;
#endif

public:
	/** @brief Get human-readable id string. */
	std::string getId(bool const & peer=false) const;

	/** @brief Get peer id. */
	std::string getPeerId() const;

	/** @name Configure socket behaviour.
	 * @{ */
	Socket & setRcvTimeout(long int seconds, long int microseconds=0);
	Socket & setSndTimeout(long int seconds, long int microseconds=0);
	Socket & setUnblock(bool unblock=true);
	/** @} */

	/** @brief Connect this socket. */
	virtual Socket & connect();

	/** @brief Bind this socket. */
	virtual Socket & bind();

	/** @brief Start listening. */
	Socket & listen(int backlog=16);

	/** @brief Accept an incoming socket connection. */
	int accept(long int toSeconds=0, long int toMicroSeconds=0);

	/** @brief Shutdown socket. */
	int shutdown(int how, bool doThrow=true);

	/** @name Wrappers for recv(2) and send(2).
	 * @{ */
	ssize_t recv(void * const buf, size_t len, int flags=0);
	ssize_t send(void const * const msg, size_t len, int flags=0);
	/** @} */

	/** @brief C++-like virtual read method
	 *
	 * This implementation uses recv(2).
	 */
	virtual std::streamsize read(void * const buf, std::streamsize count);

	/** @brief C++-like virtual write method
	 *
	 * This implementation uses send(2).
	 */
	virtual std::streamsize write(void const * const buf, std::streamsize count);
#ifdef WIN32
	int getFd() const
	{
		return (int)fd_;
	}
	void sendStoredBuffer();

protected:
	SOCKET fd_;
	bool closeFd_;
	std::vector<char> sendBuffer;	// This buffer stores all data that is send using the stdSend() method;
					// all stored data is actually send before destroying the object.
#endif
};


/** @brief INet Socket. */
class INetSocket: public Socket
{
private:
	INetSocket(INetSocket const &);

public:
	INetSocket(std::string const & host, unsigned int port, bool closeFd=true, bool reuseAddr=false);
	INetSocket(int fd, bool closeFd=false);
	~INetSocket();

	virtual INetSocket & bind();
	virtual INetSocket & connect();
private:
	struct addrinfo * addrInfo_;
	struct addrinfo * addr_;
};

/** @brief Unix Socket. */
#ifndef WIN32
class UnixSocket: public Socket
{
public:
	UnixSocket(std::string const & path, bool closeFd=true);
	UnixSocket(int fd, bool closeFd=false);
	~UnixSocket();

	/** @brief Bind this unix socket.
	 *
	 * @note Default permissions:  "srw-------" (Only owner has rw-Access).
	 */
	UnixSocket & unixBind(uid_t uid=::geteuid(), gid_t gid=::getegid(), mode_t mode=S_IRUSR | S_IWUSR, bool silentUnlink=true);
	virtual UnixSocket & bind();
	virtual UnixSocket & connect();

private:
	struct sockaddr_un unSa_;
	bool silentUnlink_;
};
#endif

/** @brief Template IO stream buffer for all file descriptors types. */
template <typename FDType>
class FDTypeBuf: public std::streambuf, private FDType
{
public:
	/** @brief Constructor for file descriptor stream buffer. */
	FDTypeBuf(int fd, bool closeFd=false, int bufPutbackSize=4, int bufDataSize=1024)
		:FDType(fd, closeFd)
		,bufPutbackSize_(bufPutbackSize)
		,bufDataSize_(bufDataSize)
		,buf_(new char[bufPutbackSize_+bufDataSize_])
	{
		setg(buf_+bufPutbackSize, buf_+bufPutbackSize, buf_+bufPutbackSize);
	}

	~FDTypeBuf()
	{
		delete[] buf_;
	}

	/** @brief Shortcut. */
	typedef std::streambuf::traits_type traits;

protected:
	/** @brief Buffer control variables. */
	int bufPutbackSize_, bufDataSize_;
	/** @brief Internal buffer. */
	char * buf_;

	/** @brief streambuf overflow overload. */
	virtual int overflow(int c)
	{
		if (c != traits::eof())
		{
			char ch((char)c);
			if (FDType::write(&ch, 1) != 1)
			{
				return traits::eof();
			}
		}
		return c;
	}

	/** @brief streambuf xsputn overload. */
	virtual std::streamsize xsputn(const char * s, std::streamsize n)
	{
		return FDType::write(s, n);
	}

	/** @brief streambuf underflow overload. */
	virtual int underflow()
	{
		if (gptr() >= egptr())
		{
			int numPutback(gptr() - eback());
			if (numPutback > bufPutbackSize_)
			{
				numPutback = bufPutbackSize_;
			}

			std::memmove(buf_+(bufPutbackSize_-numPutback), gptr()-numPutback, numPutback);

			std::streamsize num(FDType::read(buf_+bufPutbackSize_, bufDataSize_));
			if (num <= 0)
			{
				return traits::eof();
			}

			setg(buf_+(bufPutbackSize_-numPutback),	buf_+bufPutbackSize_, buf_+bufPutbackSize_+num);
		}

		return traits::to_int_type(*gptr());
	}
};

/** @brief IO stream for file descriptors. */
template <typename FDType>
class FDTypeStream: public std::iostream
{
public:
	/** Constructor for file descriptor stream. */
	FDTypeStream(int fd, bool closeFd=false)
		:std::iostream(0)
		,buf_(fd, closeFd)
	{
		rdbuf(&buf_);
	}

private:
	FDTypeBuf<FDType> buf_;
};


/** @brief socketpair(2) abstraction. */
class SocketPair
{
private:
	bool const closeFd_;
	int sv_[2];

public:
	SocketPair(bool const & closeFd=true);
	~SocketPair();
	int const & first()  const;
	int const & second() const;
};

}}
#endif
