/*
 * status.c
 * 
 * Copyright (C) 2005-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 <stdlib.h>
#include <string.h>
#include <assert.h>

#define DEBUG 		5
#include "debug.h"

#include "status.h"

#define ABBR_TO_ID__SIZE	(128 - 32)
#define ABBR_TO_ID__OFFSET	32

#define ABBR_TO_ID_ALLOC()	((struct s_trie_abbr_to_id *) g_slice_alloc0((ABBR_TO_ID__SIZE) * sizeof(struct s_trie_abbr_to_id)))
#define ABBR_TO_ID_FREE(p)	(g_slice_free1(ABBR_TO_ID__SIZE * sizeof(struct s_trie_abbr_to_id), p))

struct s_trie_abbr_to_id {
        struct s_trie_abbr_to_id *next;
        int id;
};


STATUS *status_create(char *name, struct s_status_table *table, int table_lines)
{
	STATUS *st;
	struct s_trie_abbr_to_id *ptr;
	const char *p;
	int i, n;

	assert(name != NULL);
	assert(table != NULL);
	assert(table_lines > 0);

	DBG_MSG(1, "%s", name);

	/* initialize everyting */
	st = (STATUS *) malloc(sizeof(STATUS));
	st->name = strdup(name);
	st->time = 0;
	st->current_st = 0;
	st->current_sub_state = NULL;
	st->current_desc = NULL;
	st->last_st = 0;
	st->max_st = table_lines - 1;
	st->table = (struct s_status_table *) malloc(sizeof(struct s_status_table) * table_lines);
	memcpy(st->table, table, sizeof(struct s_status_table) * table_lines);
	st->cb_before_switch = NULL;
	st->cb_between_switch = NULL;
	st->cb_after_switch = NULL;
	st->after_st = 0;
	st->after_sub_st = NULL;
	st->is_status_switch_in_progress = 0;
	st->switch_queue_full = 0;

	/* build trie for fast converting abbrevation to state id */
	DBG_MSG(3, "Building abbr to id trie");
	st->trie_abbr_to_id = ABBR_TO_ID_ALLOC();
	((struct s_trie_abbr_to_id *)(st->trie_abbr_to_id))->id = -1;
	for (i = 0; i < table_lines; i++) {
		DBG_MSG(5, "adding \"%s\"", st->table[i].status_abbr);
		ptr = (struct s_trie_abbr_to_id *)(st->trie_abbr_to_id);
		for (p = st->table[i].status_abbr; p && *p; p++) {
			n = (*p - ABBR_TO_ID__OFFSET);
			if (n >= 0 && n < ABBR_TO_ID__SIZE) {
				if (*(p + 1) == 0) {
					ptr[n].id = i + 1;
				} else {
					if (!ptr[n].next) {
						ptr[n].next = ABBR_TO_ID_ALLOC();
					}
					ptr = ptr[n].next;
				}
			}
		}
	}

	return st;
}

static void status_destroy__destroy_trie_abbr_to_id(struct s_trie_abbr_to_id *ptr)
{
	int i;
	struct s_trie_abbr_to_id *n;

	for (i = 0; i < ABBR_TO_ID__SIZE; i++) {
		n = ptr[i].next;
		if (n) {
			status_destroy__destroy_trie_abbr_to_id(n);
			ABBR_TO_ID_FREE(n);
		}
	}
}

int status_destroy (STATUS *st)
{
	assert(st != NULL);
	
	DBG_MSG(1, "%s", st->name);
	
	status_destroy__destroy_trie_abbr_to_id((struct s_trie_abbr_to_id *)(st->trie_abbr_to_id));

	if (st->current_desc)
		g_free(st->current_desc);

	free(st->name);
	free(st->table);
	free(st);

	return 0;
}


int status_switch (STATUS *st, int new_state, const char *new_sub_state)
{
	int ret = 0;
	int next_state;
	const char *next_sub_state;

	assert(st != NULL);
	assert(new_state <= st->max_st);
	
	DBG_MSG(2, "%s: Changing state from \"%s\" (%d) to \"%s\" (%d)", st->name,
			st->table[st->current_st].status_name, st->current_st,
			st->table[new_state].status_name, new_state);
	
	assert(st->switch_queue_full == 0);
	
	next_state = new_state;
	next_sub_state = new_sub_state;

	
	if (st->is_status_switch_in_progress) {
		/* session switch is in progress, only enqueue next state */
		st->after_st = new_state;
		
		if (st->after_sub_st)
			free(st->after_sub_st);
		
		if (new_sub_state)
			st->after_sub_st = strdup(new_sub_state);
		else
			st->after_sub_st = NULL;

		st->switch_queue_full = 1;

		return 1;
	}
	
	st->is_status_switch_in_progress = 1;
	
	while (next_state != st->current_st || next_sub_state) {
		ret = 1;

		/* call before-switch callback */
		if (st->cb_before_switch) {
			DBG_MSG(4, "%s: calling before-switch callback", st->name);
			(*st->cb_before_switch)(st);
		}

		/* leave current state */
		if (st->table[st->current_st].cb_leave) {
			DBG_MSG(4, "%s: leaving state \"%s\" (%d), sub state \"%s\"",
					st->name, st->table[st->current_st].status_name, st->current_st, st->current_sub_state);
			(*st->table[st->current_st].cb_leave)(st);
		}
		if (st->current_desc) {
			g_free(st->current_desc);
			st->current_desc = NULL;
		}

		/* reset efect of state_switch_after */
		st->after_st = next_state;
		if (st->after_sub_st)
			free(st->after_sub_st);
		st->after_sub_st = NULL;

		/* change to new state */
		st->last_st = st->current_st;
		st->current_st = next_state;
		st->switch_queue_full = 0;
		if (st->current_sub_state)
			free(st->current_sub_state);
		if (next_sub_state) {
			st->current_sub_state = strdup(next_sub_state);
		} else {
			st->current_sub_state = NULL;
		}
		
		/* call between-switch callback */
		if (st->cb_between_switch) {
			DBG_MSG(4, "%s: calling between-switch callback", st->name);
			(*st->cb_between_switch)(st);
		}
		
		/* enter new state */
		if (st->table[st->current_st].cb_enter) {
			DBG_MSG(4, "%s: entering state \"%s\" (%d), sub state \"%s\"",
					st->name, st->table[st->current_st].status_name, st->current_st, st->current_sub_state);
			(*st->table[st->current_st].cb_enter)(st);
		}
		
		/* call after-switch callback */
		if (st->cb_after_switch) {
			DBG_MSG(4, "%s: calling after-switch callback", st->name);
			(*st->cb_after_switch)(st);
		}

		/* continue to next status */
		next_state = st->after_st;
		next_sub_state = st->after_sub_st;
	}
	
	st->is_status_switch_in_progress = 0;

	return ret;
}


int status_get (STATUS *st)
{
	assert(st != NULL);
	return st->current_st;
}


long status_get_time (STATUS *st)
{
	assert(st != NULL);
	return st->time;
}


char * status_get_name (STATUS *st)
{
	assert(st != NULL);
	return st->table[st->current_st].status_name;
}


char * status_get_name_by_id (STATUS *st, int id)
{
	assert(st != NULL);
	assert(id <= st->max_st);
	return st->table[id].status_name;
}


char * status_get_abbr (STATUS *st)
{
	assert(st != NULL);
	return st->table[st->current_st].status_abbr;
}


char * status_get_abbr_by_id (STATUS *st, int id)
{
	assert(st != NULL);
	assert(id <= st->max_st);
	return st->table[id].status_abbr;
}

int status_get_id_by_abbr(STATUS * st, const char *abbr)
{
	assert(st != NULL);
	assert(abbr != NULL);

	struct s_trie_abbr_to_id *ptr;
	const char *p;
	int i, n;

	DBG_MSG(5, "searching \"%s\"", abbr);

	for (i = -1, ptr = (struct s_trie_abbr_to_id *)(st->trie_abbr_to_id), p = abbr;
			ptr && *p; p++)
	{
		n = *p - ABBR_TO_ID__OFFSET;
		if (n < ABBR_TO_ID__SIZE + ABBR_TO_ID__OFFSET) {
			if (*(p + 1) == 0) {
				i = ptr[n].id - 1;
				break;
			} else {
				ptr = ptr[n].next;
			}
		}
	}
	if (i >= 0) {
		DBG_MSG(5, "found id %d = %s", i, st->table[i].status_abbr);
	} else {
		DBG_MSG(5, "not found");
	}
	return i;
}


void status_set_description(STATUS *st, char *fmt, ...)
{
	va_list ap;
	assert(st != NULL);
	
	va_start(ap, fmt);
	
	if (st->current_desc)
		g_free(st->current_desc);

	st->current_desc = fmt ? g_strdup_vprintf(fmt, ap) : NULL;

	va_end(ap);
}


int status_validate_trans(STATUS *st, int old_st, int new_st)
{
	assert(st != NULL);
	assert(old_st <= st->max_st);
	assert(new_st <= st->max_st);
	
	if (st->table[old_st].allowed_st) {
		int ret;
		ret = st->table[old_st].allowed_st[new_st] == '1';
		DBG_MSG(3, "old = %s(%d), new = %s(%d), char = '%c' => %s",
				st->table[old_st].status_name, old_st,
				st->table[new_st].status_name, new_st,
				st->table[old_st].allowed_st[new_st],
				ret ? "ok":"no");
		return ret;
	} else {
		DBG_MSG(3, "no table");
		return 0;
	}
}


int status_compare_states(STATUS *st, int a, int b)
{
	assert(st != NULL);
	assert(a <= st->max_st);
	assert(b <= st->max_st);
	
	if (st->table[a].comparsion) {
		int ret;
		switch (st->table[a].comparsion[b]) {
			case '-':
				ret = -1;
				break;
			case '+':
				ret = 1;
				break;
			default:
				ret = 0;
				break;
		}
		DBG_MSG(3, "a = %s(%d), b = %s(%d), char = '%c' => %d",
				st->table[a].status_name, a,
				st->table[b].status_name, b,
				st->table[a].comparsion[b],
				ret);
		return ret;
	} else {
		DBG_MSG(3, "no table");
		return 0;
	}
	//return rand() % 2;
}

/* vim:set encoding=utf8: */
