/*
 * $Id$
 *
    Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions
    are met:
    1. Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright
       notice, this list of conditions and the following disclaimer in the
       documentation and/or other materials provided with the distribution.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
    IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
    INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
    NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
    THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <qse/si/sck.h>

#if defined(_WIN32)
#	include <winsock2.h>
#	include <ws2tcpip.h> /* sockaddr_in6 */
#	include <windows.h>
#elif defined(__OS2__)
#	if defined(TCPV40HDRS)
#		define  BSD_SELECT
#	endif
#	include <types.h>
#	include <sys/socket.h>
#	include <netinet/in.h>
#	include <sys/ioctl.h>
#	include <nerrno.h>
#	if defined(TCPV40HDRS)
#		define  USE_SELECT
#		include <sys/select.h>
#	else
#		include <unistd.h>
#	endif
#elif defined(__DOS__)
#	include <tcp.h> /* watt-32 */

#elif defined(HAVE_T_CONNECT) && !defined(HAVE_CONNECT) && defined(HAVE_TIUSER_H)

#	include "../cmn/syscall.h"
#	include <tiuser.h>
#	define USE_TLI

#else
#	include "../cmn/syscall.h"
#	include <sys/socket.h>
#	include <netinet/in.h>
#	if defined(HAVE_NETINET_TCP_H)
#		include <netinet/tcp.h>
#	endif
#	if defined(HAVE_NETINET_SCTP_H)
#		include <netinet/sctp.h>
#	endif
#endif


#if !defined(SHUT_RD)
#	define SHUT_RD 0
#endif

#if !defined(SHUT_WR)
#	define SHUT_WR 1
#endif

#if !defined(SHUT_RDWR)
#	define SHUT_RDWR 2
#endif

QSE_INLINE int qse_is_sck_valid (qse_sck_hnd_t handle)
{
#if defined(_WIN32)
	return handle != QSE_INVALID_SCKHND;

#elif defined(__OS2__)
	return handle >= 0;

#elif defined(__DOS__)
	return handle >= 0;

#elif defined(USE_TLI)
	return handle >= 0;

#else
	return handle >= 0;
#endif
}

qse_sck_hnd_t qse_open_sck (int family, int type, int protocol, int traits)
{
	int x;

#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
	if (traits & QSE_SCK_TRAIT_NONBLOCK) type |= SOCK_NONBLOCK;
	if (traits & QSE_SCK_TRAIT_CLOEXEC) type |= SOCK_CLOEXEC;
open_socket:
#endif
	x = socket(family, type, protocol);
	if (x == -1)
	{
	#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
		if (errno == EINVAL && (type & (SOCK_NONBLOCK | SOCK_CLOEXEC)))
		{
			type &= ~(SOCK_NONBLOCK | SOCK_CLOEXEC);
			goto open_socket;
		}
	#endif
		return -1;
	}
	else
	{
	#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC)
		if (type & (SOCK_NONBLOCK | SOCK_CLOEXEC)) goto done;
	#endif
	}

	if (traits)
	{
		if (((traits & QSE_SCK_TRAIT_CLOEXEC) && qse_set_sck_cloexec(x, 1) <= -1) ||
		    ((traits & QSE_SCK_TRAIT_NONBLOCK) && qse_set_sck_nonblock(x, 1) <= -1))
		{
			close (x);
			return -1;
		}
	}

done:
	return x;
}

QSE_INLINE void qse_close_sck (qse_sck_hnd_t handle)
{
#if defined(_WIN32)
	closesocket (handle);

#elif defined(__OS2__)
	soclose (handle);

#elif defined(__DOS__)
	close_s (handle);

#elif defined(USE_TLI)
	t_close (handle);

#else
	close (handle);
#endif
}

QSE_INLINE void qse_shut_sck (qse_sck_hnd_t handle, qse_shut_sck_how_t how)
{
	static int how_v[] = { SHUT_RD, SHUT_WR, SHUT_RDWR };

#if defined(_WIN32)
	shutdown (handle, how_v[how]);

#elif defined(__OS2__)
	shutdown (handle, how_v[how]);

#elif defined(__DOS__)
	shutdown (handle, how_v[how]);

#elif defined(USE_TLI)
	/* Is this correct? */
	switch (how)
	{
		case QSE_SHUT_SCK_R:
			t_rcvrel (handle);
			break;
		case QSE_SHUT_SCK_W:
			t_sndrel (handle);
			break;
		case QSE_SHUT_SCK_RW:
			t_rcvrel (handle);
			t_sndrel (handle);
			break;
	}

#else
	shutdown (handle, how_v[how]);
#endif
}

int qse_set_sck_nonblock (qse_sck_hnd_t handle, int enabled)
{
#if defined(_WIN32)
	if (ioctlsocket (handle, FIONBIO, &enabled) == SOCKET_ERROR) return -1;
	return 0;

#elif defined(__OS2__)

	if (ioctl (handle, FIONBIO, (char*)&enabled, sizeof(enabled)) <= -1) return -1;
	return 0;

#elif defined(__DOS__)

	if (ioctlsocket (handle, FIONBIO, (char*)&enabled) == SOCKET_ERROR) return -1;
	return 0;

#elif defined(O_NONBLOCK)

	int flag = fcntl(handle, F_GETFL);
	if (flag >= 0) flag = fcntl(handle, F_SETFL, (enabled? (flag | O_NONBLOCK): (flag & ~O_NONBLOCK)));
	if (flag <= -1) return -1;
	return 0;

#else
	return -1;
#endif
}

int qse_set_sck_cloexec (qse_sck_hnd_t handle, int enabled)
{
#if defined(_WIN32)
	return -1;

#elif defined(__OS2__)
	return -1;

#elif defined(__DOS__)
	return -1;

#elif defined(FD_CLOEXEC)
	int flag = fcntl(handle, F_GETFD);
	if (flag >= 0) flag = fcntl(handle, F_SETFD, (enabled? (flag | FD_CLOEXEC): (flag & ~FD_CLOEXEC)));
	if (flag <= -1) return -1;
	return 0;
#else
	return -1;
#endif
}

int qse_init_sck_conn (qse_sck_hnd_t handle, const qse_skad_t* skad)
{
	int n;
#if defined(_WIN32)
	unsigned long cmd;
#elif defined(__OS2__)
	int enabled;
#else
	int saved = 0;
#endif

#if defined(_WIN32)
	/* switch to the non-blocking mode */
	cmd = 1;
	if (ioctlsocket(handle, FIONBIO, &cmd) == SOCKET_ERROR) 
	{
		/* error code in WSAGetLastError() */
		return -1;
	}

	/* attempt to connet */
	n = connect(handle, (struct sockaddr*)skad, qse_skadsize(skad));
	if (n == -1 && WSAGetLastError() != WSAEWOULDBLOCK) 
	{
		/* attempt to restore to the blocking mode upon failure.
		 * there is no guarantee that this was the previous mode. */
		cmd = 0;
		ioctlsocket (handle, FIONBIO, &cmd); 
		return -1;
	}
#elif defined(__OS2__)

	enabled = 1;
	if (ioctl(handle, FIONBIO, (char*)&enabled, sizeof(enabled)) <= -1) return -1;

	/* attempt to connet */
	n = connect (handle, (struct sockaddr*)skad, qse_skadsize(skad));
	if (n == -1 && sock_errno() != EINPROGRESS)
	{
		/* attempt to restore to the blocking mode upon failure.
		 * there is no guarantee that this was the previous mode. */
		enabled = 0;
		ioctl (handle, FIONBIO, (char*)&enabled, sizeof(enabled));
		return -1;
	}

#else

	/* switch to the non-blocking mode if necessary */
	saved = fcntl (handle, F_GETFL, 0);
	if (saved == -1) return -1;
	if (!(saved & O_NONBLOCK) && fcntl(handle, F_SETFL, saved | O_NONBLOCK) == -1) return -1;

	/* attempt to connet */
	do 
	{
		n = connect(handle, (struct sockaddr*)skad, qse_skadsize(skad));
	}
	while (n == -1 && errno == EINTR);

	if (n == -1 && errno != EINPROGRESS) 
	{
		if (!(saved & O_NONBLOCK)) fcntl (handle, F_SETFL, saved); /* restore the flags upon failure */
		return -1;
	}
#endif

	/* the socket still remains in the non-blocking mode */
	return (n == 0)? 1: 0; /* 1: connected, 0: in progress */
}

int qse_fini_sck_conn (qse_sck_hnd_t handle)
{
	int ret;
	qse_sck_len_t len;

	len = (qse_sck_len_t)QSE_SIZEOF (ret);
	if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*)&ret, &len) == -1) return -1;

#if defined(_WIN32)
	if (ret == WSAETIMEDOUT) 
#else
	if (ret == ETIMEDOUT) 
#endif
	{
		return -1; /* failure - timed out */
	}
#if defined(_WIN32)
	else if (ret == WSAEWOULDBLOCK) 
#else
	else if (ret == EINPROGRESS) 
#endif
	{
		return 0; /* in progress */
	}
	else if (ret != 0) 
	{
		errno = ret;
		return -1; /* failure */
	}

	return 1; /* connected */
}

int qse_set_sck_keepalive (qse_sck_hnd_t handle, int enabled, int tcp_keepidle, int tcp_keepintvl, int tcp_keepcnt)
{
	if (setsockopt(handle, SOL_SOCKET, SO_KEEPALIVE, (char*)&enabled, QSE_SIZEOF(enabled)) <= -1) return -1;

	// the following values are just hints. 
	// i don't care about success and failure
#if defined(TCP_KEEPIDLE) && defined(SOL_TCP)
	if (tcp_keepidle > 0) setsockopt (handle, SOL_TCP, TCP_KEEPIDLE, (char*)&tcp_keepidle, QSE_SIZEOF(tcp_keepidle));
#endif
#if defined(TCP_KEEPINTVL) && defined(SOL_TCP)
	if (tcp_keepintvl > 0) setsockopt (handle, SOL_TCP, TCP_KEEPINTVL, (char*)&tcp_keepintvl, QSE_SIZEOF(tcp_keepintvl));
#endif
#if defined(TCP_KEEPCNT) && defined(SOL_TCP)
	if (tcp_keepcnt > 0) setsockopt (handle, SOL_TCP, TCP_KEEPCNT, (char*)&tcp_keepcnt, QSE_SIZEOF(tcp_keepcnt));
#endif
	return 0;
}


#if 0
qse_sck_hnd_t


int qse_sck_open (qse_mmgr_t* mmgr, qse_sck_type_t type)
{
}

void qse_sck_close (qse_sck_t* sck)
{
}

int qse_sck_init (qse_sck_t* sck, qse_mmgr_t* mmgr, qse_sck_type_t type)
{
	int domain, type, proto = 0;

	switch (type)
	{
		case QSE_SCK_TCP4:
			domain = AF_INET;
			type = SOCK_STREAM;
			break;

		case QSE_SCK_TCP6:
			domain = AF_INET6;
			type = SOCK_STREAM;
			break;

		case QSE_SCK_UDP4:
			domain = AF_INET;
			type = SOCK_DGRAM;
			break;

		case QSE_SCK_UDP6:
			domain = AF_INET6;
			type = SOCK_DGRAM;
			break;

		case QSE_SCK_SCTP4:
			domain = AF_INET;
			type = SCOK_SEQPACKET;
			proto = IPPROTO_SCTP;
			break;

		case QSE_SCK_SCTP6:
			domain = AF_INET6;
			type = SCOK_SEQPACKET;
			proto = IPPROTO_SCTP;
			break;

		case QSE_SCK_SCTP4:
			domain = AF_INET;
			type = SCOK_STREAM;
			proto = IPPROTO_SCTP;
			break;

		case QSE_SCK_SCTP6:
			domain = AF_INET6;
			type = SCOK_STREAM;
			proto = IPPROTO_SCTP;
			break;

#if 0
		case QSE_SCK_RAW4:
			domain = AF_INET;
			type = SOCK_RAW;
			break;

		case QSE_SCK_RAW6:
			domain = AF_INET6;
			type = SOCK_RAW;
			break;

		case QSE_SCK_PACKET:
			domain = AF_PACKET;
			type = SOCK_RAW;
			proto = qse_hton16(ETH_P_ALL);
			break;

		case QSE_SCK_PACKET:
			domain = AF_PACKET;
			type = SOCK_DGRAM; /* cooked packet with the link level header removed */
			proto = qse_hton16(ETH_P_ALL);
			break;

		case QSE_SCK_ARP:
			domain = AF_PACKET;
			type = SOCK_RAW;
			proto = qse_hton16(ETH_P_ARP);
			proto = 
#endif
	}

	sck->handle = socket (domain, type, proto);
}

void qse_sck_fini (qse_sck_t* sck)
{
#if defined(_WIN32)
	closesocket (sck->handle);
#elif defined(__OS2__)
	soclose (sck->handle);
#elif defined(__DOS__)
	close_s (sck->handle)
#else
	close (sck->handle);
#endif
}


qse_ssize_t qse_recvsocket ()

qse_ssize_t recvfromsocket ()

qse_ssize_t sendsocket ()
qse_ssize_t sendtosocket ()
#endif



