#include "htlib2.h"
#include "Socket.h"
#include "Thread.h"

BOOL
M(Socket,Open)(HTLIB* o,int domain, int type, int protocol, HTLIB_ERROR* err)
{
	if ((o->soc = socket(domain, type, protocol))==-1) {
		o->system_errno = errno;
		*err = HTLIB_E_SYSTEM;
		LOGERR("socket failed err=%d\n", errno);
		return FALSE;
	}
	return TRUE;
}
void
M(Socket,Close)(HTLIB* o)
{
	if (o->soc != -1) {
		close(o->soc);
		o->soc = -1;
	}
}

BOOL
M(Socket,GetAddrInfo)(HTLIB* o,
					  const char* host, const char* service,
					  const struct addrinfo* hints,
					  struct addrinfo** info, HTLIB_ERROR* err)
{
	if ((o->system_errno=getaddrinfo(host, service, hints, info))!=0) {
		LOGERR("getaddrinfo returns: %s\n", gai_strerror(o->system_errno));
		*err = HTLIB_E_SYSTEM;
		return FALSE;
	}
	return TRUE;
}
void
M(Socket,FreeAddrInfo)(HTLIB* o, struct addrinfo* info)
{
	freeaddrinfo(info);
}

BOOL
M(Socket,Connect)(HTLIB* o,
				  const struct sockaddr* addr, socklen_t len,
				  HTLIB_ERROR* err)
{
	LOGINFO("Socket_Connect(family=%d, s_addr=%x, sin_port=%hu) len=%d\n",
			addr->sa_family,
			ntohl(((struct sockaddr_in*)addr)->sin_addr.s_addr),
			ntohs(((struct sockaddr_in*)addr)->sin_port),
			len);
	
	if (connect(o->soc, addr, len)==-1) {
		o->system_errno = errno;
		*err = HTLIB_E_SYSTEM;
		LOGERR("connect failed errno=%d\n", o->system_errno);
		return FALSE;
	}
	return TRUE;
}

BOOL
M(Socket,Write)(HTLIB* o, const char* data, int len, HTLIB_ERROR* err)
{
	if (len<0) {
		len = strlen(data);
	}
	LOGDBG("{%.*s}", len, data);
	while (len>0) {
		int l = len;
		if (l>1000) {
			l = 1000;
		}
		if ((l=send(o->soc, data, l, MSG_NOSIGNAL))==-1) {
			o->system_errno = errno;
			*err = HTLIB_E_SYSTEM;
			LOGERR("SYSTEM errno=%d\n", o->system_errno);
			return FALSE;
		}
		len -= l;
		data += l;
	}
	return TRUE;
}

int
M(Socket,Read)(HTLIB* o, char* buffer, int buffer_len, HTLIB_ERROR* err)
{
	int len;
	if ((len=recv(o->soc, buffer, buffer_len, 0))==-1) {
		o->system_errno = errno;
		*err = HTLIB_E_SYSTEM;
		LOGERR("recv failed errno=%d\n", o->system_errno);
		return -1;
	}
	return len;
}

#define TIMEOUT_INTERVAL 100

BOOL
M(Socket,Wait)(HTLIB* o, int timeout_millis,
			   BOOL
			   (*checkCanceled)(HTLIB* o, HTLIB_ERROR* err),
			   HTLIB_ERROR* err)
{
	struct timeval tv;
	fd_set readfs;

	if (checkCanceled(o, err)==FALSE) {
		return FALSE;
	}

	if (timeout_millis < 0) {
		if (FLAG_ISSET(FLAG_REPORTED_NO_SELECT, o->_flags)==FALSE) {
			FLAG_SET(FLAG_REPORTED_NO_SELECT, o->_flags);
			LOGINFO("no select (-1), so may cause blocking\n");
		}
		return TRUE;
	}
	do {
		int t = TIMEOUT_INTERVAL;
		if (timeout_millis<TIMEOUT_INTERVAL) {
			t = timeout_millis;
		}

		tv.tv_sec = t / 1000;
		tv.tv_usec = (t%1000) * 1000;
	
		FD_ZERO(&readfs);
		FD_SET(o->soc, &readfs);

		switch (select(o->soc+1, &readfs, NULL, NULL, &tv)) {
		case 0:
			if (checkCanceled(o, err)==FALSE) {
				return FALSE;
			}
			break;

		case -1:
			o->system_errno = errno;
			*err = HTLIB_E_SYSTEM;
			LOGERR("SYSTEM errno=%d\n", o->system_errno);
			return FALSE;

		default:
			if (FD_ISSET(o->soc, &readfs)) {
				return TRUE;
			}
			*err = HTLIB_E_INVALID_STATE;
			return FALSE;
		}

		timeout_millis -= TIMEOUT_INTERVAL;

	} while (timeout_millis>0);

	*err = HTLIB_E_TIMEOUT;
	return FALSE;
}

