/***********************************************************************
 * imap.c - asynchronous streaming imap command queue
 *
 * (C) 2001 by Harald Welte <laforge@gnumonks.org>
 *
 * $Id: imap.c,v 1.7 2001/04/04 13:42:45 laforge Exp $
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */
#include <stdlib.h>
#include "imap.h"

/***********************************************************************
 * BUFFER HANDLING ROUTINES
 ***********************************************************************/

/* allocate a buffer */
buf_t *buf_alloc(int size)
{
	buf_t *buf;

	buf = (buf_t *) malloc(sizeof(buf_t) + size);
	if (!buf)
		return NULL;

	buf->offset = buf->parse = 0;
	buf->size = size;

	return buf;
}

/* free a buffer */
void buf_free(buf_t *buf)
{
	return;
	free(buf->buf);
	free(buf);
}

/* enlarge a buffer */
buf_t *buf_enlarge(buf_t *buf, u_int32_t by)
{
	u_int32_t size;
	buf_t *ret;

	size = sizeof(buf_t) + buf->size + by;
	
	ret = (buf_t *) realloc(buf, size);
	if (!ret)
		return NULL;

	ret->size += by;
	return ret;
}

/* get a pointer to first char of next line, or NULL if there is none */
static char *get_nextl(buf_t *buf)
{
	char *c;
	int f = 0;

	if (buf->parse == 0) {
		/* first byte in buffer, assumed to be start of line */
		buf->parse = 1;
		return buf->buf;
	}

	for (c = buf_parse(buf); c <= buf_cur(buf); c++) {
		if (*c == '\r') {
			f = 1;
			continue;
		}
		if (f == 1) {
			if (*c == '\n')
				f = 2;
			else
				f = 0;
			continue;
		}
		if (f == 2) {
			buf->parse = (c - buf->buf);
			return c;
		}
	}

	/* nothing found, whole buffer parsed */
	buf->parse = buf->offset;
	return NULL;
}

/* alloc one IMAP command */
imap_cmd_t *imap_cmd_alloc(void)
{
	imap_cmd_t *cmd;

	cmd = (imap_cmd_t *) malloc(sizeof(imap_cmd_t));
	/* FIXME */
	memset(cmd, 0, sizeof(*cmd));
	return cmd;
}

/* free one IMAP command */
void imap_cmd_free(imap_cmd_t *cmd)
{
	free(cmd);
}

/* enqueue one command into a selectable queue */
static int _imap_enqueue_command(imap_cmd_t **queue, imap_cmd_t *cmd)
{
	imap_cmd_t *c;

	cmd->next = NULL;

	if (!*queue) {
		*queue = cmd;
	} else {
		c = *queue;
		while (c->next) {
			c = c->next;
		}
		c->next = cmd;
	}

	return 0;
}

/* enqueue one command into the command queue */
int imap_enqueue_command(imap_t *imap, imap_cmd_t *cmd)
{
	cmd->imap = imap;
	cmd->next = NULL;
	return _imap_enqueue_command(&(imap->command_queue), cmd);
}

/* get one command from the queue */
static imap_cmd_t *imap_dequeue_command(imap_cmd_t **queue)
{
	imap_cmd_t *c;

	c = *queue;
	if (!c)
		return NULL;

	*queue = c->next;
	c->next = NULL;

	return c;
}

/* dequeue one particular command */
static int imap_delete_selective(imap_cmd_t **queue, imap_cmd_t *cmd)
{
	imap_cmd_t *c;
	imap_cmd_t *prev = NULL;

	for (c = *queue; c; c = c->next) {
		if (c == cmd) {
			if (!prev)
				*queue = c->next;
			else
				prev->next = c->next;
			imap_cmd_free(c);
			return 1;
		}
		prev = c;
	}
	return 0;
}

/* one complete message has been received */
static int handle_response(imap_t *imap, buf_t *buf)
{
	int ret;
	imap_cmd_t *c;
	IMAP_DEBUGP("handle_response\n");

	/* find out the action connected to this Tag */
	for (c = imap->active_queue; c; c = c->next) {
		if (imap->receiver_tag == c->tag) {
			/* call the callback function */
			if (c->callback)
				ret = (c->callback) (buf, c);
			imap_delete_selective(&(imap->active_queue), c);
			return ret;
		}

	}
	return 0;
}

/* dequeue one command, return pointer to null-terminated buffer 
 * the caller has to make sure it is at least IMAP_MAX_CMD_SIZE space */
int imap_sender(imap_t *imap, char *buf)
{
	imap_cmd_t *cmd;

	/* record the action connected to this Tag */
	cmd = imap_dequeue_command(&(imap->command_queue));
	if (!cmd)
		return 1;

	cmd->tag = imap->sender_tag++;

	/* enqueue in the waiting queue */
	_imap_enqueue_command(&(imap->active_queue), cmd);

	/* send the command */
	snprintf(buf, IMAP_CMD_MAXSIZE, "0%4.4d %s\r\n",
		 cmd->tag, cmd->command);

	IMAP_DEBUGP("imap_sender: %s\n", buf);

	return 0;
}

/* Main receiver routine
 * called if select indicates there's something to read */
int imap_receiver(imap_t *imap, buf_t *buf)
{
	char *cmd = buf->buf;
	char *nl, *tmp, *p, *q;
	unsigned int bytecnt;
	unsigned int movecnt;

	/* we've received something ! */
	while (nl = get_nextl(buf)) {
		IMAP_DEBUGP("get_nextl(buf): %.50s\n", nl);
		IMAP_DEBUGP("\t%lu-%lu\n", imap->receiver_skip, buf->parse);
		if (imap->receiver_skip && 
		    (buf->parse < imap->receiver_skip)) {
			IMAP_DEBUGP("skipping, skip=%lu, current parse: %lu\n",
			       imap->receiver_skip, buf->parse);
		} else if (imap->receiver_lastline == 1) {
			imap->receiver_lastline = 0;

			handle_response(imap, buf);

			imap->receiver_tag = 0;
			imap->receiver_skip = 0;

			/* move remainder of buffer to top */
			movecnt = buf_cur(buf) - nl;
			memmove(buf->buf, nl, movecnt);
			buf->offset = buf->offset - (nl - buf->buf);
			buf->parse = 0;

			memset(buf_cur(buf), 0, buf_space(buf));
			IMAP_DEBUGP("buf_after_move: ofs = %d, %s\n",
				    buf->offset, buf->buf);
		} else if (nl >= buf_cur(buf)) {
			/* empty line, skip */
			IMAP_DEBUGP("empty line, skip\n");
		} else if (*nl == '*') {
			/* part of a multiline response */
			IMAP_DEBUGP("part of multipart\n");
			if ((p = strchr(nl, '\n')) && (q = strchr(nl, '{'))
			    && (q < p)) {
				IMAP_DEBUGP("found { at %p (before %p)",
				       q, p);
				/* we found a '{' in this line */
				if ((bytecnt = strtoul(q+1, &p, 10)) &&
				    *p == '}') {
					/* we've now parsed a lenght */
					imap->receiver_skip = 
						(p + bytecnt) - buf->buf;
				}
			}
		} else if (*nl == '+') {
			/* whatever */
		} else if (*nl == '0') {
			/* tagged response == last line */
			imap->receiver_tag = strtoul(nl, NULL, 10);

			IMAP_DEBUGP(">> tagged response: %lu\n",
				    imap->receiver_tag);

			imap->receiver_lastline = 1;
		}
	}
}

int imap_commands_pending(imap_t *imap)
{
	if (imap->command_queue)
		return 1;

	return 0;
}

int imap_replies_pending(imap_t *imap)
{
	if (imap->active_queue)
		return 1;
	return 0;
}
