/* =============================================================================
 *  scpp_socket.hpp
 *  Copyright (c)  2003 - 2013  Nomura Kei
 *  LICENSE :
 *  LGPL (GNU Lesser General General Public License - Version 3,29 June 2007)
 *  http://www.gnu.org/copyleft/lesser.html
 * =============================================================================
 *
 * ソケットを扱うモジュール
 */
#ifndef SCPP_SOCKET_HPP
#define SCPP_SOCKET_HPP

#include <cstring>

#include <scpp_os.hpp>
#include <scpp_exception.hpp>
#include <scpp_errno.hpp>


#if (SCPP_IS_WINDOWS)
	#include <ws2tcpip.h>
	#ifdef _MSV_VER
		#pragma comment(lib, "ws2_32.lib")
	#endif
	typedef SOCKET socket_t;
	#define SHUT_RD		SD_RECEIVE
	#define SHUT_WR		SD_SEND
	#define SHUT_RDWR	SD_BOTH
	#define socket_close(sockfd) closesocket(sockfd)

#else
// ! SCPP_IS_WINDOWS
	#include <sys/socket.h>
	#include <sys/types.h>
	#include <arpa/inet.h>
	#include <netinet/in.h>
	#include <netdb.h>
	#include <stdlib.h>
	#include <unistd.h>
	typedef int socket_t;
	#define INVALID_SOCKET (-1)
	#define SOCKET_ERROR   (-1)
	#define socket_close(sockfd) close(sockfd)

#endif


namespace scpp
{


/**
 * Socket に関するエラーが発生した際に throw される Exception クラスです.
 */
class SocketException : public Exception
{
	public:
		SocketException() throw();
		SocketException(const SocketException& t) throw();
		SocketException(const std::string& msg) throw();
		virtual ~SocketException() throw();

};


/**
 * 抽象ソケットクラス.
 * TCP の場合は, Socket クラスを,
 * UDP の場合は, DatagramSocket クラスを使用してください.
 */
class AbstractSocket
{
	public:
		AbstractSocket(int type, int family = AF_UNSPEC);
		virtual ~AbstractSocket();
		AbstractSocket& operator=(const AbstractSocket& sock);
		virtual const char*    getAddress(char* addr, size_t addrLen)      const;
		virtual int            getPort()                                   const;
		virtual const char*    getLocalAddress(char* addr, size_t addrLen) const;
		virtual int            getLocalPort()                              const;
		virtual const socket_t getSocket()                                 const;
		virtual const int      getSocketType()                             const;
		virtual const int      getSocketFamily()                           const;
		virtual void bind(const char* addr, int port);
		virtual void bind(const char* addr, const char* service) = 0;

	protected:
		virtual void getAddrInfo(addrinfo** result, const char* addr, const char* service, bool isPassive);
		virtual void clone(const AbstractSocket& sock);
		socket_t sockFd;
		int      sockType;
		int      sockFamily;
		sockaddr_storage remoteAddr;
		socklen_t        remoteAddrLen;
		sockaddr_storage localAddr;
		socklen_t        localAddrLen;
		
};


/**
 * TCP ソケットを扱います.
 * 
 * @code
 * 使用例) サーバ側の例
 * Socket  server(AF_INET);				// IPv4 を扱う
 * sock->bind("127.0.0.1", "5000");		// 127.0.0.1:5000 番ポートにバインドする
 * sock->listen(10);					// バックログ 10
 *
 * SocketSet readSet;
 * timeval   timeout;
 * int       ret;
 * ssize_t   recvSize;
 * Socket    client;
 * char      buff[1024];
 *
 * for (;;)
 * {
 *   // select で扱うディスクリプタのセットを初期化
 *   readSet.reset();
 *   readSet.add(server);
 *
 *   // タイムアウト時間設定
 *   timeout.tv_sec  = 0;
 *   timeout.tv_usec = 100000;			// 100ms
 *
 *   ret = Select::select(&readSet, 0, 0, &timeout);
 *   if (ret > 0)
 *   {
 *     if (readSet.isSet(server))
 *     { // 接続された！
 *       server.accept(&client);
 *
 *       // クライアントソケットに対する処理
 *       // 例) データを受信
 *       recvService = client.recv(buff, sizeof(buff));
 *       client.close();
 *     }
 *   }
 * }
 *
 * @endcode
 */
class Socket : public AbstractSocket
{
	public:
		Socket(int family = AF_UNSPEC);
		Socket(const Socket&);
		virtual ~Socket();
		Socket& operator=(const Socket&);
		virtual void  bind(const char* addr, const char* service);
		virtual void   listen(int backlog);
		virtual void   accept(Socket* clientSocket);
		virtual void   connect(const char* addr, int port, const char* loAddr= 0, int loPort = -1);
		virtual void   connect(const char* addr, const char* service, const char* loAddr = 0, const char* loService = 0);
		virtual void   close();
		virtual ssize_t send(const char* buff, size_t size, int flag = 0);
		virtual ssize_t recv(char* buff, size_t size, int flag = 0);
		virtual int     write(const char* buff, size_t size);
		virtual int     read(char* buff, size_t size);

	protected:

	private:
		void bindAndConnect(const char* addr, const char* service, const char* loAddr, const char* loService);
		bool connect(socket_t sock, const char* addr, const char* service);

};


/**
 * UDP ソケットを扱います.
 *
 * @code
 * 使用例) サーバ側の例
 * DatagramSocket  server(AF_INET);		// IPv4 を扱う
 * sock->bind("127.0.0.1", "5000");		// 127.0.0.1:5000 番ポートにバインドする
 *
 * SocketSet readSet;
 * timeval   timeout;
 * int       ret;
 *
 * ssize_t   recvSize;
 * char      buff[1024];
 *
 * for (;;)
 * {
 *   // select で扱うディスクリプタのセットを初期化
 *   readSet.reset();
 *   readSet.add(server);
 *
 *   // タイムアウト時間設定
 *   timeout.tv_sec  = 0;
 *   timeout.tv_usec = 100000;			// 100ms
 *
 *   ret = Select::select(&readSet, 0, 0, &timeout);
 *   if (ret > 0)
 *   {
 *     if (readSet.isSet(server))
 *     { // データを受信した！
 *       // データ受信時の処理
 *     }
 *   }
 * }
 *
 * @endcode
 */
class DatagramSocket : public AbstractSocket
{
	public:
		DatagramSocket(int family = AF_UNSPEC);
		DatagramSocket(const DatagramSocket&);
		virtual ~DatagramSocket();
		DatagramSocket& operator=(const DatagramSocket&);
		virtual void  bind(const char* addr, const char* service);
		virtual ssize_t sendto  (const char* buff, size_t len, int flags, const char* addr    , int portf);
		virtual ssize_t sendto  (const char* buff, size_t len, int flags, const char* addr    , const char* service);
		virtual ssize_t sendto  (const char* buff, size_t len, int flags, const sockaddr* addr, socklen_t addrlen);
		virtual ssize_t recvfrom(      char* buff, size_t len, int flags,
										char* srcAddr, size_t srcAddrLen, char* srcPort, size_t srcPortLen);
		virtual ssize_t recvfrom(      char* buff, size_t len, int flags, sockaddr* srcAddr, socklen_t* addrlen);
		virtual void    close();
		virtual void join(const sockaddr* sa, socklen_t salen, const char* ifname, unsigned int ifindex);
		virtual void leave(const sockaddr* sa, socklen_t salen);
		virtual void setTTL(int val);
	
	protected:

	private:
		void joinIPv4(const sockaddr_in* grp, socklen_t grplen, const char* ifname, unsigned int ifindex);
		void joinIPv6(const sockaddr_in6* grp, socklen_t grplen, const char* ifname, unsigned int ifindex);
		void leaveIPv4(const sockaddr_in* grp, socklen_t grplen);
		void leaveIPv6(const sockaddr_in6* grp, socklen_t grplen);

};


/**
 * Select で用いるための ソケット集合を表します.
 */
class SocketSet
{
	public:
		SocketSet();
		void reset();
		void add(const AbstractSocket& s);
		void add(socket_t sockfd);
		void remove(const AbstractSocket& s);
		void remove(socket_t sockfd);
		int  isSet(const AbstractSocket& s) const;
		int  isSet(socket_t sockfd) const;
		int  selectn() const;
		fd_set* getFdSet();

	private:
		SocketSet(const SocketSet&);
		SocketSet& operator=(const SocketSet&);
		fd_set  fdset;
		int     maxfd;

};


namespace Select
{

int select(SocketSet* readSet, SocketSet* writeSet, SocketSet* errSet, timeval* timeout);

}	// namespace Select




}	// namespace scpp

#endif	// SCPP_SOCKET_HPP

