#include "htlib2.h"
#include "HtLib_.h"
#include "Sender.h"
#include "Socket.h"
#include "Header.h"
#include "Date.h"

static const char*
getHttpPath(const char* url, const char** host, int* hostlen, HTLIB_ERROR* err)
{
	if (strncmp(url, "http://", 7)!=0) {
		LOGERR("not HTTP: %s\n", url);
		*err = HTLIB_E_INVALID_ARG;
		return FALSE;
	}
	if (host) {
		*host = url+7;
	}
	const char* p = strchr(url+7, '/');
	if (p) {
		if (hostlen) {
			*hostlen = p - (url+7);
		}
		return p;
	} else {
		if (hostlen) {
			*hostlen = strlen(url+7);
		}
		return "/";
	}
}

static BOOL
getSockAddr(HTLIB* o, const char* host, const char* service,
			struct sockaddr_storage* ss, socklen_t* sslen,
			HTLIB_ERROR* err)
{
	struct addrinfo* info;
	if (M(Socket,GetAddrInfo)(o, host, service, o->hints, &info, err)==FALSE) {
		return FALSE;
	}

	struct addrinfo* ai = info;
	while (ai) {
		if (ai->ai_socktype == SOCK_STREAM) {
			memcpy(ss, ai->ai_addr, ai->ai_addrlen);
			*sslen = ai->ai_addrlen;
			break;
		}
		ai = ai->ai_next;
	}

	M(Socket,FreeAddrInfo)(o, info);
	if (ai == NULL) {
		*err = HTLIB_E_INVALID_STATE;
		return FALSE;
	}
	return TRUE;
}


static BOOL
connectToHost(HTLIB* o, const char* host, const char* service,
			  HTLIB_ERROR* err)
{
	struct sockaddr_storage ss;
	socklen_t sslen;
	
	if (getSockAddr(o, host, service, &ss, &sslen, err)==FALSE) {
		LOGERR("getSockAddr failed err=%d\n", *err);
		return FALSE;
	}
	if (M(Sender,Open)(o, (struct sockaddr*)&ss, sslen, err)==FALSE) {
		LOGERR("Sender_Open failed err=%d\n", *err);
		return FALSE;
	}
	return TRUE;
}

BOOL
M(Sender,Open)(HTLIB_HANDLE o,
		   const struct sockaddr* addr,
		   socklen_t addr_len,
		   HTLIB_ERROR* err)
{
	M(Sender,Close)(o);
	if (M(Socket, Open)(o, addr->sa_family, SOCK_STREAM, 0, err)==FALSE) {
		LOGERR("socket failed errno=%d\n", o->system_errno);
		return FALSE;
	}
	if (M(Socket, Connect)(o, addr, addr_len, err)==FALSE) {
		LOGERR("Socket_Connect failed errno=%d\n", o->system_errno);
		goto err1;
	}
	M(HTLIB,Reset)(o);
	return TRUE;

err1:
	M(Socket,Close)(o);
	return FALSE;
}	

void
M(Sender,Close)(HTLIB_HANDLE o)
{
	M(Socket,Close(o));
}

static BOOL
checkAndWrite(HTLIB_HANDLE o, int len, HTLIB_ERROR* err)
{
	if (o->_send_buffer_len <= len) {
		*err = HTLIB_E_OUT_OF_MEMORY;
		LOGERR("would overflow: %.*s\n", o->_send_buffer_len, o->_send_buffer);
		return FALSE;
	}
	if (M(Socket,Write)(o, o->_send_buffer, len, err)==FALSE) {
		LOGERR("Socket_Write failed err=%d\n", *err);
		return FALSE;
	}
	return TRUE;
}

static BOOL
sendMessage(HTLIB_HANDLE o,
			int timeout_millis,
			const HTLIB_Header* headers,
			HTLIB_USHORT header_len,
			const char* body,
			long long body_len,
			HTLIB_ERROR* err)
{
	int len;

	/* "headers" */
	if (headers && 0<header_len) {
		if ((len = M(Header,Serialize)(headers, header_len,
									   o->_send_buffer, o->_send_buffer_len,
									   err))==-1) {
			return FALSE;
		}
		if (M(Socket,Write)(o, o->_send_buffer, len, err)==FALSE) {
			return FALSE;
		}
	}

	/* Content-Length: */
	if (0 <= body_len) {
		/* if 1XX, no "Content-Length" */
		len = snprintf(o->_send_buffer, o->_send_buffer_len,
					   "Content-Length: %lld\r\n", body_len);

		o->_send_content_length = body_len;
		o->_send_content_transfered = 0;

		if (checkAndWrite(o, len, err)==FALSE) {
			return FALSE;
		}
	
		/* manage keep-alive */
		if (M(Header,HasConnectionClose)(headers, header_len)) {
			LOGINFO("CLR KEEP_ALIVE\n");
			FLAG_CLR(FLAG_KEEP_ALIVE, o->_flags);

		} else if (FLAG_ISSET(FLAG_KEEP_ALIVE, o->_flags)==FALSE) {
			/* no keep-alive, but no Connection: close */
			if (M(Socket,Write)(o, "Connection: close\r\n", -1, err)==FALSE) {
				return FALSE;
			}
		}
	}

	/* header-body separator */
	if (M(Socket,Write)(o, "\r\n", 2, err)==FALSE) {
		return FALSE;
	}


	/* for body */
	if (body!=NULL && 0<body_len) {
		if (M(Socket,Write)(o, body, body_len, err)==FALSE) {
			return FALSE;
		}
		o->_send_content_transfered = body_len;
	}

	return TRUE;
}

static BOOL
expectedBody(HTLIB_HANDLE o)
{
	if (o->_send_content_length<=0) {
		return FALSE;
	}
	if (o->_send_content_length <= o->_send_content_transfered) {
		return FALSE;
	}
	return TRUE;
}

BOOL
M(Sender,SendResponse)(HTLIB_HANDLE o,
				   int timeout_millis,
				   int status, const char* msg,
				  const HTLIB_Header* headers,
				  HTLIB_USHORT header_len,
				  const char* body,
				  HTLIB_ULONGLONG body_len,
				   HTLIB_ERROR* err)
{
	int len;

	if (expectedBody(o)) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("expected body\n");
		return FALSE;
	}
	/* status */
	if (msg == NULL) {
		msg = M(Header,GetHttpStatusMessage)(status);
		if (msg == NULL) {
			msg = "(unrecognized status)";
		}
	}
	/* status line */
	len = snprintf(o->_send_buffer, o->_send_buffer_len,
				   "HTTP/1.1 %3d %s\r\n", status, msg);
	if (checkAndWrite(o, len, err)==FALSE) {
		return FALSE;
	}

	/* "Date:" */
	char buf32[32];
	M(Date,Set)(buf32, NULL);
	len = snprintf(o->_send_buffer, o->_send_buffer_len,
				   "Date: %s\r\n", buf32);
	if (checkAndWrite(o, len, err)==FALSE) {
		return FALSE;
	}

	/* "Server:" */
	if (o->agent_or_server_name != NULL) {
		len = snprintf(o->_send_buffer, o->_send_buffer_len,
					   "Server: %s\n", o->agent_or_server_name);
		if (checkAndWrite(o, len, err)==FALSE) {
			return FALSE;
		}
	}

	long long blen = body_len;
	if ((int)(status / 100)==1) {
		/* 1XX */
		blen = -1;
		body = NULL;
	}
	if (sendMessage(o, timeout_millis,
					headers, header_len, body, blen,
					err)==FALSE) {
		return FALSE;
	}
	return TRUE;
}

/*
 * calculating inside:
 * - Host:
 * - Content-Length:
 */
HTLIB_BOOL
M(Sender,SendRequest)(HTLIB* o,
					 int timeout_millis,
					 const char* method,
					 const char* url,
					 const HTLIB_Header* headers,
					 USHORT header_len,
					 const char* body,
					 ULONGLONG body_len,
					 HTLIB_ERROR* err)
{
	/* sockaddr */
	const char* host;
	int hostlen;
	const char* path;

	if (expectedBody(o)) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("expected body\n");
		return FALSE;
	}

	if ((path=getHttpPath(url, &host, &hostlen, err))==NULL) {
		return FALSE;
	}

	if (o->soc != -1 && FLAG_ISSET(FLAG_KEEP_ALIVE, o->_flags)==FALSE) {
		LOGINFO("reset socket\n");
		M(HTLIB,Close)(o);
	}
	if (o->soc == -1) {
		snprintf(o->_send_buffer, o->_send_buffer_len, "%.*s", hostlen, host);

		char* p;
		if ((p=strchr(o->_send_buffer, ':'))!=NULL) {
			*p++ = 0;
		} else {
			p = "http";
		}
		if (connectToHost(o, o->_send_buffer, p, err)==FALSE) {
			return FALSE;
		}
	}
	
	if (M(Socket,Write)(o, method, -1, err)==FALSE) {
		return FALSE;
	}
	if (M(Socket,Write)(o, " ", 1, err)==FALSE) {
		return FALSE;
	}
	if (M(Socket,Write)(o, path, -1, err)==FALSE) {
		return FALSE;
	}
	if (M(Socket,Write)(o, " ", 1, err)==FALSE) {
		return FALSE;
	}
	if (M(Socket,Write)(o, "HTTP/1.1\r\n", -1, err)==FALSE) {
		return FALSE;
	}

	int len;
	
	/* "Host:" */
	len = snprintf(o->_send_buffer, o->_send_buffer_len,
				   "Host: %.*s\r\n", hostlen, host);
	if (checkAndWrite(o, len, err)==FALSE) {
		return FALSE;
	}
	/* "User-Agent:" */
	if (o->agent_or_server_name != NULL) {
		len = snprintf(o->_send_buffer, o->_send_buffer_len,
					   "User-Agent: %s\n", o->agent_or_server_name);
		if (checkAndWrite(o, len, err)==FALSE) {
			return FALSE;
		}
	}

	if (sendMessage(o, timeout_millis,
						headers, header_len, body, body_len,
						err)==FALSE) {
		return FALSE;
	}
	
	if (strcmp(method, "HEAD")==0) {
		FLAG_SET(FLAG_HEAD_REQUEST, o->_flags);
	} else {
		FLAG_CLR(FLAG_HEAD_REQUEST, o->_flags);
	}
	return TRUE;
}

BOOL
M(Sender,SendBody)(HTLIB_HANDLE o,
			   int timeout_millis,
			   const char* body,
			   HTLIB_USHORT body_len,
			   HTLIB_ERROR* err)
{
	if (expectedBody(o)==FALSE) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("expected request/response\n");
		return FALSE;
	}
	/*
	 * Connection-Length consideration
	 */
	long long body_rest;
	if ((body_rest = o->_send_content_length
		 - o->_send_content_transfered) < body_len) {
		/* would overflow */
		LOGERR("too much body (%d < %d)\n",
			   (int)body_rest, body_len);
		*err = HTLIB_E_INVALID_ARG;
		return FALSE;
	}
	if (M(Socket,Write)(o, body, body_len, err)==FALSE) {
		return FALSE;
	}
	o->_send_content_transfered += body_len;
	return TRUE;
}

