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

#include "session.h"
#include "status.h"
#include "message.h"
#include "options.h"
#include "backend.h"
#include "playerlist.h"
#include "gui.h"
#include "dirs.h"
#include "record.h"
#include "roles.h"
#include "draw.h"

#define DEBUG 5
#include "debug.h"

STATUS *session_status = NULL;
BACKEND *backend_game = NULL;

SESSION_GLOBAL_ST session_global_st = {
	0, 0, NULL
};


static void on_backend_cleanup(void)
{
	gui_append_to_gamelog("-- Backend skončil.\n\n");
	backend_game = NULL;
}


static void on_backend_exit(void)
{
	int st = status_get(session_status);

	switch (st) {
		case S_S_NONE:
		case S_S_IDLE:
		case S_S_LOAD_ERROR:
		case S_S_GAME_ERROR:
			break;
			
		case S_S_LOAD_ABORT:
		case S_S_GAME_OVER:
			status_switch(session_status, S_S_IDLE, NULL);
			break;

		case S_S_LOADING:
		case S_S_LOADED:
		case S_S_ROLE_SELECT:
		case S_S_READY:
		case S_S_START_CHECK:
			status_switch(session_status, S_S_LOAD_ERROR, NULL);
			break;
			
		case S_S_GAME_PLAYING:
			status_switch(session_status, S_S_GAME_ERROR, NULL);
			break;
			
		case S_S_KILL:
		case S_S_SYNCING:
			if (session_status->last_st == S_S_GAME_PLAYING)
				status_switch(session_status, S_S_GAME_ERROR, NULL);
			else
				status_switch(session_status, S_S_LOAD_ERROR, NULL);
			break;
	}

}


static void enter_idle(STATUS * st)
{
	DBG_MSG(5, "");
	assert(backend_game == NULL);
	status_set_description(st, _("Čekám."));
	option_set_string("main", "game", "backend", NULL);
	option_set_string("main", "game", "my_role", NULL);
}


static void leave_idle(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_loading(STATUS * st)
{
	gchar *argv[] = { NULL, NULL };
	const char *game, *base_dir;
	int log;

	DBG_MSG(5, "");
	assert(backend_game == NULL);

	/* prepare */
	roles_clear();
	dir_update();
	gui_clean_statusbar();
	draw_remove_all();

	/* store game name */
	game = st->current_sub_state;
	option_set_string("main", "game", "backend", game);
	option_set_string("main", "game", "my_role", NULL);
	option_set_string("main", "ui", "last_game_backend", game);

	/* get configuration */
	base_dir = option_get_string("main", "dir", "game");
	log = option_get_integer("main", "debug", "enable_comm_log");
	
	/* log and debug messages */
	DBG_MSG(3, "Game:");
	DBG_MSG(3, "  backend:   %s", game);
	DBG_MSG(3, "  directory: %s", base_dir);
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Spouštím herní modul \"%s\" ..."), game);
	gui_append_to_gamelog(_("\n-- Spouštím backend \"%s\" ...\n"), game);

	/* spawn backend */
	argv[0] = (char *) game;
	backend_game = backend_spawn('G', base_dir, argv, log, GUI_LOG_GAME);
	if (backend_game) {
		/* set callbacks */
		backend_game->on_cleanup_cb = on_backend_cleanup;
		backend_game->on_exit_cb = on_backend_exit;
		
		/* success */
		gui_append_to_gamelog(_("-- Backend spuštěn (pid %d)...\n"), backend_game->pid);
			
		/* send starting notification */
		status_set_description(st, _("Budeme hrát %s."), game);
	} else {
		/* failed */
		gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Chyba při spouštení herního modulu \"%s\" ..."), game);
		gui_append_to_gamelog(_("-- Chyba při spouštění backendu\n"));

		status_set_description(st, _("Nemohu hrát hru %s."), game);
		status_switch(st, S_S_LOAD_ERROR, NULL);
	}
}


static void leave_loading(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_loaded(STATUS * st)
{
	DBG_MSG(5, "");

	if (st->last_st == S_S_LOADING) {
		gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Herní modul spuštěn. Vyberte svou roli a zahajte hru."));
		gui_append_to_gamelog(_("-- Backend připraven.\n"));
		gui_switch_main_page(GUI_PAGE_CHAT);
	}

	status_set_description(st, _("Jsem připraven."));
}


static void leave_loaded(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_load_abort(STATUS * st)
{
	DBG_MSG(5, "");

	/* poslat info o tom, ze sme skoncili */
	status_set_description(st, _("Ruším hru."));

	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Hra zrušena."));
	
	if (backend_game) {
		gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Ukončuji herní backend ..."));
		backend_kill(backend_game);
	} else {
		status_switch(st, S_S_IDLE, NULL);
	}
	
}


static void leave_load_abort(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_load_error(STATUS * st)
{
	DBG_MSG(5, "");
	status_set_description(st, _("Nemohu načíst hru."));
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Chyba při načítání hry."));
}


static void leave_load_error(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_role_select(STATUS * st) 
{
	const gchar *role;
	int role_id;
	
	DBG_MSG(5, "");
	
	role = st->current_sub_state;
	if (role) {
		role_id = roles_get_id_by_name(role);
		if (role_id < 0)
			role_id = 0;
	} else {
		role_id = 0;
	}
	
	option_set_string("main", "game", "my_role", role);
	gui_role_select(role_id);
	
	if (role && role_id > 0)
		status_set_description(st, _("Budu hrát jako \"%s\"."), role);
	else
		status_set_description(st, _("Budu se jen dívat."));

}


static void leave_role_select(STATUS * st) 
{
	DBG_MSG(5, "");
}


static void enter_ready(STATUS * st)
{
	DBG_MSG(5, "");
	gui_append_to_gamelog(_("-- Zahajuji hru.\n"));
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Zahajuji hru. Čekám na ostatní."));
	status_set_description(st, _("Můžeme začít."));
}


static void leave_ready(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_start_check(STATUS * st)
{
	DBG_MSG(5, "");
	status_set_description(st, _("Zahajuji hru..."));
}


static void leave_start_check(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_game_playing(STATUS * st)
{
	MESSAGE *msg;
	DBG_MSG(5, "");

	/* poslat info game backendu a uz jen pockat nez to skonci */
	/*MSG* Gs
	 *
	 * Game Start
	 *
	 * Start game notification.
	 */
	msg = message_create('S', 'G');
	message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Gs", MESSAGE_END);
	message_send(msg);
	message_destroy(msg);

	playerlist_clean(1, 0);
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Hra zahájena."));
	status_set_description(st, _("Hrajem!"));
	gui_switch_main_page(GUI_PAGE_BOARD);
}


static void leave_game_playing(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_game_over(STATUS * st)
{
	MESSAGE *msg;
	DBG_MSG(5, "");

	if (backend_game) {
		/* poslat info game backendu a uz jen pockat nez to skonci */
		/*MSG* Go
		 *
		 * Game Over
		 *
		 * Notify game backend about game over.
		 */
		msg = message_create('S', 'G');
		message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Go", MESSAGE_END);
		message_send(msg);
		message_destroy(msg);
	} else {
		status_switch(session_status, S_S_IDLE, NULL);
	}

	/* poslat info o tom, ze sme skoncili */
	status_set_description(st, _("Končím hru."));

	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Hra ukončena."));
}


static void leave_game_over(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_game_error(STATUS * st)
{
	DBG_MSG(5, "");
	status_set_description(st, _("Při běhu hry došlo k chybě."));
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Při běhu hry došlo k chybě."));
}


static void leave_game_error(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_kill(STATUS * st)
{
	DBG_MSG(5, "");
	
	if (backend_game) {
		gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Ukončuji herní backend ..."));
		backend_kill(backend_game);
	} else {
		on_backend_exit();
	}
}


static void leave_kill(STATUS * st)
{
	DBG_MSG(5, "");
}


static void enter_syncing(STATUS * st)
{
	MESSAGE *msg;

	DBG_MSG(5, "");
	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Zjišťuji stav ostatních hráčů..."));
	
	/* send status query */
	msg = message_create('S', 'N');
	message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Sq", MESSAGE_STRING, _("Jak jste na tom?"), MESSAGE_INTEGER, session_status->time, MESSAGE_END);
	message_send(msg);
	message_destroy(msg);
}


static void leave_syncing(STATUS * st)
{
	DBG_MSG(5, "");
}


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


static struct s_status_table session_status_table[] = {
	/*									naslednici:        porovnani:      */
	/* stav                zkratka 	pri vstupu              pri opousteni		 NILLLLRRSGGGKS    NILLLLRRSGGGKS  */
	/*										 ODGDAESDCPOELY    ODGDAESDCPOELY  */
	{ "none",		"NO",	NULL,			NULL,			".1...........1", ".++++++++++---" },
	{ "idle",		"ID",	enter_idle,		leave_idle,		"..1..........1", "-.++--++++----" },
	{ "loading",		"LG",	enter_loading,		leave_loading,		"...111.......1", "-+.++.+++++---" },
	{ "loaded",		"LD",	enter_loaded,		leave_loaded,		"....1.11....11", "---.+....++---" },
	{ "load abort",		"LA",	enter_load_abort,	leave_load_abort,	".1..........11", "-+--..--------" },
	{ "load error",		"LE",	enter_load_error,	leave_load_error,	"..1........1.1", "-+.........+--" },
	{ "role select",	"RS",	enter_role_select,	leave_role_select,	"...11.......11", "---++..++++---" },
	{ "ready",		"RD",	enter_ready,		leave_ready,		"...11.1.1...11", "----+...+++---" },
	{ "start check",	"SC",	enter_start_check,	leave_start_check,	"...11.1..1..11", "-++++.++.++---" },
	{ "game playing",	"GP",	enter_game_playing,	leave_game_playing,	".........11111", "-----.---.+.--" },
	{ "game over",		"GO",	enter_game_over,	leave_game_over,	".1.........111", "-+---.----..--" },
	{ "game error",		"GE",	enter_game_error,	leave_game_error,	"..1.........11", "-++++.+++...--" },
	{ "kill",		"KL",	enter_kill,		leave_kill,		".....1.....1.1", "++++++++++++.+" },
	{ "syncing",		"SY",	enter_syncing,		leave_syncing,		".11.........11", "+++++++++++++." },
};


static void between_switch (STATUS * st)
{
	st->time = st->time > session_global_st.time ? st->time : session_global_st.time;
}


static void after_switch (STATUS * st)
{
	MESSAGE *msg;
	gchar *text;

	/* prepare text description */
	if (st->current_desc) {
		text = st->current_desc;
	} else {
		text = g_strdup_printf(_("Změna stavu z %s na %s. (arg: %s)"),
				session_status_table[st->last_st].status_name,
				session_status_table[st->current_st].status_name,
				st->current_sub_state);
	}

	/* increment time */
	st->time++;
	
	/* send notification about change to others */
	msg = message_create('S', 'N');
	message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Sc", MESSAGE_STRING, text,
			MESSAGE_INTEGER, st->time,
			MESSAGE_STRING, status_get_abbr_by_id(st, st->current_st) ? :"?",
			MESSAGE_STRING, st->current_sub_state ? st->current_sub_state : "",
			MESSAGE_END);
	message_send(msg);
	message_destroy(msg);

	/* cleanup */
	if (!st->current_desc) {
		g_free(text);
	}

	/* update UI */
	gui_update_expanders();
	gui_update_main_window();
}


void session_init(void)
{
	session_status = status_create("session", session_status_table, sizeof(session_status_table) / sizeof(session_status_table[0]));
	session_status->current_st = S_S_NONE;
	session_status->cb_between_switch = between_switch;
	session_status->cb_after_switch = after_switch;
}


void session_cleanup(void)
{
	status_destroy(session_status);
}





void session_update_info(int time, char cmd, int new_state, const char *new_state_param, const char *new_state_desc,
		const char *new_role, const char *peer_addr, int remote)
{
	int old_state, is_valid = 0;
	char str[2] = { 0, 0 };

	DBG_MSG(2, "new_state = %s, new_state_param = \"%s\", peer_addr = \"%s\", remote = %d, new_state_desc = \"%s\"",
			session_status->table[session_status->current_st].status_name, new_state_param, peer_addr, remote, new_state_desc);
	
	/* validate transition */
	old_state = record_get_last_state(peer_addr);
	if (old_state != -1) {
		switch (cmd) {
			case 'i':
				if (old_state == new_state) {
					is_valid = 1;
					break;
				}
			case 'c':
				is_valid = status_validate_trans(session_status, old_state, new_state);
		}
		
		/* record event */
		str[0] = cmd;
		record_append(time, str, peer_addr, remote, new_state, new_state_param, is_valid, new_state_desc);
	}
		

	/* update player list */
	playerlist_update_state(peer_addr, new_state, new_state_desc);
	if (new_role)
		playerlist_update(peer_addr, NULL, new_role, NULL, NULL);

	switch (new_state) {
			case S_S_ROLE_SELECT:
				playerlist_update(peer_addr, NULL, new_state_param, NULL, NULL);
				break;

			case S_S_LOADING:
			case S_S_LOAD_ABORT:
				playerlist_update(peer_addr, NULL, "", NULL, NULL);
				break;
	
			case S_S_IDLE:
			case S_S_LOADED:
			case S_S_LOAD_ERROR:
			case S_S_GAME_OVER:
			case S_S_READY:
			case S_S_NONE:
			case S_S_START_CHECK:
			case S_S_GAME_PLAYING:
			case S_S_GAME_ERROR:
			case S_S_KILL:
			case S_S_SYNCING:
				break;
	}
}


/*
 * wrapper for session_message_action_switch
 */
void session_message_action_switch_param(int time, char cmd, int new_state, const char *new_state_param, const char *peer_addr, int remote)
{
	const char *new_game = NULL;
	const char *new_my_role = NULL;

	/* get meaning of state parameter */
	switch (new_state) {
		case S_S_LOADING:
			new_game = new_state_param;
			break;
			
		case S_S_ROLE_SELECT:
			if (!remote)
				new_my_role = new_state_param;
			break;
			
	}

	session_message_action_switch(time, cmd, new_state, new_game, new_my_role, peer_addr, remote);
}


/*
 * cmd: 'i' = info, 'c' = change, etc. -- it is second letter from command (Sc, Si, ...)
 */
void session_message_action_switch(int time, char cmd, int new_state, const char *new_game, const char *new_my_role, const char *peer_addr, int remote)
{
	/* action mask -- this replaces ugly multidimensional switch */
	static const enum {
		nop,	/* nothing to do */
		lg,	/* loading */
		rs,	/* role select confirmed */
		rd,	/* is everybody ready ? */
		sc,	/* start check */
		sf	/* start check failed */
	} local_action[2][S_S_MAX][S_S_MAX] = {
		{	
			/*** LOCAL ***/
			/*         \ incomming  N   I   L   L   L   L   R   R   S   G   G   G   K   S  *
			 *  current \           O   D   G   D   A   E   S   D   C   P   O   E   L   Y  */
			/* NONE           */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* IDLE           */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOADING        */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOADED         */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOAD ABORT     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOAD ERROR     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* ROLE SELECT    */{   0,  0,  0,  0,  0,  0, rs,  0,  0,  0,  0,  0,  0,  0  },
			/* READY	  */{   0,  0,  0,  0,  0,  0,  0, rd,  0,  0,  0,  0,  0,  0  },
			/* START CHECK    */{   0, sf, sf, sf, sf, sf, sf, sf, sc,  0,  0,  0,  0,  0  },
			/* GAME PLAYING   */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* GAME OVER      */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* GAME ERROR     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* KILL           */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* SYNCING        */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
		}, {	
			/*** REMOTE ***/
			/*         \ incomming  N   I   L   L   L   L   R   R   S   G   G   G   K   S  *
			 *  current \           O   D   G   D   A   E   S   D   C   P   O   E   L   Y  */
			/* NONE           */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* IDLE           */{   0,  0, lg,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOADING        */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOADED         */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOAD ABORT     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* LOAD ERROR     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* ROLE SELECT    */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* READY	  */{   0,  0,  0,  0,  0,  0,  0, rd,  0,  0,  0,  0,  0,  0  },
			/* START CHECK    */{   0, sf, sf, sf, sf, sf, sf, sf, sc,  0,  0,  0,  0,  0  },
			/* GAME PLAYING   */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* GAME OVER      */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* GAME ERROR     */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* KILL           */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
			/* SYNCING        */{   0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0  },
		}
	};
						/* N   I   L   L   L   L   R   R   S   G   G   G   K   S  *
						 * O   D   G   D   A   E   S   D   C   P   O   E   L   Y  */
	static const int ready_mask[S_S_MAX]  = {  1,  0,  0,  0,  0,  1,  0,  1,  0,  0,  0,  0,  0,  0  };
	static const int start_mask[S_S_MAX]  = {  1,  0,  0,  0,  0,  1,  0,  0,  1,  0,  0,  0,  0,  0  };
	static const int wait_mask[S_S_MAX]   = {  1,  0,  0,  0,  0,  1,  0,  1,  1,  0,  0,  0,  0,  0  };

	static const char *chat_message[S_S_MAX] = {
		/* NONE           */ NULL,
		/* IDLE           */ NULL,
		/* LOADING        */ NULL,
		/* LOADED         */ NULL,
		/* LOAD ABORT     */ "%s odmítá hru.",
		/* LOAD ERROR     */ "%s je mimo.",
		/* ROLE SELECT    */ NULL,
		/* READY	  */ "%s chce začít.",
		/* START CHECK    */ "%s potvrzuje zahájení hry.",
		/* GAME PLAYING   */ NULL,
		/* GAME OVER      */ "%s opouští hru.",
		/* GAME ERROR     */ "%s je mimo.",
		/* KILL           */ NULL,
		/* SYNCING        */ NULL,
	};
	

	/* log message to chat */
	if (chat_message[new_state])
		gui_append_to_chat(NULL, GUI_CHAT_SYS, _(chat_message[new_state]), playerlist_get_nick(peer_addr));
	
	/* update global state */
	session_update_global_state(time, new_state, new_game, new_my_role);
	
	/* perform action */
	if (cmd == 'c') {
		/* act on change only */
		DBG_MSG(3, "%s", remote ? "remote":"local");
		switch(local_action[remote][status_get(session_status)][new_state]) {
			case nop: /* sync to global state */
				DBG_MSG(3, "sync only");
				session_calculate_next_state();  /* mostly do nothing */
				break;

			case lg: /* loading */
				DBG_MSG(3, "loading");
				gui_append_to_chat(NULL, GUI_CHAT_SYS, _("%s vybírá hru \"%s\""), playerlist_get_nick(peer_addr), new_game);
				session_calculate_next_state();  /* mostly do nothing */
				break;
				
			case rs: /* role select confirmation has arrived */
				DBG_MSG(3, "role select");
				status_switch(session_status, S_S_LOADED, NULL);
				break;

			case rd: /* is everybody ready ? */
				DBG_MSG(3, "ready");
				if (playerlist_have_all_states(ready_mask, S_S_MAX)) {
					session_send_game_check_rq();
				}
				break;

			case sc: /* start check ok, game has been started */
				DBG_MSG(3, "start  check ok");
				if (playerlist_have_all_states(start_mask, S_S_MAX)) {
					gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Hra je zahájena."));
					status_switch(session_status, S_S_GAME_PLAYING, NULL);
				} else if (!playerlist_have_all_states(wait_mask, S_S_MAX)) {
					status_switch(session_status, S_S_LOADED, NULL);
				}
				break;

			case sf: /* somebody screwed start */
				DBG_MSG(3, "start failed");
				gui_append_to_chat(NULL, GUI_CHAT_SYS, _("%s odmítá zahájení hry."), playerlist_get_nick(peer_addr));
				status_switch(session_status, S_S_LOADED, NULL);
				break;
				
		}
	} else {
		/* info and other message types are ignored */
		DBG_MSG(3, "info: syncing only");
		session_calculate_next_state();	
	}
}


/*
 * update info about global game state
 */
void session_update_global_state(int time, int state, const char *game, const char *my_role)
{
	int alias[S_S_MAX] = {		/* -1 == ignore */
		/* S_S_NONE		*/ -1,
		/* S_S_IDLE		*/ S_S_IDLE,	
		/* S_S_LOADING		*/ S_S_LOADING,
		/* S_S_LOADED		*/ S_S_LOADED,
		/* S_S_LOAD_ABORT	*/ S_S_LOAD_ABORT,
		/* S_S_LOAD_ERROR	*/ -1,
		/* S_S_ROLE_SELECT	*/ S_S_LOADED,
		/* S_S_READY		*/ S_S_READY,
		/* S_S_START_CHECK	*/ S_S_START_CHECK,
		/* S_S_GAME_PLAYING	*/ S_S_GAME_PLAYING,
		/* S_S_GAME_OVER	*/ S_S_GAME_OVER,
		/* S_S_GAME_ERROR	*/ -1,
		/* S_S_KILL		*/ -1,
		/* S_S_SYNCING		*/ -1
	};
	int require[S_S_MAX][2] = {
		/* 0 = does not matter, 1 = must be set, -1 = unset */
					/* game, my_role */
		/* S_S_NONE		*/ { -1, -1 },
		/* S_S_IDLE		*/ { -1, -1 },
		/* S_S_LOADING		*/ { +1, -1 },
		/* S_S_LOADED		*/ { +1,  0 },
		/* S_S_LOAD_ABORT	*/ { +1, -1 },
		/* S_S_LOAD_ERROR	*/ { +1,  0 },
		/* S_S_ROLE_SELECT	*/ { +1,  0 },
		/* S_S_READY		*/ { +1,  0 },
		/* S_S_START_CHECK	*/ { +1,  0 },
		/* S_S_GAME_PLAYING	*/ { +1,  0 },
		/* S_S_GAME_OVER	*/ { +1,  0 },
		/* S_S_GAME_ERROR	*/ { +1,  0 },
		/* S_S_KILL		*/ { +1,  0 },
		/* S_S_SYNCING		*/ { -1, -1 }
	};

	if (state > session_status->max_st) {
		DBG_MSG(1, "wrong state");
	}

	DBG_MSG(3, "time = %d, state = %s, game = %s, my_role = %s",
			time, status_get_name_by_id(session_status, state), game, my_role);
	
	if (time > session_global_st.time) {
		int have_game = (session_global_st.game && session_global_st.game[0]) || game != NULL;
		int have_my_role = (session_global_st.my_role && session_global_st.my_role[0]) || my_role != NULL;
		
		/* update time */
		session_global_st.time = time;

		/* update last status */
		session_global_st.last_status = session_global_st.status;

		if (alias[state] != -1 && status_compare_states(session_status, session_global_st.status, state) >= 0
				&& ((have_game && require[state][0] == 1) || require[state][0] <= 0)
				&& ((have_my_role && require[state][1] == 1) || require[state][1] <= 0))
		{
			/* update game */
			if (require[state][0] == -1) {
				free(session_global_st.game);
				session_global_st.game = strdup("");
			} else if (game) {
				free(session_global_st.game);
				session_global_st.game = strdup(game);
			}

			/* update my role */
			if (require[state][1] == -1) {
				free(session_global_st.my_role);
				session_global_st.my_role = strdup("");
			} if (my_role) {
				free(session_global_st.my_role);
				session_global_st.my_role = strdup(my_role);
			}

			/* update state */
			if (status_compare_states(session_status, session_global_st.status, state) != 0)
				session_global_st.status = alias[state];
			
			/* there are few indicators */
			gui_update_main_window();

			DBG_MSG(3, "global state updated");
		}

		DBG_MSG(4, "");
		DBG_MSG(4, "Global state:");
		DBG_MSG(4, "   time:       %ld", session_global_st.time);
		DBG_MSG(4, "   state:      %s", status_get_name_by_id(session_status, session_global_st.status));
		DBG_MSG(4, "   last state: %s", status_get_name_by_id(session_status, session_global_st.last_status));
		DBG_MSG(4, "   game:       %s", session_global_st.game);
		DBG_MSG(4, "   my_role:    %s", session_global_st.my_role);
		DBG_MSG(4, "");
	} else {
		DBG_MSG(3, "current global state is fresh");
	}
}


/*
 * Select next state when global and local game states are not sync
 */
void session_calculate_next_state(void)
{
	/* few aliases */
	int	id = S_S_IDLE,
		lg = S_S_LOADING,
		la = S_S_LOAD_ABORT,
		rd = S_S_READY,
		sc = S_S_START_CHECK,
		//gp = S_S_GAME_PLAYING,
		go = S_S_GAME_OVER,
		ge = S_S_GAME_ERROR,
		kl = S_S_KILL;

	const int next_state_table[S_S_MAX][S_S_MAX] = {
		/*           \  target       N   I   L   L   L   L   R   R   S   G   G   G   K   S  *
		 *   current  \              O   D   G   D   A   E   S   D   C   P   O   E   L   Y  */
		/* S_S_NONE		*/{ -1, id, id, id, id, id, id, id, id, id, id, id, id, id  },
		/* S_S_IDLE		*/{ -1, -1, lg, lg, -1, -1, lg, lg, lg, ge, -1, -1, -1, -1  },
		/* S_S_LOADING		*/{ -1, kl, -1, -1, la, -1, -1, -1, -1, -1, -1, -1, -1, -1  },
		/* S_S_LOADED		*/{ -1, -1, -1, -1, la, -1, -1, rd, -1, kl, kl, -1, -1, -1  },
		/* S_S_LOAD_ABORT	*/{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1  },
		/* S_S_LOAD_ERROR	*/{ -1, id, -1, -1, la, -1, -1, -1, -1, ge, id, ge, -1, -1  },
		/* S_S_ROLE_SELECT	*/{ -1, -1, -1, -1, la, -1, -1, -1, -1, -1, kl, -1, -1, -1  },
		/* S_S_READY		*/{ -1, -1, -1, rd, la, -1, -1, rd, -1, -1, kl, -1, -1, -1  },
		/* S_S_START_CHECK	*/{ -1, -1, -1, -1, la, -1, -1, -1, sc, -1, kl, -1, -1, -1  },
		/* S_S_GAME_PLAYING	*/{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, go, -1, -1, -1  },
		/* S_S_GAME_OVER	*/{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1  },
		/* S_S_GAME_ERROR	*/{ -1, id, lg, -1, -1, -1, -1, -1, -1, -1, id, -1, -1, -1  },
		/* S_S_KILL		*/{ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1  },
		/* S_S_SYNCING		*/{ -1, -1, lg, lg, lg, -1, lg, lg, lg, lg, kl, -1, -1, -1  },
	};                                                                                            
	const int check_game[S_S_MAX] =   {  0,  0,  1,  1,  0,  1,  1,  1,  1,  1,  0,  1,  0,  1  };
	
	int next, st;
	const char *local_game, *my_role;
	
	DBG_MSG(2, "");
	
	/* lookup in next_state_table: use current status if we have same game loaded */
	local_game = option_get_string("main", "game", "backend");
	my_role = option_get_string("main", "game", "my_role");
	st = next = status_get(session_status);

	/* is correct game loaded ? */
	if (check_game[session_global_st.status] && strcmp(local_game ? : "", session_global_st.game ? : "") != 0) {
		DBG_MSG(3, "Wrong game loaded.");
		/* wrong game loaded */
		if (st == S_S_IDLE || st == S_S_LOAD_ERROR) {
			next = S_S_LOADING;
		} else if (st != S_S_KILL) {
			next = S_S_KILL;
		}
	/* is role selected ? */
	} else if (st == S_S_LOADED && strcmp(session_global_st.my_role ? : "", my_role ? : "") != 0) {
		DBG_MSG(3, "Changing role...");
		next = S_S_ROLE_SELECT;
	} else {
		/* all ok */
		next = next_state_table[st][session_global_st.status]; 
	}
	DBG_MSG(3, "next state is %s(%d)", next >= 0 ? status_get_name_by_id(session_status, next) : "same as current ", next);
	
	/* switch to next state */
	if (next >= 0) {
		switch (next) {
			case S_S_LOADING:
				status_switch(session_status, S_S_LOADING, session_global_st.game);
				break;

			case S_S_ROLE_SELECT:
				status_switch(session_status, S_S_ROLE_SELECT, session_global_st.my_role);
				break;

			case S_S_NONE:
			case S_S_IDLE:
			case S_S_LOADED:
			case S_S_LOAD_ABORT:
			case S_S_LOAD_ERROR:
			case S_S_READY:
			case S_S_START_CHECK:
			case S_S_GAME_PLAYING:
			case S_S_GAME_OVER:
			case S_S_GAME_ERROR:
			case S_S_KILL:
			case S_S_SYNCING:
				status_switch(session_status, next, NULL);
				break;

		}
	}
}


void session_send_sync_rq(void)
{
	int st = status_get(session_status);

	DBG_MSG(2, "");

	if (st == S_S_LOAD_ERROR || st == S_S_GAME_ERROR) {
		status_switch(session_status, S_S_SYNCING, NULL);
	} else {
		enter_syncing(session_status);
	}
}


void session_send_state_info(const char *address)
{	
	MESSAGE *msg;
	char *role;
	
	DBG_MSG(2, "address = \"%s\"", address);

	if (address)
		playerlist_get(address, NULL, &role, NULL, NULL, NULL, NULL, NULL);
	else
		role = NULL;
	
	msg = message_create('S', 'N');
	message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Si",
			MESSAGE_STRING, session_status->current_desc ? session_status->current_desc : status_get_name(session_status),
			MESSAGE_INTEGER, session_global_st.time,
			MESSAGE_STRING, status_get_abbr_by_id(session_status, session_global_st.status) ? :"?",
			MESSAGE_STRING, session_global_st.game,
			MESSAGE_STRING, status_get_abbr_by_id(session_status, session_status->current_st) ? :"?",
			MESSAGE_STRING, session_status->current_sub_state,
			MESSAGE_STRING, option_get_string("main", "game", "my_role"),
			MESSAGE_STRING, address,
			MESSAGE_STRING, role ? :"",
			MESSAGE_END);
	message_send(msg);
	message_destroy(msg);
	
	DBG_MSG(2, "done");
}


int session_is_synced(void)
{
	return status_compare_states(session_status, session_global_st.status, status_get(session_status)) == 0;
}

void session_send_game_check_rq(void) 
{
	MESSAGE *msg;
	
	/* send check request */
	/*MSG* Gc
	 *
	 * Game Check
	 *
	 * Check player roles and allow/deny game start.
	 */
	msg = message_create('S', 'G');
	message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Gc", MESSAGE_END);
	message_send(msg);
	message_destroy(msg);

	gui_append_to_chat(NULL, GUI_CHAT_SYS, _("Kontroluji podmínky zahájení hry..."));
}

void session_surrender(void) 
{
	MESSAGE *msg;
	int st = status_get(session_status);

	if (st == S_S_GAME_PLAYING || st == S_S_GAME_ERROR) {;
		msg = message_create('S', 'N');
		message_append(msg, MESSAGE_PART_DATA, MESSAGE_STRING, "Su", MESSAGE_END);
		message_send(msg);
		message_destroy(msg);
	}
}

