/*
 * backend.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 <gtk/gtk.h>
#include <string.h>
#include <assert.h>
#include <glib.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include "backend.h"

#include "gui.h"
#include "status.h"
#include "message.h"

#define DEBUG		0
#include "debug.h"

extern GtkWidget *main_window;

BACKEND *backend_network = NULL;
BACKEND *backend_game = NULL;


static gboolean on_backend_event (GIOChannel *source, GIOCondition condition, gpointer data) 
{
	//const int buf_size = 4096;
	const int buf_size = 256;
	gchar buf[buf_size];
	gsize rd = 0;
	GIOStatus ret = G_IO_STATUS_NORMAL;
	char *cmd, *str;
	GError *err = NULL;
	BACKEND *backend = (BACKEND *) data;
	int parsed, i;
	//STATUS new_status;

	DBG_MSG(6, "on_backend_event... (condition =%s%s%s%s%s%s)",
			/* this is realy useful staff */
			condition & G_IO_IN   ? " G_IO_IN"  :"",
			condition & G_IO_OUT  ? " G_IO_OUT" :"",
			condition & G_IO_PRI  ? " G_IO_PRI" :"",
			condition & G_IO_ERR  ? " G_IO_ERR" :"",
			condition & G_IO_HUP  ? " G_IO_HUP" :"",
			condition & G_IO_NVAL ? " G_IO_NVAL":"");

	assert(source != NULL);
	assert(backend != NULL);


	/* try to read */
	if (condition & G_IO_IN)
		ret = g_io_channel_read_chars(source, buf, buf_size - 1, &rd, &err);

	/* check for errors */
	if (ret == G_IO_STATUS_ERROR) {
		DBG_MSG(4, "Error: %s", err->message);
		/* error */
		if (backend == backend_network)
			gui_append_to_netlog("\n-- Error: %s\n", err->message);
		else if (backend == backend_game)
			gui_append_to_gamelog("\n-- Error: %s\n", err->message);
		gui_append_to_chat("\n-- Error: %s\n", err->message);
		g_error_free(err);
	}

	/* process read data */
	if (rd > 0) {
		buf[rd] = 0;
		DBG_MSG(8, "Read %u bytes", rd);
		if (source == backend->ch_out) {
			/* parse message */
			DBG_MSG(8, "Processing data: \"%s\"", buf);
			for (i = 0; i < rd && i < buf_size; i++) {
				parsed = message_from_stream(backend->msg_src, 0, buf[i], &(backend->msg));
				if (parsed == 1 && backend->msg) {
					DBG_MSG(3, "Message received.");
					if (backend->log_enabled) {
						str = message_to_string(backend->msg, 0);
						if (backend == backend_network)
							gui_append_to_netlog("-> %s\n", str);
						else if (backend == backend_game)
							gui_append_to_gamelog("-> %s\n", str);
						free(str);
					}
	
					if (message_get(backend->msg, MESSAGE_STRING, &cmd, MESSAGE_END) && cmd)
						message_edit(backend->msg, 0, cmd[0]);
					message_send(backend->msg);
					message_destroy(backend->msg);
					backend->msg = NULL;
					gtk_main_iteration_do(FALSE);
				}
			}
		}
		else if (backend_network && source == backend_network->ch_err) {
			DBG_MSG(8, "It's network log.");
			gui_append_to_netlog("%s", buf);
			gtk_main_iteration_do(FALSE);
		}
		else if (backend_game && source == backend_game->ch_err) {
			DBG_MSG(8, "It's game log.");
			gui_append_to_gamelog("%s", buf);
			gtk_main_iteration_do(FALSE);
		} else {
			DBG_MSG(1, "Received data from unknown source!");
		}
	}
	
	/* remove dead channels -- HUP && IN means that pipe is closed and buffer is not empty */
	if (ret != G_IO_STATUS_NORMAL || condition & (G_IO_ERR | G_IO_NVAL) || ((condition & G_IO_HUP) && !(condition & G_IO_IN))) {
		/* remove source */
		if (source == backend->ch_in) {
			backend->ch_in = NULL;
			DBG_MSG(5, "... input is down");
		}
		if (source == backend->ch_out) {
			backend->ch_out = NULL;
			DBG_MSG(5, "... output is down");
		}
		if (source == backend->ch_err) {
			backend->ch_err = NULL;
			DBG_MSG(5, "... error output is down");
		}

		/* close source */
		g_io_channel_unref(source);

		/* if it was last, backend is gone */
		if (backend->ch_in == NULL && backend->ch_out == NULL && backend->ch_err == NULL) {
			DBG_MSG(2, "Backend is gone...");
			if(backend == backend_game) {
				switch_to_status(backend_network ? S_GAME_ENDING : S_OFFLINE);
			} else if (backend == backend_network) {
				if (backend_game)
					backend_kill(backend_game);
				switch_to_status(S_OFFLINE);
			}
			backend_cleanup(backend);
		}
		return FALSE;
	}
	return TRUE;
}


BACKEND *backend_spawn(char msg_src, const char *dir, char *argv[], int log_enabled)
{
	BACKEND *backend;
	GError *err = NULL;
	GtkWidget *dialog = NULL;

	DBG_MSG(5, "backend_spawn");
	assert(dir != NULL);
	assert(argv != NULL);

	backend = (BACKEND *) malloc(sizeof(BACKEND));
	if (backend) {
		backend->msg_src = msg_src;
		backend->msg = NULL;
		backend->log_enabled = log_enabled;
		
		DBG_MSG(2, "backend_spawn: Spawning \"%s\" (cwd = \"%s\") ...", argv[0], dir);
		if(!g_spawn_async_with_pipes(dir, argv, NULL, 0, NULL, (gpointer) backend,
				&backend->pid, &backend->fd_in, &backend->fd_out, &backend->fd_err, &err)) {
			/* error */
			dialog = gtk_message_dialog_new(GTK_WINDOW(main_window), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
					GTK_BUTTONS_CLOSE, _("Backend nemohl být spuštěn."));
			gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), err->message);
			gtk_dialog_run(GTK_DIALOG(dialog));
			gtk_widget_destroy(dialog);
			g_error_free(err);
			free(backend);
			return NULL;
		}

		backend->ch_in = g_io_channel_unix_new(backend->fd_in);
		g_io_channel_set_encoding(backend->ch_in, NULL, NULL);
		g_io_channel_set_buffered(backend->ch_in, FALSE);
		g_io_channel_set_close_on_unref(backend->ch_in, TRUE);
		g_io_add_watch(backend->ch_in, G_IO_ERR | G_IO_HUP | G_IO_NVAL, on_backend_event, (gpointer)backend);
		
		backend->ch_out = g_io_channel_unix_new(backend->fd_out);
		g_io_channel_set_encoding(backend->ch_out, NULL, NULL);
		g_io_channel_set_buffered(backend->ch_out, FALSE);
		g_io_channel_set_close_on_unref(backend->ch_out, TRUE);
		g_io_channel_set_flags(backend->ch_out, G_IO_FLAG_NONBLOCK | g_io_channel_get_flags(backend->ch_out), NULL);
		g_io_add_watch(backend->ch_out, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, on_backend_event, (gpointer)backend);
		
		backend->ch_err = g_io_channel_unix_new(backend->fd_err);
		g_io_channel_set_encoding(backend->ch_err, NULL, NULL);
		g_io_channel_set_buffered(backend->ch_err, FALSE);
		g_io_channel_set_close_on_unref(backend->ch_err, TRUE);
		g_io_channel_set_flags(backend->ch_err, G_IO_FLAG_NONBLOCK | g_io_channel_get_flags(backend->ch_err), NULL);
		g_io_add_watch(backend->ch_err, G_IO_IN | G_IO_PRI | G_IO_ERR | G_IO_HUP | G_IO_NVAL, on_backend_event, (gpointer)backend);
		
		DBG_MSG(2, "backend_spawn: Spawning ok. pid = %d", backend->pid);
	}
	return backend;
}


int backend_send(BACKEND *backend, const char *str)
{
	guint sent = 0;
	GError *err = NULL;

	DBG_MSG(6, "Sending: \"%s\"", str);
	assert(str != NULL);
	assert(backend != NULL);

	if (! backend->ch_in)
		return 0;

	if (backend->log_enabled) {
		if (backend == backend_network)
			gui_append_to_netlog("<- %s", str);
		else if (backend == backend_game)
			gui_append_to_gamelog("<- %s", str);
	}
	
	if (g_io_channel_write_chars(backend->ch_in, str, -1, &sent, &err) == G_IO_STATUS_ERROR) {
		/* error */
		gui_show_error_message(main_window, _("Chyba při posílání dat"), "write: %s", err->message);
		g_error_free(err);
		return 0;
	}
	DBG_MSG(6, "%d bytes sent.", sent);
	
	if (g_io_channel_flush(backend->ch_in, &err) == G_IO_STATUS_ERROR) {
		/* error */
		gui_show_error_message(main_window, _("Chyba při posílání dat"), "flush: %s", err->message);
		g_error_free(err);
		return 0;
	}
	DBG_MSG(8, "Data sent.");
	return 1;
}


void backend_kill(BACKEND *backend)
{
	DBG_MSG(2, "backend_kill");
	assert(backend != NULL);
	gui_append_to_netlog("-- Terminating backend...\n");
	if (backend->ch_in)
		g_io_channel_shutdown(backend->ch_in, TRUE, NULL);
	if (backend->ch_out)
		g_io_channel_shutdown(backend->ch_out, TRUE, NULL);
	if (backend->ch_err)
		g_io_channel_shutdown(backend->ch_err, TRUE, NULL);
	kill(backend->pid, SIGTERM);
}


void backend_cleanup(BACKEND *backend)
{
	DBG_MSG(5, "backend_cleanup");
	assert(backend != NULL);

	if (backend->msg)
		message_destroy(backend->msg);
	
	if (backend == backend_network) {
		gui_append_to_netlog("-- Spojení uzavřeno.\n\n");
		backend_network = NULL;
	} else if (backend == backend_game) {
		gui_append_to_gamelog("-- Backend skončil.\n\n");
		backend_game = NULL;
	}

	gui_update_main_window();

	free(backend);
}


