/*
 * message.c
 * 
 * Copyright (C) 2006-2007  Josef Kufner <jk@myserver.cz>
 * 
 * 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.
 *
 */

#include <glib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "message.h"

#define DEBUG 0
#include "debug.h"


static struct {
	MESSAGE_HANDLER handler;
	char *desc;
} message_target[MESSAGE_MAX_TARGETS];


#ifndef USE_GLIB_SLICES
#define USE_GLIB_SLICES		1	/* 1 or 0 -- use g_slice_* functions from glib ? */
#endif

#if USE_GLIB_SLICES
#	define message_alloc_mem()		g_slice_new(MESSAGE)
#	define message_free_mem(m)		g_slice_free(MESSAGE, (m))
#	define message_item_alloc_mem()		g_slice_new(struct s_message_item)
#	define message_item_free_mem(m)		g_slice_free(struct s_message_item, (m))
#else
#	define message_alloc_mem()		((MESSAGE *) malloc(sizeof(MESSAGE)))
#	define message_free_mem(m)		free((m))
#	define message_item_alloc_mem()		(struct s_message_item *) malloc(sizeof(struct s_message_item))
#	define message_item_free_mem(m)		free((m))
#endif


void message_init(void)
{
	DBG_MSG(1, "");

	/* prepare handlers table */
	memset(message_target, 0, sizeof(message_target));
	
	return;
}


MESSAGE *message_create(char src, char dest)
{
	MESSAGE *msg;
	int i;
	DBG_MSG(4, "");

	msg = message_alloc_mem();
	if (msg) {
		msg->src = src;
		msg->dest = dest;
		msg->token = NULL;
		msg->state = 0;
		msg->cur_part = 0;

		for (i = 0; i < MESSAGE_PART_MAX; i++) {
			msg->part[i].list = NULL;
			msg->part[i].p_list_end = NULL;
			msg->part[i].p_list_next = NULL;
			msg->part[i].length = 0;
		}
	}
	return msg;
}


void message_destroy_part(MESSAGE *msg, MESSAGE_PART_ID part_id)
{
	struct s_message_item *p, *d;

	DBG_MSG(4, "");
	assert(msg != NULL);
	
	if (part_id >= MESSAGE_PART_MAX || part_id < 0)
		return;
	
	/* free memory */
	p = msg->part[part_id].list;
	while (p) {
		d = p;
		p = p->next;

		if (d->type == MESSAGE_STRING && d->value.string)
			free(d->value.string);
		message_item_free_mem(d);
	}

	/* reinitialize part */
	msg->part[part_id].list = NULL;
	msg->part[part_id].p_list_end = NULL;
	msg->part[part_id].p_list_next = NULL;
	msg->part[part_id].length = 0;
}

void message_destroy(MESSAGE *msg)
{
	int i;
	
	DBG_MSG(4, "");
	assert(msg != NULL);
	
	for (i = 0; i < MESSAGE_PART_MAX; i++) {
		message_destroy_part(msg, i);
	}
	if (msg->token)
		g_string_free(msg->token, TRUE);
	message_free_mem(msg);
	DBG_MSG(9, "done");
}


void message_edit(MESSAGE *msg, char src, char dest) {
	assert(msg != NULL);
	if (src) {
		msg->src = src;
		DBG_MSG(4, "src = %c", src);
	}
	if (dest) {
		msg->dest = dest;
		DBG_MSG(4, "dest = %c", dest);
	}
}


int message_append(MESSAGE *msg, MESSAGE_PART_ID part, MESSAGE_ITEM_TYPE first_item, ...)
{
	va_list ap;
	MESSAGE_ITEM_TYPE t;
	struct s_message_item *item;
	int count = 0;
	char *arg;
	MESSAGE_PART *mp;
	
	DBG_MSG(4, "");
	assert(msg != NULL);

	if (part >= MESSAGE_PART_MAX || part < 0) {
		DBG_MSG(1, "Wrong part!");
		return 0;
	}

	mp = &(msg->part[part]);
	
	va_start(ap, first_item);
	while ((t = count ? va_arg(ap, MESSAGE_ITEM_TYPE) : first_item)) {
		item = message_item_alloc_mem();
		if (!item)
			return 0;

		if (mp->p_list_end)
			mp->p_list_end->next = item;
		else
			mp->list = item;
		mp->p_list_end = item;
		item->next = NULL;
		item->type = t;
		mp->length++;
		
		switch (t) {
			case MESSAGE_INTEGER:
				item->value.integer = va_arg(ap, long);
				DBG_MSG(8, "integer %ld", item->value.integer);
				break;

			case MESSAGE_STRING:
				arg = va_arg(ap, char*);
				item->value.string = strdup(arg ? arg : "");
				DBG_MSG(8, "string \"%s\"", item->value.string);
				break;

			default:
				DBG_MSG(1, "unknown type!");
				return 0;
		}
		count++;
	}
	va_end(ap);
	
	DBG_MSG(4, "%d item%s appended.", count, count > 1 ? "s":"");
	return 1;
}


int message_get_from(MESSAGE *msg, int part, int get_next, MESSAGE_ITEM_TYPE first_item, ...)
{
	va_list ap;
	MESSAGE_ITEM_TYPE t;
	struct s_message_item **p = NULL, *pl = NULL;
	MESSAGE_PART *mp;
	int first_loop = 1;
	
	DBG_MSG(4, "");
	assert(msg != NULL);
	assert(part < MESSAGE_PART_MAX);
	
	mp = &(msg->part[part]);
	
	if (get_next)
		p = &(mp->p_list_next);
	else
		p = &pl;
	
	va_start(ap, first_item);
	for (*p = get_next ? mp->p_list_next : mp->list;; *p = (*p)->next) {
		if (first_loop) {
			first_loop = 0;
			t = first_item;
		} else {
			t = va_arg(ap, MESSAGE_ITEM_TYPE);
		}
		if (t == MESSAGE_END) {
			va_end(ap);
			DBG_MSG(8, "End of message.")
			return 1;
		}
		if (!*p) {
			va_end(ap);
			DBG_MSG(1, "Unexpected end of list.")
			return 0;
		}

		if (t != MESSAGE_SKIP && t != (*p)->type) {
			va_end(ap);
			DBG_MSG(1, "type mismatch.");
			return 0;
		}
		
		switch (t) {
			case MESSAGE_INTEGER:
				*(va_arg(ap, long*)) = (*p)->value.integer;
				DBG_MSG(8, "integer %ld", (*p)->value.integer);
				break;

			case MESSAGE_STRING:
				*(va_arg(ap, char**)) = (*p)->value.string;
				DBG_MSG(8, "string \"%s\"", (*p)->value.string);
				break;

			case MESSAGE_SKIP:
				DBG_MSG(8, "skip");
				break;
				
			default:
				DBG_MSG(1, "unknown type!");
				va_end(ap);
				return 0;
		}
	}
}


void message_get_rewind(MESSAGE *msg, MESSAGE_PART_ID part)
{
	DBG_MSG(4, "");
	assert(msg != NULL);
	assert(part < MESSAGE_PART_MAX);

	msg->part[part].p_list_next = msg->part[part].list;
}


MESSAGE *message_from_string(char src, char dest, const char *str, char **p)
{
	MESSAGE *msg = NULL;
	int ret = 0;
	
	DBG_MSG(3, "str = \"%s\"", p && *p ? *p : str);
	assert(str != NULL);
	assert(p != NULL);

	if (*p == NULL)
		*p = (char *) str;
	
	/* build message */
	while (**p && (ret = message_from_stream(src, dest, *((*p)++), &msg)) != 1)
		;

	/* remove incomplete message, if there is no input remaining */
	if (!**p && ret != 1 && msg) {
		message_destroy(msg);
		msg = NULL;
	}

	return msg;
}

	
int message_from_stream(char src, char dest, char ch, MESSAGE **msg)
{
	enum {
		S_none = 0,
		S_start,
		S_next,
		S_string,
		S_string_end,
		S_backslash,
		S_integer_zero,
		S_integer_first,
		S_integer_hex,
		S_integer_oct,
		S_integer
	};

	assert(msg != NULL);

	DBG_MSG(9, "ch = '%c', state = %d, token = \"%s\"",
			ch, *msg ? (*msg)->state:S_start, *msg && (*msg)->token ? (*msg)->token->str:"");

	/* destroy old message */
	if (*msg && (*msg)->state == S_none) {
		message_destroy(*msg);
		*msg = NULL;
	}
	
	/* pre-create message to store token & state */
	if (!*msg) {
		*msg = message_create(src, dest);
		(*msg)->state = S_start;
		(*msg)->token = g_string_sized_new(256);
	}
	
	switch((*msg)->state) {
		case S_start:
			if (ch == '(') {
				(*msg)->state = S_next;
			} else if (!isspace(ch)) {
				goto failure;
			}
			break;

		case S_next:
			if (isdigit(ch) || ch == '-' || ch == '+') {
				if (ch == '0')
					(*msg)->state = S_integer_zero;
				else if (isdigit(ch))
					(*msg)->state = S_integer;
				else
					(*msg)->state = S_integer_first;
				g_string_assign((*msg)->token, "");
				g_string_append_c((*msg)->token, ch);
			} else if (ch == '"') {
				(*msg)->state = S_string;
				g_string_assign((*msg)->token, "");
			} else if (ch == '|') {
				(*msg)->cur_part++;
			} else if (ch == ')') {
				goto success;
			} else if (isspace(ch)) {
				/* nothing */
			} else {
				goto failure;
			}
			break;

		case S_string:
			if (ch == '\\') {
				(*msg)->state = S_backslash;
			} else if (ch == '"') {
				(*msg)->state = S_string_end;
				message_append(*msg, (*msg)->cur_part, MESSAGE_STRING, ((*msg)->token)->str, MESSAGE_END);
			} else {
				g_string_append_c((*msg)->token, ch);
			}
			break;

		case S_string_end:
			if (ch == ')') {
				goto success;
			} else if (ch == '|') {
				(*msg)->cur_part++;
				(*msg)->state = S_next;
			} else if (isspace(ch)) {
				(*msg)->state = S_next;
			} else {
				goto failure;
			}
			break;

		case S_backslash:
			g_string_append_c((*msg)->token, ch);
			(*msg)->state = S_string;
			break;

		case S_integer_first:
			if (isdigit(ch)) {
				if (ch == '0')
					(*msg)->state = S_integer_zero;
				else if (isdigit(ch))
					(*msg)->state = S_integer;
				else
					(*msg)->state = S_integer_first;
				g_string_append_c((*msg)->token, ch);
			} else {
				goto failure;
			}
			break;
			
		case S_integer_zero:
		case S_integer_oct:
		case S_integer_hex:
		case S_integer:
			if ((*msg)->state == S_integer_zero && ch == 'x') {
				g_string_append_c((*msg)->token, ch);
				(*msg)->state = S_integer_hex;
			} else if ((*msg)->state == S_integer_zero && ch >= '0' && ch < '8') {
				g_string_append_c((*msg)->token, ch);
				(*msg)->state = S_integer_oct;
			} else if (isspace(ch)) {
				message_append(*msg, (*msg)->cur_part, MESSAGE_INTEGER, (long) strtol(((*msg)->token)->str, NULL, 0), MESSAGE_END);
				(*msg)->state = S_next;
			} else if (ch == ')') {
				message_append(*msg, (*msg)->cur_part, MESSAGE_INTEGER, (long) strtol(((*msg)->token)->str, NULL, 0), MESSAGE_END);
				goto success;
			} else if (ch == '|') {
				message_append(*msg, (*msg)->cur_part, MESSAGE_INTEGER, (long) strtol(((*msg)->token)->str, NULL, 0), MESSAGE_END);
				(*msg)->cur_part++;
				(*msg)->state = S_next;
			} else if ((*msg)->state == S_integer_zero) {
				goto failure;
			} else if ((*msg)->state == S_integer && isdigit(ch)) {
				g_string_append_c((*msg)->token, ch);
			} else if ((*msg)->state == S_integer_oct && ch >= '0' && ch < '8') {
				g_string_append_c((*msg)->token, ch);
			} else if ((*msg)->state == S_integer_hex && isxdigit(ch)) {
				g_string_append_c((*msg)->token, ch);
			} else {
				goto failure;
			}
			break;
	}
	return 0;

failure:
	message_destroy(*msg);
	*msg = NULL;
	DBG_MSG(3, "failed.");
	return -1;
	
success:
	(*msg)->state = S_none;
	if ((*msg)->token) {
		g_string_free((*msg)->token, TRUE);
		(*msg)->token = NULL;
	}
	DBG_MSG(3, "succeed.");
	return 1;
}

#define message_to_string__realloc(add_len) {				\
	register int addlen = (add_len);				\
	if (len < pos + addlen) {					\
		str = realloc(str, len = ((len + addlen) * 2));		\
		if (!str)						\
			goto failure;					\
	}								\
}
char *message_to_string(MESSAGE *msg, int append_nl)
{
	struct s_message_item *p;
	int len = 512, pos = 0;
	char *str, *s;
	int part, add_sep = 0;
	
	DBG_MSG(4, "");
	assert(msg != NULL);
	
	str = malloc(len);
	if (!str)
		return NULL;
	
	pos = 0;
	str[pos++] = '(';
	
	for (part = 0; part < MESSAGE_PART_MAX; part++) {
		for (p = msg->part[part].list; p && p->type; p = p->next) {

			/* append part separators */
			for (; add_sep > 0; add_sep--) {
				message_to_string__realloc(4);
				str[pos++] = ' ';
				str[pos++] = '|';
			}
			
			/* append value */
			switch (p->type) {
				case MESSAGE_INTEGER:
					message_to_string__realloc(32);
					snprintf(str + pos, 32, " %ld", p->value.integer);
					pos += strlen(str + pos);
					DBG_MSG(8, "integer %ld", p->value.integer);
					break;

				case MESSAGE_STRING:
					message_to_string__realloc(4);
					
					str[pos++] = ' ';
					str[pos++] = '"';

					s = p->value.string;
					while ((str[pos] = *s++)) {
						message_to_string__realloc(8);
						if(str[pos] == '"') {
							str[pos++] = '\\';
							str[pos] = '"';
						} else if(str[pos] == '\\') {
							str[++pos] = '\\';
						}
						pos++;
					}
					str[pos++] = '"';

					DBG_MSG(8, "string \"%s\"", p->value.string);
					break;

				default:
					DBG_MSG(1, "unknown type!");
					goto failure;
			}

			DBG_MSG(8, "tmp result: \"%s\"", str);
		}
		add_sep++;
	}
	
	message_to_string__realloc(5);
	str[pos++] = ' ';
	str[pos++] = ')';
	if (append_nl)
		str[pos++] = '\n';
	str[pos] = 0;
	
	DBG_MSG(3, "result: \"%s\"", str);
	return str;

failure:
	free(str);
	return NULL;
}
#undef message_to_string__realloc


int message_to_file(MESSAGE *msg, FILE *file, int append_nl)
{
	char *str = NULL;
	int ret;
	
	assert(msg != NULL);
	assert(file != NULL);

	str = message_to_string(msg, append_nl);
	ret = fputs(str, file) != EOF;
	fflush(file);
	free(str);
	return ret;
}


int message_register_target(char dest, MESSAGE_HANDLER handler, const char *description)
{
	assert(dest >= 0 && dest < MESSAGE_MAX_TARGETS);
	
	/* check if there is handler */
	if (message_target[(unsigned)dest].handler) {
		DBG_MSG(1, "Handler colision for target '%c'!", dest);
		return 0;
	}

	/* register handler */
	message_target[(unsigned)dest].handler = handler;
	
	/* set description */
	if (message_target[(unsigned)dest].desc)
		free(message_target[(unsigned)dest].desc);
	message_target[(unsigned)dest].desc = description ? strdup(description) : NULL;
	
	DBG_MSG(2, "Target '%c' registered.", dest);
	return 1;
}


int message_unregister_target(char dest)
{
	assert(dest >= 0 && dest < MESSAGE_MAX_TARGETS);
	
	if (message_target[(unsigned)dest].handler) {
		/* remove handler */
		message_target[(unsigned)dest].handler = NULL;
		
		/* free description */
		if (message_target[(unsigned)dest].desc)
			free(message_target[(unsigned)dest].desc);
		message_target[(unsigned)dest].desc = NULL;
		
		DBG_MSG(2, "Target '%c' removed.", dest);
		return 1;
	} else {
		DBG_MSG(1, "There is no target '%c'!", dest);
		return 0;
	}
}


int message_send(MESSAGE *msg)
{
	DBG_MSG(4, "");
	assert(msg != NULL);
	DBG_MSG(1, "src = '%c', dest = '%c'", msg->src, msg->dest);

	if(message_target[(unsigned)msg->dest].handler) {
		DBG_MSG(3, "Sending message ...");
		(*message_target[(unsigned)msg->dest].handler)(msg);
		DBG_MSG(3, "Message delivered.");
		return 1;
	} else {
		DBG_MSG(1, "Destination not found. Message dropped.");
		return 0;
	}
}

const char *message_get_target_description(char target)
{
	return message_target[(unsigned) target].desc;
}

void message_get_target_id_rewind (MESSAGE_GET_TARGET_ITER *iter)
{
	DBG_MSG(4, "");
	assert(iter != NULL);
	*iter = 0;
}

int message_get_target_id_next (MESSAGE_GET_TARGET_ITER *iter)
{
	assert(iter != NULL);
	DBG_MSG(8, "*iter = %d", *iter);

	while (*iter < MESSAGE_MAX_TARGETS && !message_target[*iter].handler)
		++*iter;

	if (*iter == MESSAGE_MAX_TARGETS)
		return -1;
	else
		return (*iter)++;
}

