/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  sc_socket.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <sc_socket.h>
#include <sc_string.h>

/* =====================================================================
 *  プロトタイプ宣言
 * =====================================================================
 */
#if (SC_isWindows)
static void SC_Socket_cleanup(void);
#endif


/**
 * ソケットスタートアップ
 *
 * @return true/false (初期化成功/失敗)
 */
bool SC_Socket_init(void)
{
#if (SC_isWindows)
	static bool isInit = false;
	WSADATA     wsaData;
	int         ret;
	if (isInit)
	{	/* 初期化済みならなにもしない	*/
		return true;
	}
	isInit = true;
	ret    = WSAStartup(MAKEWORD(2,0), &wsaData);
	if (ret != 0)
	{	/* 初期化エラー	*/
		return false;
	}
	atexit(SC_Socket_cleanup);
#endif
	return true;
}


/**
 * ソケットに関するエラー番号を取得します。
 *
 * @return エラー番号
 */
int SC_getSocketError(void)
{
#if (SC_isWindows)
	return WSAGetLastError();
#else
	return SC_getError();
#endif
}


/**
 * 指定されたホスト、サービスにTCPにて接続します。
 *
 * @param host     ホスト名またはIPアドレス
 * @param service  サービス名またはポート番号
 * @return ソケット
 */
socket_t SC_Socket_tcpConnect(const char* host, const char* service)
{
	int    sockfd, ret;
	struct addrinfo hints, *rp, *result;

	memset(&hints, 0x00, sizeof(struct addrinfo));
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	ret = getaddrinfo(host, service, &hints, &result);
	if (ret != 0) { return INVALID_SOCKET; }

	for (rp = result; rp != NULL; rp = rp->ai_next)
	{
		sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sockfd < 0)
		{	/* 生成失敗	*/
			continue;
		}

		ret = connect(sockfd, rp->ai_addr, rp->ai_addrlen);
		if (ret != SOCKET_ERROR)
		{	/* 接続成功	*/
			break;
		}

		/* 接続失敗	*/
		socket_close(sockfd);
	}
	if (rp == NULL)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	freeaddrinfo(result);
	return sockfd;
}


/**
 * 指定されたアドレス、サービスにて待ち受けます。
 *
 * @param host     ホスト名またはIPアドレス
 * @param service  サービス名またはポート番号
 * @param addrlenp アドレス構造体サイズ格納用ポインタ
 * @param backlog  バックログ数
 * @return ソケット
 */
socket_t SC_Socket_tcpListen(
		const char* host, const char* service,
		socklen_t* addrlenp, int backlog)
{
	int             sockfd, ret;
	struct addrinfo hints, *rp, *result;
	const int       on = 1;

	memset(&hints, 0x00, sizeof(struct addrinfo));
	hints.ai_flags    = AI_PASSIVE;
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;

	ret = getaddrinfo(host, service, &hints, &result);
	if (ret != 0) { return INVALID_SOCKET; }

	for (rp = result; rp != NULL; rp = rp->ai_next)
	{
		sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sockfd < 0)
		{	/* 生成失敗	*/
			continue;
		}

		/* bind の前の setsockopt は常に成功のためノーチェック	*/
		setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,
				(const char*) &on, sizeof(on));

		ret = bind(sockfd, rp->ai_addr, rp->ai_addrlen);
		if (ret == 0)
		{	/* bind 成功	*/
			break;
		}

		/* 接続失敗	*/
		socket_close(sockfd);
	}
	if (rp == NULL)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	ret = listen(sockfd, backlog);
	if (ret == SOCKET_ERROR)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	if (addrlenp != NULL)
	{
		*addrlenp = rp->ai_addrlen;
	}
	freeaddrinfo(result);
	return sockfd;
}


/**
 * 指定されたアドレス、サービスにて待ち受けます。
 *
 * @param host     ホスト名またはIPアドレス
 * @param service  サービス名またはポート番号
 * @param saptr    アドレスポインタ
 * @param lenp     アドレス構造体サイズ格納用ポインタ
 * @return ソケット
 */
socket_t SC_Socket_udpClient(const char* host, const char* service,
		struct sockaddr** saptr, socklen_t* lenp)
{
	int             sockfd, ret;
	struct addrinfo hints, *rp, *result;

	memset(&hints, 0x00, sizeof(struct addrinfo));
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	ret = getaddrinfo(host, service, &hints, &result);
	if (ret != 0) { return INVALID_SOCKET; }

	for (rp = result; rp != NULL; rp = rp->ai_next)
	{
		sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sockfd >= 0)
		{	/* 生成成功	*/
			break;
		}
	}
	if (rp == NULL)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	*saptr = (struct sockaddr*) malloc(rp->ai_addrlen);
	if (*saptr == NULL)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	memcpy(*saptr, rp->ai_addr, rp->ai_addrlen);
	*lenp = rp->ai_addrlen;
	freeaddrinfo(result);
	return sockfd;
}


/**
 * 指定されたアドレス、サービスにて待ち受けます。
 *
 * @param host     ホスト名またはIPアドレス
 * @param service  サービス名またはポート番号
 * @param addrlenp アドレス構造体サイズ格納用ポインタ
 * @return ソケット
 */
socket_t SC_Socket_udpServer(
		const char* host, const char* service, socklen_t* addrlenp)
{
	int             sockfd, ret;
	struct addrinfo hints, *rp, *result;

	memset(&hints, 0x00, sizeof(struct addrinfo));
	hints.ai_flags    = AI_PASSIVE;
	hints.ai_family   = AF_UNSPEC;
	hints.ai_socktype = SOCK_DGRAM;

	ret = getaddrinfo(host, service, &hints, &result);
	if (ret != 0) { return INVALID_SOCKET; }

	for (rp = result; rp != NULL; rp = rp->ai_next)
	{
		sockfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
		if (sockfd < 0)
		{	/* 生成失敗		*/
			continue;
		}
		ret = bind(sockfd, rp->ai_addr, rp->ai_addrlen);
		if (ret == 0)
		{	/* bind 成功	*/
			break;
		}
		/* 失敗		*/
		socket_close(sockfd);
	}
	if (rp == NULL)
	{
		freeaddrinfo(result);
		return INVALID_SOCKET;
	}

	if (addrlenp != NULL)
	{
		*addrlenp = rp->ai_addrlen;
	}
	freeaddrinfo(result);
	return sockfd;
}


/**
 * ソケットをクローズする。
 *
 * @param sockfd ソケットディスクリプタ
 */
bool SC_Socket_close(socket_t sockfd)
{
	int  ret;
	int  readSize;
	char buff[SC_SOCKET_TEMPBUFF_SIZE];

	ret = shutdown(sockfd, SHUT_WR);
	if (ret == 0)
	{
		do
		{	/* 届いているデータをすべて読み取る	*/
			readSize = recv(sockfd, buff, sizeof(buff), 0);
		} while (readSize > 0);
	}

	ret = socket_close(sockfd);
	if (ret == 0)
	{
		return true;
	}
	return false;
}



/* =====================================================================
 *  以下、内部でのみ使用する関数
 * =====================================================================
 */
/**
 * ソケットクリーンアップ
 */
#if (SC_isWindows)
static
void SC_Socket_cleanup(void)
{
	WSACleanup();
}
#endif


