/*
 * nb_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 "nb_message.h"

#define DEBUG 0
#include "debug.h"


#define MAX_HANDLERS 127
static NB_MESSAGE_HANDLER nb_message_handler[MAX_HANDLERS];


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

#if USE_GLIB_SLICES
#	define nb_message_alloc_mem()		g_slice_new(NB_MESSAGE)
#	define nb_message_free_mem(m)		g_slice_free(NB_MESSAGE, (m))
#	define nb_message_item_alloc_mem()		g_slice_new(struct s_message_item)
#	define nb_message_item_free_mem(m)		g_slice_free(struct s_message_item, (m))
#else
#	define nb_message_alloc_mem()		((NB_MESSAGE *) malloc(sizeof(NB_MESSAGE)))
#	define nb_message_free_mem(m)		free((m))
#	define nb_message_item_alloc_mem()		(struct s_message_item *) malloc(sizeof(struct s_message_item))
#	define nb_message_item_free_mem(m)		free((m))
#endif


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

	/* prepare handlers table */
	memset(nb_message_handler, 0, sizeof(NB_MESSAGE_HANDLER) * MAX_HANDLERS);
	
	return;
}


NB_MESSAGE *nb_message_create(char src, char dest)
{
	NB_MESSAGE *msg;
	DBG_MSG(4, "nb_message_create");

	msg = nb_message_alloc_mem();
	if (msg) {
		msg->src = src;
		msg->dest = dest;
		msg->list = NULL;
		msg->p_list_end = NULL;
		msg->p_list_next = NULL;
		msg->length = 0;
		msg->token = NULL;
		msg->state = 0;
	}
	return msg;
}


void nb_message_destroy(NB_MESSAGE *msg)
{
	struct s_message_item *p, *d;
	
	DBG_MSG(4, "nb_message_destroy");
	assert(msg != NULL);
	
	p = msg->list;
	while (p) {
		d = p;
		p = p->next;

		if (d->type == NB_MESSAGE_STRING && d->value.string)
			free(d->value.string);
		nb_message_item_free_mem(d);
	}
	if (msg->token)
		g_string_free(msg->token, TRUE);
	nb_message_free_mem(msg);
	DBG_MSG(9, "nb_message_destroy: done");
}


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


int nb_message_append(NB_MESSAGE *msg, ...)
{
	va_list ap;
	NB_MESSAGE_ITEM_TYPE t;
	struct s_message_item *item;
	int count = 0;
	
	DBG_MSG(4, "nb_message_append");
	assert(msg != NULL);
	
	va_start(ap, msg);
	while ((t = va_arg(ap, NB_MESSAGE_ITEM_TYPE))) {
		item = nb_message_item_alloc_mem();
		if (!item)
			return 0;

		if (msg->p_list_end)
			msg->p_list_end->next = item;
		else
			msg->list = item;
		msg->p_list_end = item;
		item->next = NULL;
		item->type = t;
		msg->length++;
		
		switch (t) {
			case NB_MESSAGE_INTEGER:
				item->value.integer = va_arg(ap, int);
				DBG_MSG(8, "nb_message_append: integer %d", item->value.integer);
				break;

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

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


int nb_message_get_next(NB_MESSAGE *msg, NB_MESSAGE_GET_FLAG rewind, ...)
{
	va_list ap;
	NB_MESSAGE_ITEM_TYPE t;
	struct s_message_item **p = NULL, *pl = NULL;
	
	DBG_MSG(4, "nb_message_get_next");
	assert(msg != NULL);
	
	if (rewind == 1 || rewind & NB_MESSAGE_GET_REWIND) {
		msg->p_list_next = msg->list;
	}

	if (rewind == -1 || rewind & NB_MESSAGE_GET_BEGIN)
		p = &pl;
	else
		p = &(msg->p_list_next);
	
	va_start(ap, rewind);
	for (*p = (rewind == -1) ? msg->list : msg->p_list_next;; *p = (*p)->next) {
		t = va_arg(ap, NB_MESSAGE_ITEM_TYPE);
		if (t == NB_MESSAGE_END) {
			va_end(ap);
			DBG_MSG(8, "nb_message_get: End of nb_message.")
			return 1;
		}
		if (!*p) {
			va_end(ap);
			DBG_MSG(1, "nb_message_get: Unexpected end of list.")
			return 0;
		}

		if (t != NB_MESSAGE_SKIP && t != (*p)->type) {
			va_end(ap);
			DBG_MSG(1, "nb_message_get: type mismatch.");
			return 0;
		}
		
		switch (t) {
			case NB_MESSAGE_INTEGER:
				*(va_arg(ap, int*)) = (*p)->value.integer;
				DBG_MSG(8, "nb_message_get: integer %d", (*p)->value.integer);
				break;

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

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


NB_MESSAGE *nb_message_from_string(char src, char dest, const char *str, char **p)
{
	NB_MESSAGE *msg = NULL;
	
	DBG_MSG(3, "nb_message_from_string... str = \"%s\"", p && *p ? *p : str);
	assert(str != NULL);
	assert(p != NULL);

	if (*p == NULL)
		*p = (char *) str;
	
	while (**p && nb_message_from_stream(src, dest, *((*p)++), &msg) != 1)
		;

	return msg;
}

	
int nb_message_from_stream(char src, char dest, char ch, NB_MESSAGE **msg)
{
	enum {
		S_none = 0,
		S_start,
		S_next,
		S_string,
		S_string_end,
		S_backslash,
		S_integer
	};

	assert(msg != NULL);

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

	/* destroy old nb_message */
	if (*msg && (*msg)->state == S_none) {
		nb_message_destroy(*msg);
		*msg = NULL;
	}
	
	/* pre-create nb_message to store token & state */
	if (!*msg) {
		*msg = nb_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 == '+') {
				(*msg)->state = S_integer;
				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 == ')') {
				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;
				nb_message_append(*msg, NB_MESSAGE_STRING, ((*msg)->token)->str, NB_MESSAGE_END);
			} else {
				g_string_append_c((*msg)->token, ch);
			}
			break;

		case S_string_end:
			if (ch == ')') {
				goto success;
			} 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:
			if (isdigit(ch)) {
				g_string_append_c((*msg)->token, ch);
			} else if (isspace(ch)) {
				nb_message_append(*msg, NB_MESSAGE_INTEGER, atoi(((*msg)->token)->str), NB_MESSAGE_END);
				(*msg)->state = S_next;
			} else if (ch == ')') {
				nb_message_append(*msg, NB_MESSAGE_INTEGER, atoi(((*msg)->token)->str), NB_MESSAGE_END);
				goto success;
			} else {
				goto failure;
			}
			break;
	}
	return 0;

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


char *nb_message_to_string(NB_MESSAGE *msg, int append_nl)
{
	struct s_message_item *p;
	int len = 256, pos = 0, l;
	char *str, *s;
	
	DBG_MSG(4, "nb_message_to_string");
	assert(msg != NULL);
	
	str = malloc(len);
	if (!str)
		return NULL;
	
	pos = 0;
	str[pos++] = '(';
	str[pos] = 0;
	
	for (p = msg->list; p && p->type; p = p->next) {
		switch (p->type) {
			case NB_MESSAGE_INTEGER:
				if (len < pos + 32) {
					str = realloc(str, len = ((len + 32) * 2));
					if (!str)
						goto failure;
				}
				sprintf(str + pos, " %d", p->value.integer);
				pos = strlen(str);
				DBG_MSG(8, "nb_message_to_string: integer %d", p->value.integer);
				break;

			case NB_MESSAGE_STRING:
				if (len < pos + (l = strlen(p->value.string)) * 2 + 3) {
					str = realloc(str, len = ((len + l * 2) * 2));
					if (!str)
						goto failure;
				}
				
				str[pos++] = ' ';
				str[pos++] = '"';

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

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

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

		DBG_MSG(8, "tmp result: \"%s\"", str);
	}
	
	if (len < pos + 3 + (append_nl == 1)) {
		str = realloc(str, len = (len + 5 + (append_nl == 1)));
		if (!str)
			goto failure;
	}
	strcat(str, " )");
	if (append_nl)
		strcat(str, "\n");
	DBG_MSG(3, "nb_message_to_string: result: \"%s\"", str);
	return str;

failure:
	free(str);
	return NULL;
}


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

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


int nb_message_register_target(char dest, NB_MESSAGE_HANDLER handle)
{
	assert(dest >= 0 && dest < MAX_HANDLERS);
	
	/* check if there is handler */
	if (nb_message_handler[(unsigned)dest]) {
		DBG_MSG(1, "nb_message_register_target: Handler colision for destination '%c'!", dest);
		return 0;
	}

	/* register */
	nb_message_handler[(unsigned)dest] = handle;
	DBG_MSG(2, "nb_message_register_target: Destination '%c' registered.", dest);
	return 1;
}


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

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


