#include "htlib2.h"
#include "Receiver.h"
#include "Header.h"
#include "Socket.h"
#include "HtLib_.h"

static void
prepareBuffer(HTLIB* o)
{
	/* manage buffer for 100 Continue response */

	int len;
	if ((len=o->_read_index - o->_body_index)>0) {
		LOGDBG("memove %d:{%.20s...} to head\n",
			   o->_body_index, o->_rec_buffer);
		memmove(o->_rec_buffer, o->_rec_buffer + o->_body_index, len);
	}
	o->_header = NULL;
	o->_read_index = len;
	o->_body_index = 0;
	return;
}

static int
findBody(char* buffer, int size)
{
	LOGDBG("findBody(%s, %d)\n", buffer, size);
	BOOL lf = FALSE;
	int i;
	for (i=0; i<size; i++) {
		switch (buffer[i]) {
		case '\r':
			continue;

		case '\n':
			if (lf) {
				buffer[i] = 0;
				if (i>0 && buffer[i-1]=='\r') {
					buffer[i-1] = 0;
				}
				return i+1;
			}
			lf = TRUE;
			break;

		default:
			lf = FALSE;
			break;
		}
	}
	return -1;
}


static BOOL
manageHeaders(HTLIB* o, const HTLIB_Header* header_buffer, USHORT blen,
			  BOOL chkclen, HTLIB_ERROR* err)
{
	/*
	 * Connection:
	 * manage keeping-alive for the next time
	 */
	if (M(Header,HasConnectionClose)(header_buffer, blen)) {
		LOGINFO("CLR KEEP_ALIVE\n");
		FLAG_CLR(FLAG_KEEP_ALIVE, o->_flags);
	} else {
		/* can't be set TRUE,
		   because it may be already set FALSE when the request. */
	}

	/*
	 * Content-Length:
	 */
	if (chkclen) {
		o->_rec_content_length = -1;
		o->_rec_content_transfered = 0;
		int index;
		if ((index=M(Header,Find)(header_buffer, blen, "Content-Length"))
			==-1 ||
			header_buffer[index].value == NULL) {
			if (FLAG_ISSET(FLAG_KEEP_ALIVE, o->_flags)) {
				*err = HTLIB_E_INVALID_SYNTAX;
				LOGERR("Content-Length not found when keep-alive\n");
				return FALSE;
			}
		} else {
			if (sscanf(header_buffer[index].value, "%lld",
					   &o->_rec_content_length)==0) {
				*err = HTLIB_E_INVALID_SYNTAX;
				LOGERR("Content-Length cannot be recognized\n");
				return FALSE;
			}
		}
	} else {
		o->_rec_content_length = 0;
		o->_rec_content_transfered = 0;
	}
	return TRUE;
}

static int
parseHeader(HTLIB* o,
			HTLIB_Header* header_buffer, USHORT blen,
			HTLIB_NEEDS_FUNC needs,	 
			HTLIB_ERROR* err)
{
	LOGINFO("ParseHeader(%d)\n", blen);

	if (o->_header == NULL || o->_body_index==0) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("didn't receive all headers yet\n");
		return -1;
	}
	return M(Header,Parse)(o,
						   header_buffer, blen,
						   needs,
						   err);
}

static void
proceedUntilSpace(HTLIB* o)
{
	int index = 0;
	while (index < o->_read_index && isspace(o->_rec_buffer[index])) {
		index++;
	}
	if (0 < index) {
		memmove(o->_rec_buffer, o->_rec_buffer + index,
				o->_read_index - index);
		o->_read_index -= index;
	}
}

static BOOL
receiveMessage(HTLIB* o,
			   int timeout_millis,
			   HTLIB_Header* header_buffer, USHORT* blen,
			   HTLIB_NEEDS_FUNC needs,
			   HTLIB_ERROR* err)
{
	if (o->_header) {
		/* already receiveMessage returned non-NULL before */
		prepareBuffer(o);
	}
	while (TRUE) {
		/*
		 * First, check if the head-end can be found.
		 * (In case of 1XX, it may be already received.)
		 */
		
		if ((o->_body_index=findBody(o->_rec_buffer, o->_read_index))>0) {

			/* "CR LF CR LF" found */

			/* skip the first line */
			LOGDBG("check 1st line: (len=%d) %s\n",
					o->_read_index,  o->_rec_buffer);
			o->_header = strchr(o->_rec_buffer, '\n');
			if (o->_header == NULL) {
				/* NEVER */
				*err = HTLIB_E_FATAL;
				LOGERR("LF not found(why not?)\n");
				return FALSE;
			}
			*o->_header++ = 0;
			char* p;
			if ((p=strchr(o->_rec_buffer, '\r'))==NULL) {
				*p = 0;
			}

			int blen2;
			if ((blen2=parseHeader(o,
								   header_buffer, *blen,
								   needs,
								   err))==-1) {
				LOGERR("parseHeader failed err=%d\n", *err);
				return FALSE;
			}
			*blen = (USHORT)blen2;

			return TRUE;
		}

		if (o->_rec_buffer_len <= o->_read_index) {
			/* memory cannot be assigned any more */
			break;
		}

		/*
		 * Now try, to receive from network
		 */
		if (M(Socket,Wait)(o, timeout_millis,
						   M(HTLIB,CheckCanceled), err)==FALSE) {
			if (*err == HTLIB_E_TIMEOUT) {
				LOGINFO("Socket_Wait timeout\n");
			} else {
				LOGERR("Socket_Wait failed\n");
			}
			return FALSE;
		}
		int len = o->_rec_buffer_len - o->_read_index;
		switch ((len=M(Socket,Read)(o,
									o->_rec_buffer+o->_read_index,
									len,
									err))) {
		case -1:
			LOGERR("Socket_Read failed err=%d\n", *err);
			return FALSE;

		case 0:
			*err = HTLIB_E_DISCONNECTED;
			LOGERR("Socket disconnected\n", *err);
			return FALSE;

		default:
			o->_read_index += len;
			proceedUntilSpace(o);
			break; /* into the while-loop again */
		}
	} /* while */

	*err = HTLIB_E_OUT_OF_MEMORY;
	LOGERR("ReceiveMessage: outof memory(%d >= %d)\n",
		   o->_read_index, o->_rec_buffer_len);
	return FALSE;
}

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

const char*
M(Receiver,ReceiveRequest)(HTLIB* o,
						   int timeout_millis,
						   const char** method,
						   HTLIB_Header* header_buffer, USHORT* blen,
						   HTLIB_NEEDS_FUNC needs,
						   HTLIB_ERROR* err)
{
	if (expectedBody(o)) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("expected body\n");
		return FALSE;
	}

	if (FLAG_ISSET(FLAG_KEEP_ALIVE, o->_flags)==FALSE) {
		LOGINFO("no more KEEP_ALIVE, quit.\n");
		*err = HTLIB_E_DISCONNECTED;
		return NULL;
	}

	if (receiveMessage(o,
					   timeout_millis,
					   header_buffer, blen,
					   needs,
					   err)==FALSE) {
		LOGERR("ReceiveMessage failed err=%d\n", *err);
		return NULL;
	}

	/* process request-line */

	LOGINFO("request-line: %s\n", o->_rec_buffer);

	/* method */
	char* p = strpbrk(o->_rec_buffer, " ");
	if (p == NULL) {
		*err = HTLIB_E_INVALID_SYNTAX;
		LOGERR("no space after method\n");
		return NULL;
	}
	*p++ = 0;

	/* url */
	while (*p && *p==' ') {
		p++;
	}
	if (*p == 0 || isspace(*p)) {
		/* *p == '\0', '\r', '\n' */
		*err = HTLIB_E_INVALID_SYNTAX;
		LOGERR("no token after method\n");
		return NULL;
	}

	char* url = NULL;
	if (*p == '/') {
		url = p;
	} else if (strncmp(p, "http://", 7)==0) {
		/* absoluteURI */
		p = strpbrk(p+7, " /");
		if (p == NULL || *p == ' ') {
			url = "/";
		} else {
			url = p;
		}
	} else {
		*err = HTLIB_E_INVALID_SYNTAX;
		LOGERR("unrecognized URL %s\n", p);
		return NULL;
	}

	while (*p && isspace(*p)==0) {
		p++;
	}
	if (*p != ' ') {
		/* HTTP0.9 ? */
		*err = HTLIB_E_INVALID_SYNTAX;
		LOGERR("no space after url\n");
		return NULL;
	}
	*p++ = 0;

	/* p == "HTTP/1.1" */
	/* ToDo: */
	
	*method = o->_rec_buffer;
	
	BOOL chkclen = FALSE;
	if (strcmp(*method, "POST")==0 || strcmp(*method, "PUT")==0) {
		chkclen = TRUE;
	}
	if (manageHeaders(o, header_buffer, *blen, chkclen, err)==FALSE) {
		LOGERR("manageHeaders failed err=%d\n", *err);
		return FALSE;
	}

	return url;
}

int
M(Receiver,ReceiveResponse)(HTLIB* o,
						 int timeout_millis,
						 HTLIB_Header* header_buffer, USHORT* blen,
						 HTLIB_NEEDS_FUNC needs,
						 HTLIB_ERROR* err)
{
	if (expectedBody(o)) {
		*err = HTLIB_E_INVALID_STATE;
		LOGERR("expected body\n");
		return FALSE;
	}

	if (receiveMessage(o,
					   timeout_millis,
					   header_buffer, blen,
					   needs,
					   err)==FALSE) {
		LOGERR("ReceiveMessage failed err=%d\n", *err);
		return -1;
	}

	int status;

	LOGINFO("response-line: %.20s...\n", o->_rec_buffer);
	if (sscanf(o->_rec_buffer+sizeof("HTTP/1.1 ")-1, "%d", &status)!=1) {
		*err = HTLIB_E_INVALID_SYNTAX;
		LOGERR("INVALID_SYNTAX\n");
		return -1;
	}

	if ((int)(status/100)==1) {
		prepareBuffer(o);
		return status;
	}

	BOOL chkclen = TRUE;
	if (FLAG_ISSET(FLAG_HEAD_REQUEST, o->_flags)) {
		chkclen = FALSE;
	}
	
	switch (status) {
	case 204:
	case 304:
		chkclen = FALSE;
		break;

	default:
		break;
	}
	if (manageHeaders(o, header_buffer, *blen, chkclen, err)==FALSE) {
		LOGERR("manageHeaders failed err=%d\n", *err);
		return FALSE;
	}
	return status;
}

static void
manageBuffer(HTLIB* o)
{
	if (o->_rec_content_length>=0 &&
		o->_rec_content_length <= o->_rec_content_transfered) {

		LOGINFO("transfering %lld bytes completed.\n",
				o->_rec_content_transfered);

		/* for the next request */
		int restlen = o->_read_index - o->_body_index;
		memmove(o->_rec_buffer,
				o->_rec_buffer + o->_body_index,
				restlen);
		o->_read_index = restlen;
		o->_body_index = 0;
		o->_header = NULL;
	}
}

int
M(Receiver,ReceiveBody)(HTLIB* o,
				  int timeout_millis,
				  char* buffer,
				  USHORT buffer_len,
				  HTLIB_ERROR* err)
{
	if (expectedBody(o)==FALSE) {
		/* no more body */
		manageBuffer(o);
		return 0;
	}

	if (M(HTLIB,CheckCanceled)(o, err)==FALSE) {
		return -1;
	}

	/*
	 * Connection-Length consideration
	 */
	long long body_rest;
	if (o->_rec_content_length !=-1 &&
		(body_rest = o->_rec_content_length
		 - o->_rec_content_transfered) < buffer_len) {
		LOGINFO("buffer_len shortened (%d => %d)\n",
				buffer_len, body_rest);
		buffer_len = body_rest;
		if (buffer_len == 0) {
			/* reached the end */
			manageBuffer(o);
			return 0;
		}
	}
	/*
	 * First, check if some bytes already read.
	 */
	if (o->_body_index < o->_read_index) {
		
		int len = o->_read_index - o->_body_index;
		if (buffer_len < len) {
			len = buffer_len;
		}
		memcpy(buffer, o->_rec_buffer + o->_body_index, len);
		o->_body_index += len;
		o->_rec_content_transfered += len;
		manageBuffer(o);
		return len;
	}

	/*
	 * Now try to read from the socket.
	 */
	{
		int len;

		if (M(Socket,Wait)(o, timeout_millis, M(HTLIB,CheckCanceled),
						   err)==FALSE) {
			return -1;
		}
		
		len = M(Socket,Read)(o, buffer, buffer_len, err);
		if (len>0) {
			o->_rec_content_transfered += len;
		}
		manageBuffer(o);
		return len;
	}
}


