
#include "config.h"

#include <stdio.h>
#include <string.h>

#include "camel-imapp-engine.h"
#include "camel-imapp-stream.h"
#include "camel-imapp-utils.h"
#include "camel-imapp-exception.h"

#include <camel/camel-folder-summary.h>
#include <camel/camel-stream-mem.h>
#include <camel/camel-stream-null.h>
#include <camel/camel-data-wrapper.h>
#include <camel/camel-sasl.h>

#include <ctype.h>

#define e(x) 
#define c(x)			/* command build debug */

static void imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap);
static void imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic);

struct _handler {
	CamelIMAPPEngineFunc func;
	void *data;
	char name[1];
};

static void
class_init(CamelIMAPPEngineClass *ieclass)
{
	ieclass->tagprefix = 'A';

	camel_object_class_add_event((CamelObjectClass *)ieclass, "status", NULL);
}

static void
object_init(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass)
{
	ie->handlers = g_hash_table_new(g_str_hash, g_str_equal);
	e_dlist_init(&ie->active);
	e_dlist_init(&ie->queue);
	e_dlist_init(&ie->done);

	ie->tagprefix = ieclass->tagprefix;
	ieclass->tagprefix++;
	if (ieclass->tagprefix > 'Z')
		ieclass->tagprefix = 'A';
	ie->tagprefix = 'A';

	ie->state = IMAP_ENGINE_DISCONNECT;
}

static void
handler_free(void *key, void *mem, void *data)
{
	g_free(mem);
}

static void
object_finalise(CamelIMAPPEngine *ie, CamelIMAPPEngineClass *ieclass)
{
	/* FIXME: need to free the commands ... */
	while (camel_imapp_engine_iterate(ie, NULL) > 0)
		;

	g_hash_table_foreach(ie->handlers, (GHFunc)handler_free, NULL);
	g_hash_table_destroy(ie->handlers);
}

CamelType
camel_imapp_engine_get_type (void)
{
	static CamelType type = CAMEL_INVALID_TYPE;

	if (type == CAMEL_INVALID_TYPE) {
		type = camel_type_register (
			camel_object_get_type (),
			"CamelIMAPPEngine",
			sizeof (CamelIMAPPEngine),
			sizeof (CamelIMAPPEngineClass),
			(CamelObjectClassInitFunc) class_init,
			NULL,
			(CamelObjectInitFunc) object_init,
			(CamelObjectFinalizeFunc) object_finalise);
	}
	
	return type;
}

/* FIXME: check this, just taken from old code, not rfc */
struct {
	char *name;
	guint32 flag;
} capa_table[] = {
	{ "IMAP4", IMAP_CAPABILITY_IMAP4 },
	{ "IMAP4REV1", IMAP_CAPABILITY_IMAP4REV1 },
	{ "STATUS",  IMAP_CAPABILITY_STATUS } ,
	{ "NAMESPACE", IMAP_CAPABILITY_NAMESPACE },
	{ "UIDPLUS",  IMAP_CAPABILITY_UIDPLUS },
	{ "LITERAL+", IMAP_CAPABILITY_LITERALPLUS },
	{ "STARTTLS", IMAP_CAPABILITY_STARTTLS },
};


/*
capability_data ::= "CAPABILITY" SPACE [1#capability SPACE] "IMAP4rev1"
                    [SPACE 1#capability]
                    ;; IMAP4rev1 servers which offer RFC 1730
                    ;; compatibility MUST list "IMAP4" as the first
                    ;; capability.
*/
static int resp_capability(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	int tok, len, i;
	unsigned char *token, *p, c;

	/* FIXME: handle auth types */

	printf("got capability response:\n");
	while (1) {
		tok = camel_imapp_stream_token(ie->stream, &token, &len);
		switch(tok) {
		case IMAP_TOK_TOKEN:
			p = token;
			while ((c = *p))
				*p++ = toupper(c);
		case IMAP_TOK_INT:
			printf(" cap: '%s'\n", token);
			for (i=0;i<(int)(sizeof(capa_table)/sizeof(capa_table[0]));i++)
				if (strcmp(token, capa_table[i].name))
					ie->capa |= capa_table[i].flag;
			break;
		case '\n':
			return 0;
		case IMAP_TOK_ERROR:
		case IMAP_TOK_PROTOCOL:
			camel_imapp_engine_skip(ie);
			return -1;
		default:
			printf("Unknown Response token %02x '%c'\n", tok, isprint(tok)?tok:'.');
		}
	} while (tok != '\n');

	return 0;
}

/* expunge command, id is expunged seq number */
/* message_data    ::= nz_number SPACE ("EXPUNGE" /
   ("FETCH" SPACE msg_att)) */
static int resp_expunge(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	printf("message expunged: %d\n", id);

	return camel_imapp_engine_skip(ie);
}

static int resp_flags(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	guint32 flags;

	imap_parse_flags(ie->stream, &flags);

	printf("flags: %08x\n", flags);

	return camel_imapp_engine_skip(ie);
}

/* exists count */
static int resp_exists(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	printf("messages exist: %d\n", id);

	if (ie->select_response)
		ie->select_response->exists = id;

	return camel_imapp_engine_skip(ie);
}

static int resp_recent(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	printf("messages recent: %d\n", id);

	if (ie->select_response)
		ie->select_response->recent = id;

	return camel_imapp_engine_skip(ie);
}

static int resp_fetch(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	struct _fetch_info *finfo;

	finfo = imap_parse_fetch(ie->stream);
	imap_dump_fetch(finfo);
	imap_free_fetch(finfo);

	return camel_imapp_engine_skip(ie);
}

#if 0
static int resp_list(CamelIMAPPEngine *ie, guint32 id, void *data)
{
	struct _list_info *linfo;

	linfo = imap_parse_list(ie->stream);
	printf("list:  '%s' (%c)\n", linfo->name, linfo->separator);
	imap_free_list(linfo);

	return camel_imapp_engine_skip(ie);
}
#endif

CamelIMAPPEngine *
camel_imapp_engine_new(CamelIMAPPStream *stream)
{
	CamelIMAPPEngine * volatile engine;

	engine = CAMEL_IMAPP_ENGINE (camel_object_new (CAMEL_IMAPP_ENGINE_TYPE));
	engine->stream = stream;
	camel_object_ref((CamelObject *)stream);

	camel_imapp_engine_add_handler(engine, "CAPABILITY", resp_capability, engine);

	/* mailbox_data */
	camel_imapp_engine_add_handler(engine, "FLAGS", (CamelIMAPPEngineFunc)resp_flags, engine);
	camel_imapp_engine_add_handler(engine, "EXISTS", (CamelIMAPPEngineFunc)resp_exists, engine);
	camel_imapp_engine_add_handler(engine, "RECENT", (CamelIMAPPEngineFunc)resp_recent, engine);

#if 0
	camel_imapp_engine_add_handler(engine, "LIST", (CamelIMAPPEngineFunc)resp_list, engine);
	camel_imapp_engine_add_handler(engine, "LSUB", (CamelIMAPPEngineFunc)resp_list, engine);
#endif
	/* message_data */
	camel_imapp_engine_add_handler(engine, "EXPUNGE", (CamelIMAPPEngineFunc)resp_expunge, engine);
	camel_imapp_engine_add_handler(engine, "FETCH", (CamelIMAPPEngineFunc)resp_fetch, engine);

	/* TODO: move this to a driver:connect call? */
	CAMEL_TRY {
		unsigned char *token;
		unsigned int len;
		int tok;

		tok = camel_imapp_stream_token(stream, &token, &len);
		if (tok == '*') {
			struct _status_info *sinfo = imap_parse_status(stream);

			switch (sinfo->result) {
			case IMAP_OK:
				engine->state = IMAP_ENGINE_CONNECT;
				printf("Server connected ok: %s\n", sinfo->text);
				break;
			case IMAP_PREAUTH:
				printf("pre-authenticated ...\n");
				engine->state = IMAP_ENGINE_AUTH;
				break;
			default:
				imap_free_status(sinfo);
				camel_exception_throw(1, "Server refused connection: %s", sinfo->text);
				break;
			}
			imap_free_status(sinfo);
		} else {
			engine->state = IMAP_ENGINE_CONNECT;
			printf("unknwon server greeting, ignored\n");
			camel_imapp_engine_skip(engine);
		}
		camel_imapp_engine_capabilities(engine);
	} CAMEL_CATCH(ex) {
		printf("connection failed: %s\n", ex->desc);
		camel_object_unref((CamelObject *)engine);
		engine = NULL;
	} CAMEL_DONE;

	return engine;
}

void
camel_imapp_engine_add_handler(CamelIMAPPEngine *imap, const char *response, CamelIMAPPEngineFunc func, void *data)
{
	struct _handler *h;
	const unsigned char *p;
	unsigned char *o, c;
	
	h = g_malloc0(sizeof(*h) + strlen(response));
	h->func = func;
	h->data = data;

	p = response;
	o = h->name;
	while ((c = *p++))
		*o++ = toupper(c);
	*o = 0;

	g_hash_table_insert(imap->handlers, h->name, h);
}

int
camel_imapp_engine_capabilities(CamelIMAPPEngine *ie)
{
	CamelIMAPPCommand *ic;

	/* reset capabilities */
	ie->capa = 0;

	ic = camel_imapp_engine_command_new(ie, "CAPABILITY", NULL, "CAPABILITY");
	camel_imapp_engine_command_queue(ie, ic);
	while (camel_imapp_engine_iterate(ie, ic)>0)
		;
	camel_imapp_engine_command_free(ie, ic);

	return 0;
}

/* skip the rest of the line of tokens */
int
camel_imapp_engine_skip(CamelIMAPPEngine *imap)
{
	int tok;
	unsigned char *token;
	unsigned int len;

	do {
		tok = camel_imapp_stream_token(imap->stream, &token, &len);
		if (tok == IMAP_TOK_LITERAL) {
			camel_imapp_stream_set_literal(imap->stream, len);
			while ((tok = camel_imapp_stream_getl(imap->stream, &token, &len)) > 0) {
				printf("Skip literal data '%.*s'\n", (int)len, token);
			}
		}
	} while (tok != '\n' && tok >= 0);

	if (tok < 0)
		return -1;

	return 0;
}

/* handle any untagged responses */
static int
iterate_untagged(CamelIMAPPEngine *imap)
{
	unsigned int id, len;
	unsigned char *token, *p, c;
	int tok;
	struct _handler *h;
	struct _status_info *sinfo;
	
	e(printf("got untagged response\n"));
	id = 0;
	tok = camel_imapp_stream_token(imap->stream, &token, &len);
	if (tok == IMAP_TOK_INT) {
		id = strtoul(token, NULL, 10);
		tok = camel_imapp_stream_token(imap->stream, &token, &len);
	}

	if (tok == '\n')
		camel_exception_throw(1, "truncated server response");

	e(printf("Have token '%s' id %d\n", token, id));
	p = token;
	while ((c = *p))
		*p++ = toupper(c);

	/* first, check for generic unsolicited response */
	h = g_hash_table_lookup(imap->handlers, token);
	if (h) {
		tok = h->func(imap, id, h->data);
		if (tok < 0)
			return tok;
		return 1;
	}

	/* TODO: apart from bye/preauth, these could be callbacks/events? */

	/* now, check for status responses */
	switch (imap_tokenise(token, len)) {
	case IMAP_BYE:
	case IMAP_OK:
	case IMAP_NO:
	case IMAP_BAD:
	case IMAP_PREAUTH:
		/* TODO: validate which ones of these can happen as unsolicited responses */
		/* TODO: handle bye/preauth differently */
		/* FIXME: free sinfo */
		camel_imapp_stream_ungettoken(imap->stream, tok, token, len);
		sinfo = imap_parse_status(imap->stream);
		camel_object_trigger_event(imap, "status", sinfo);
		imap_free_status(sinfo);
#if 0
		switch(sinfo->condition) {
		case IMAP_READ_WRITE:
			printf("folder is read-write\n");
			break;
		case IMAP_READ_ONLY:
			printf("folder is read-only\n");
			break;
		case IMAP_UIDVALIDITY:
			if (imap->select_response)
				imap->select_response->uidvalidity = sinfo->u.uidvalidity;
			break;
#if 0	
			/* not defined yet ... */
		case IMAP_UIDNEXT:
			printf("got uidnext for folder: %d\n", sinfo->u.uidnext);
			break;
#endif	
		case IMAP_UNSEEN:
			if (imap->select_response)
				imap->select_response->unseen = sinfo->u.unseen;
			break;
		case IMAP_PERMANENTFLAGS:
			if (imap->select_response)
				imap->select_response->permanentflags = sinfo->u.permanentflags;
			break;
		case IMAP_ALERT:
			printf("ALERT!: %s\n", sinfo->text);
			break;
		case IMAP_PARSE:
			printf("PARSE: %s\n", sinfo->text);
			break;
		default:
			break;
		}
#endif
		break;
	default:
		printf("unknown token: %s\n", token);
		camel_imapp_engine_skip(imap);
		/* unknown response, just ignore it */
	}

	return 1;
}

/* handle any continuation requests
   either data continuations, or auth continuation */
static int
iterate_continuation(CamelIMAPPEngine *imap)
{
	CamelIMAPPCommand *ic;
	CamelIMAPPCommandPart *cp;
	
	printf("got continuation response\n");

	ic = imap->literal;
	imap->literal = NULL;
	if (ic == NULL) {
		camel_imapp_engine_skip(imap);
		printf("got continuation response with no outstanding continuation requests?\n");
		return 1;
	}

	printf("got continuation response for data\n");
	cp = ic->current;
	switch(cp->type & CAMEL_IMAPP_COMMAND_MASK) {
	case CAMEL_IMAPP_COMMAND_DATAWRAPPER:
		printf("writing data wrapper to literal\n");
		camel_data_wrapper_write_to_stream((CamelDataWrapper *)cp->ob, (CamelStream *)imap->stream);
		break;
	case CAMEL_IMAPP_COMMAND_STREAM:
		printf("writing stream to literal\n");
		camel_stream_write_to_stream((CamelStream *)cp->ob, (CamelStream *)imap->stream);
		break;
	case CAMEL_IMAPP_COMMAND_AUTH: {
		CamelException *ex = camel_exception_new();
		char *resp;
		unsigned char *token;
		int tok, len;
		
		tok = camel_imapp_stream_token(imap->stream, &token, &len);
		resp = camel_sasl_challenge_base64((CamelSasl *)cp->ob, token, ex);
		if (camel_exception_is_set(ex))
			camel_exception_throw_ex(ex);
		camel_exception_free(ex);
		
		printf("got auth continuation, feeding token '%s' back to auth mech\n", resp);
		
		camel_stream_write((CamelStream *)imap->stream, resp, strlen(resp));
		
		/* we want to keep getting called until we get a status reponse from the server
		   ignore what sasl tells us */
		imap->literal = ic;
		
		break; }
	default:
		/* should we just ignore? */
		camel_exception_throw(1, "continuation response for non-continuation request");
	}
	
	camel_imapp_engine_skip(imap);
	
	cp = cp->next;
	if (cp->next) {
		ic->current = cp;
		printf("next part of command \"A%05u: %s\"\n", ic->tag, cp->data);
		camel_stream_printf((CamelStream *)imap->stream, "%s\r\n", cp->data);
		if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) {
			imap->literal = ic;
		} else {
			g_assert(cp->next->next == NULL);
		}
	} else {
		printf("%p: queueing continuation\n", ic);
		camel_stream_printf((CamelStream *)imap->stream, "\r\n");
	}
	
	if (imap->literal == NULL) {
		ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue);
		if (ic) {
			printf("found outstanding op, queueing\n");
			camel_imapp_engine_command_queue(imap, ic);
		}
	}

	return 1;
}

/* handle a completion line */
static int
iterate_completion(CamelIMAPPEngine *imap, unsigned char *token)
{
	CamelIMAPPCommand *ic;
	unsigned int tag;

	if (token[0] != imap->tagprefix)
		camel_exception_throw(1, "Server sent unexpected response: %s", token);

	tag = strtoul(token+1, NULL, 10);
	ic = camel_imapp_engine_command_find_tag(imap, tag);
	if (ic) {
		printf("Got completion response for command %05u '%s'\n", ic->tag, ic->name);
		printf("%p: removing command from qwueue, we were at '%s'\n", ic, ic->current->data);
		printf("%p: removing command\n", ic);
		e_dlist_remove((EDListNode *)ic);
		e_dlist_addtail(&imap->done, (EDListNode *)ic);
		if (imap->literal == ic)
			imap->literal = NULL;
		ic->status = imap_parse_status(imap->stream);
		printf("got response code: %s\n", ic->status->text);

		/* TODO: remove this stuff and use a completion handler? */
		/* TODO: handle 'SELECT' command cleanup here */
		/* FIXME: have this use tokeniser, have this handle close/logout/select etc as well */
		/* ok response from login/authenticate, then we're in happy land */
		if ((!strcmp(ic->name, "LOGIN") || !strcmp(ic->name, "AUTHENTICATE"))
		    && ic->status->result == IMAP_OK)
			imap->state = IMAP_ENGINE_AUTH;

		if (ic->complete)
			ic->complete(imap, ic, ic->complete_data);
	} else {
		camel_exception_throw(1, "got response tag unexpectedly: %s", token);
	}
	
	if (imap->literal != NULL) {
		printf("Warning: continuation command '%s' finished with outstanding continuation\n", imap->literal->name);
		ic = imap->literal;
				/* set the command complete with a failure code? */
		e_dlist_remove((EDListNode *)ic);
		e_dlist_addtail(&imap->done, (EDListNode *)ic);
		imap->literal = NULL;
	}
	
	ic = (CamelIMAPPCommand *)e_dlist_remhead(&imap->queue);
	if (ic) {
		printf("found outstanding op, queueing\n");
		camel_imapp_engine_command_queue(imap, ic);
	}
	
	return 1;
}


/* Do work if there's any to do */
int
camel_imapp_engine_iterate(CamelIMAPPEngine *imap, CamelIMAPPCommand *icwait)
/* throws IO,PARSE exception */
{
	unsigned int len;
	unsigned char *token;
	int tok;

	if ((icwait && icwait->status != NULL) || e_dlist_empty(&imap->active))
		return 0;

	/* handle exceptions here? */

	/* lock here? */

	tok = camel_imapp_stream_token(imap->stream, &token, &len);
	if (tok == '*')
		iterate_untagged(imap);
	else if (tok == IMAP_TOK_TOKEN)
		iterate_completion(imap, token);
	else if (tok == '+')
		iterate_continuation(imap);
	else
		camel_exception_throw(1, "unexpected server response: %s", token);

	if (e_dlist_empty(&imap->active))
		return 0;

	return 1;
}

CamelIMAPPCommand *
camel_imapp_engine_command_new(CamelIMAPPEngine *imap, const char *name, const char *select, const char *fmt, ...)
{
	CamelIMAPPCommand *ic;
	va_list ap;

	ic = g_malloc0(sizeof(*ic));
	ic->tag = imap->tag++;
	ic->name = name;
	ic->mem = (CamelStreamMem *)camel_stream_mem_new();
	ic->select = g_strdup(select);
	e_dlist_init(&ic->parts);

	if (fmt && fmt[0]) {
		va_start(ap, fmt);
		imap_engine_command_addv(imap, ic, fmt, ap);
		va_end(ap);
	}

	return ic;
}

void
camel_imapp_engine_command_add(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, ...)
{
	va_list ap;

	g_assert(ic->mem);	/* gets reset on queue */

	if (fmt && fmt[0]) {
		va_start(ap, fmt);
		imap_engine_command_addv(imap, ic, fmt, ap);
		va_end(ap);
	}
}

void
camel_imapp_engine_command_complete(CamelIMAPPEngine *imap, struct _CamelIMAPPCommand *ic, CamelIMAPPCommandFunc func, void *data)
{
	ic->complete = func;
	ic->complete_data = data;
}

/* FIXME: make imap command's refcounted */
void
camel_imapp_engine_command_free (CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
{
	CamelIMAPPCommandPart *cp, *cn;

	if (ic == NULL)
		return;

	/* validity check - we cant' free items still in any queue ... */
	/* maybe we should just have another queue to keep them? */
	{
		CamelIMAPPCommand *iw;
		int found = 0;

		iw = (CamelIMAPPCommand *)imap->active.head;
		while (iw->next) {
			if (iw == ic) {
				found = 1;
				g_warning("command '%s' still in active queue", iw->name);
				break;
			}
			iw = iw->next;
		}
		iw = (CamelIMAPPCommand *)imap->queue.head;
		while (iw->next) {
			if (iw == ic) {
				found = 1;
				g_warning("command '%s' still in waiting queue", iw->name);
				break;
			}
			iw = iw->next;
		}
		iw = (CamelIMAPPCommand *)imap->done.head;
		while (iw->next) {
			if (iw == ic) {
				found = 1;
				break;
			}
			iw = iw->next;
		}
		if (!found) {
			g_warning("command '%s' not found anywhere", ic->name);
			abort();
		}
	}

	e_dlist_remove((EDListNode *)ic);

	if (ic->mem)
		camel_object_unref((CamelObject *)ic->mem);
	imap_free_status(ic->status);
	g_free(ic->select);

	cp = (CamelIMAPPCommandPart *)ic->parts.head;
	cn = cp->next;
	while (cn) {
		g_free(cp->data);
		if (cp->ob)
			camel_object_unref(cp->ob);
		g_free(cp);
		cp = cn;
		cn = cn->next;
	}

	g_free(ic);
}

/* FIXME: error handling */
void
camel_imapp_engine_command_queue(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
{
	CamelIMAPPCommandPart *cp;

	if (ic->mem)
		imap_engine_command_complete(imap, ic);

	/* FIXME: remove select stuff */

	/* see if we need to pre-queue a select command to select the right folder first */
	if (ic->select && (imap->last_select == NULL || strcmp(ic->select, imap->last_select) != 0)) {
		CamelIMAPPCommand *select;
		
		/* of course ... we can't do anything like store/search if we have to select
		   first, because it'll mess up all the sequence numbers ... hrm ... bugger */

		select = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", ic->select);
		g_free(imap->last_select);
		imap->last_select = g_strdup(ic->select);
		camel_imapp_engine_command_queue(imap, select);
		/* how does it get freed? handle inside engine? */
	}
	
	/* first, check if command can be sent yet ... queue if not */
	if (imap->literal != NULL) {
		printf("%p: queueing while literal active\n", ic);
		e_dlist_addtail(&imap->queue, (EDListNode *)ic);
		return;
	}

	cp = (CamelIMAPPCommandPart *)ic->parts.head;
	g_assert(cp);
	ic->current = cp;

	/* how to handle exceptions here? */

	printf("queueing command \"%c%05u %s\"\n", imap->tagprefix, ic->tag, cp->data);
	camel_stream_printf((CamelStream *)imap->stream, "%c%05u %s\r\n", imap->tagprefix, ic->tag, cp->data);

	if (cp->type & CAMEL_IMAPP_COMMAND_CONTINUATION) {
		printf("%p: active literal\n", ic);
		g_assert(cp->next);
		imap->literal = ic;
		e_dlist_addtail(&imap->active, (EDListNode *)ic);
	} else {
		printf("%p: active non-literal\n", ic);
		g_assert(cp->next && cp->next->next == NULL);
		e_dlist_addtail(&imap->active, (EDListNode *)ic);
	}
}

CamelIMAPPCommand *
camel_imapp_engine_command_find (CamelIMAPPEngine *imap, const char *name)
{
	CamelIMAPPCommand *ic, *in;

	ic = imap->literal;
	if (ic && strcmp(ic->name, name) == 0)
		return ic;

	/* first, try active */
	ic = (CamelIMAPPCommand *)imap->active.head;
	in = ic->next;
	while (in) {
		if (strcmp(ic->name, name) == 0)
			return ic;
		ic = in;
		in = in->next;
	}

	return NULL;
}

CamelIMAPPCommand *
camel_imapp_engine_command_find_tag(CamelIMAPPEngine *imap, unsigned int tag)
{
	CamelIMAPPCommand *ic, *in;

	ic = imap->literal;
	if (ic && ic->tag == tag)
		return ic;

	ic = (CamelIMAPPCommand *)imap->active.head;
	in = ic->next;
	while (in) {
		if (ic->tag == tag)
			return ic;
		ic = in;
		in = in->next;
	}

	return NULL;
}

/* ********************************************************************** */

CamelIMAPPSelectResponse *
camel_imapp_engine_select(CamelIMAPPEngine *imap, const char *name)
{
	CamelIMAPPSelectResponse * volatile resp;
	CamelIMAPPCommand * volatile ic = NULL;

	resp = g_malloc0(sizeof(*resp));
	imap->select_response = resp;

	CAMEL_TRY {
		ic = camel_imapp_engine_command_new(imap, "SELECT", NULL, "SELECT %s", name);
		camel_imapp_engine_command_queue(imap, ic);
		while (camel_imapp_engine_iterate(imap, ic) > 0)
			;

		if (ic->status->result != IMAP_OK)
			camel_exception_throw(1, "select failed: %s", ic->status->text);
		resp->status = ic->status;
		ic->status = NULL;
	} CAMEL_CATCH (e) {
		camel_imapp_engine_command_free(imap, ic);
		camel_imapp_engine_select_free(imap, resp);
		imap->select_response = NULL;
		camel_exception_throw_ex(e);
	} CAMEL_DONE;

	camel_imapp_engine_command_free(imap, ic);
	imap->select_response = NULL;

	return resp;
}

void
camel_imapp_engine_select_free(CamelIMAPPEngine *imap, CamelIMAPPSelectResponse *select)
{
	if (select) {
		imap_free_status(select->status);
		g_free(select);
	}
}

/* ********************************************************************** */

static void
imap_engine_command_add_part(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, camel_imapp_command_part_t type, CamelObject *ob)
{
	CamelIMAPPCommandPart *cp;
	CamelStreamNull *null;
	unsigned int ob_size = 0;
	
	switch(type & CAMEL_IMAPP_COMMAND_MASK) {
	case CAMEL_IMAPP_COMMAND_DATAWRAPPER:
	case CAMEL_IMAPP_COMMAND_STREAM:
		null = (CamelStreamNull *)camel_stream_null_new();
		if ( (type & CAMEL_IMAPP_COMMAND_MASK) == CAMEL_IMAPP_COMMAND_DATAWRAPPER) {
			camel_data_wrapper_write_to_stream((CamelDataWrapper *)ob, (CamelStream *)null);
		} else {
			camel_stream_reset((CamelStream *)ob);
			camel_stream_write_to_stream((CamelStream *)ob, (CamelStream *)null);
			camel_stream_reset((CamelStream *)ob);
		}
		type |= CAMEL_IMAPP_COMMAND_CONTINUATION;
		camel_object_ref(ob);
		ob_size = null->written;
		camel_object_unref((CamelObject *)null);
		camel_stream_printf((CamelStream *)ic->mem, "{%u}", ob_size);
		break;
	case CAMEL_IMAPP_COMMAND_AUTH:
		/* we presume we'll need to get additional data only if we're not authenticated yet */
		camel_object_ref(ob);
		camel_stream_printf((CamelStream *)ic->mem, "%s", ((CamelSasl *)ob)->mech);
		if (!camel_sasl_authenticated((CamelSasl *)ob))
			type |= CAMEL_IMAPP_COMMAND_CONTINUATION;
		break;
	default:
		ob_size = 0;
	}

	cp = g_malloc0(sizeof(*cp));
	cp->type = type;
	cp->ob_size = ob_size;
	cp->ob = ob;
	cp->data_size = ic->mem->buffer->len;
	cp->data = g_malloc(cp->data_size+1);
	memcpy(cp->data, ic->mem->buffer->data, cp->data_size);
	cp->data[cp->data_size] = 0;

	camel_stream_reset((CamelStream *)ic->mem);
	/* FIXME: hackish? */
	g_byte_array_set_size(ic->mem->buffer, 0);

	e_dlist_addtail(&ic->parts, (EDListNode *)cp);
}

#if c(!)0
static int len(EDList *list)
{
	int count = 0;
	EDListNode *n = list->head;

	while (n->next) {
		n = n->next;
		count++;
	}
	return count;
}
#endif

static void
imap_engine_command_complete(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic)
{
	c(printf("completing command buffer is [%d] '%.*s'\n", ic->mem->buffer->len, (int)ic->mem->buffer->len, ic->mem->buffer->data));
	c(printf("command has %d parts\n", len(&ic->parts)));
	if (ic->mem->buffer->len > 0)
		imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_SIMPLE, NULL);
	
	c(printf("command has %d parts\n", len(&ic->parts)));

	camel_object_unref((CamelObject *)ic->mem);
	ic->mem = NULL;
}

static void
imap_engine_command_addv(CamelIMAPPEngine *imap, CamelIMAPPCommand *ic, const char *fmt, va_list ap)
{
	const unsigned char *p, *ps, *start;
	unsigned char c;
	unsigned int width;
	char ch;
	int llong;
	int left;
	int fill;
	int zero;
	char *s;
	int d;
	long int l;
	guint32 f;
	CamelStream *S;
	CamelDataWrapper *D;
	CamelSasl *A;
	char buffer[16];

	c(printf("adding command, fmt = '%s'\n", fmt));

	p = fmt;
	ps = fmt;
	while ( ( c = *p++ ) ) {
		switch(c) {
		case '%':
			if (*p == '%') {
				camel_stream_write((CamelStream *)ic->mem, ps, p-ps);
				p++;
				ps = p;
			} else {
				camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1);
				start = p-1;
				width = 0;
				left = FALSE;
				fill = FALSE;
				zero = FALSE;
				llong = FALSE;

				do {
					c = *p++;
					if (c == '0')
						zero = TRUE;
					else if ( c== '-')
						left = TRUE;
					else
						break;
				} while (c);

				do {
					if (isdigit(c))
						width = width * 10 + (c-'0');
					else
						break;
				} while ((c = *p++));

				if (c == 'l') {
					llong = TRUE;
					c = *p++;
				}

				switch(c) {
				case 'A': /* auth object - sasl auth, treat as special kind of continuation */
					A = va_arg(ap, CamelSasl *);
					imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_AUTH, (CamelObject *)A);
					break;
				case 'S': /* stream */
					S = va_arg(ap, CamelStream *);
					c(printf("got stream '%p'\n", S));
					imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_STREAM, (CamelObject *)S);
					break;
				case 'D': /* datawrapper */
					D = va_arg(ap, CamelDataWrapper *);
					c(printf("got data wrapper '%p'\n", D));
					imap_engine_command_add_part(imap, ic, CAMEL_IMAPP_COMMAND_DATAWRAPPER, (CamelObject *)D);
					break;
				case 't': /* token */
					s = va_arg(ap, char *);
					camel_stream_write((CamelStream *)ic->mem, s, strlen(s));
					break;
				case 's': /* simple string */
					s = va_arg(ap, char *);
					c(printf("got string '%s'\n", s));
					/* FIXME: escpae chars, convert to literal or literal+, etc */
					camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s);
					break;
				case 'f': /* imap folder name */
					s = va_arg(ap, char *);
					c(printf("got folder '%s'\n", s));
					/* FIXME: encode folder name */
					/* FIXME: namespace? */
					camel_stream_printf((CamelStream *)ic->mem, "\"%s\"", s?s:"");
					break;
				case 'F': /* IMAP flags set */
					f = va_arg(ap, guint32);
					imap_write_flags((CamelStream *)ic->mem, f);
					break;
				case 'c':
					d = va_arg(ap, int);
					ch = d;
					camel_stream_write((CamelStream *)ic->mem, &ch, 1);
					break;
				case 'd': /* int/unsigned */
				case 'u':
					if (llong) {
						l = va_arg(ap, long int);
						c(printf("got long int '%d'\n", (int)l));
						memcpy(buffer, start, p-start);
						buffer[p-start] = 0;
						camel_stream_printf((CamelStream *)ic->mem, buffer, l);
					} else {
						d = va_arg(ap, int);
						c(printf("got int '%d'\n", d));
						memcpy(buffer, start, p-start);
						buffer[p-start] = 0;
						camel_stream_printf((CamelStream *)ic->mem, buffer, d);
					}
					break;
				}

				ps = p;
			}
			break;
		case '\\':	/* only for \\ really, we dont support \n\r etc at all */
			c = *p;
			if (c) {
				g_assert(c == '\\');
				camel_stream_write((CamelStream *)ic->mem, ps, p-ps);
				p++;
				ps = p;
			}
		}
	}

	camel_stream_write((CamelStream *)ic->mem, ps, p-ps-1);
}


/* here temporarily while its experimental */


#ifdef ENABLE_THREADS
#include <pthread.h>

static pthread_key_t handler_key = 0;

void camel_exception_setup(void)
{
	pthread_key_create(&handler_key, NULL);
}

#else
/* this is per-thread in threaded mode */
static struct _CamelExceptionEnv *handler = NULL;

void camel_exception_setup(void)
{
}
#endif

void
camel_exception_try(struct _CamelExceptionEnv *env)
{
#ifdef ENABLE_THREADS
	struct _CamelExceptionEnv *handler;

	handler = pthread_getspecific(handler_key);
#endif
	env->parent = handler;
	handler = env;
	env->ex = NULL;

#ifdef ENABLE_THREADS
	pthread_setspecific(handler_key, handler);
#endif
}

void
camel_exception_throw_ex(CamelException *ex)
{
	struct _CamelExceptionEnv *env;
#ifdef ENABLE_THREADS
	struct _CamelExceptionEnv *handler;

	handler = pthread_getspecific(handler_key);
#endif
	printf("throwing exception '%s'\n", ex->desc);

	env = handler;
	if (env != NULL) {
		env->ex = ex;
		handler = env->parent;
#ifdef ENABLE_THREADS
		pthread_setspecific(handler_key, handler);
#endif
		longjmp(env->env, ex->id);
	} else {
		g_warning("Uncaught exception: %s\n", ex->desc);
		/* we just crash and burn, this is a code problem */
		/* we dont use g_assert_not_reached() since its not a noreturn function */
		abort();
	}
}

void
camel_exception_throw(int id, char *fmt, ...)
{
	CamelException *ex;
	va_list ap;

	ex = camel_exception_new();
	ex->id = id;
	va_start(ap, fmt);
	ex->desc = g_strdup_vprintf(fmt, ap);
	va_end(ap);

	camel_exception_throw_ex(ex);
}

void
camel_exception_drop(struct _CamelExceptionEnv *env)
{
#ifdef ENABLE_THREADS
	pthread_setspecific(handler_key, env->parent);
#else
	handler = env->parent;
#endif
}

void
camel_exception_done(struct _CamelExceptionEnv *env)
{
#ifdef ENABLE_THREADS
	pthread_setspecific(handler_key, env->parent);
#else
	handler = env->parent;
#endif
	if (env->ex != NULL) {
		camel_exception_free(env->ex);
	}
}
