/* PureAdmin
 * Copyright (C) 2003 Isak Savo
 *
 *  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.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Callback-functions for the main window GUI.
 *
 * Copyright (C) 2003- Isak Savo
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "mainwin.h"
#include "globals.h"
#include "srv_comm.h"
#include "gui_helper.h"
#include "helper.h"
#include "cfg.h"
#include "logfile.h"
/** Private functions **/

/* Called for all remaining elements in the hash table. It adds those entries to
 * a GList */
static gboolean rmd_users_func (gchar *key, gchar *val, GList **lst_p)
{
	*lst_p = g_list_append (*lst_p, g_strdup(key));

	return TRUE;
}

static GList *find_completed_activities (GList *old, GList *new)
{
	GList *node;
	GList *rv = NULL;
	
	if (old == NULL)
		return NULL;
	if (new == NULL)
		return old;
	
	node = old;
	while (node)
	{
		guint id_oldlist = ((PActivity*) node->data)->id;
		GList *new2 = new;
		
		/* Search the entire new list for any occurrance of the current activity in the old list */
		while (new2)
		{
			guint id_newlist = ((PActivity*) new2->data)->id;
			if (id_oldlist > id_newlist) {
				/* No need to search the list anymore, we know it doesn't exist in the new list since
				 * both lists are sorted by ID*/
				rv = g_list_append (rv, node->data);
				break;
			}
			if (id_oldlist == id_newlist)
				/* The same activity exists in both lists, nothing more to do this iteration */
				break;
			new2 = g_list_next (new2);
		}
		node = g_list_next (node);
	}

	return rv;
}

struct usrlist_diff {
	GList *new_users;
	GList *rmd_users;
};

static struct usrlist_diff *compare_user_lists (GList *L1, GList *L2)
{
	GHashTable *h;
	GList *node;
	POnlineUser *u;
	struct usrlist_diff *rv = g_new0 (struct usrlist_diff, 1);
	
	h = g_hash_table_new_full ((GHashFunc) g_str_hash,
				   (GEqualFunc) g_str_equal,
				   (GDestroyNotify) g_free,
				   NULL);
	node = L1;
	while (node) {
		/* Add all elements from list 1 to a hash table with username@host as key and random junk as value
		 * (we're only interested in O(1) lookup of users, not any values) */
		gchar *tmp;
		u = (POnlineUser *) node->data;
		tmp = g_strconcat (u->username, "@", u->remote_addr, NULL);
		
		g_hash_table_insert (h, tmp, "random junk");
		node = node->next;
	}

	node = L2;
	while (node)
	{
		gchar *tmp;
		u = (POnlineUser *) node->data;
		tmp = g_strconcat (u->username, "@", u->remote_addr, NULL);
		if (g_hash_table_lookup (h, tmp)) {
			/* User:Address pair existed earlier */
			g_hash_table_remove (h, tmp);
		} else {
			/* Whooo, a new user! */
			rv->new_users = g_list_append (rv->new_users, tmp);
			g_hash_table_remove (h, tmp);
		}
		node = node->next;
	}
	if (g_hash_table_size (h) > 0) {
		/* One or more users have disconnected */
		g_hash_table_foreach_remove (h,(GHRFunc) rmd_users_func, (gpointer) &(rv->rmd_users));
	}

	return rv;
}

void user_list_select_user (const gchar *user, const gchar *remote_addr)
{
	GtkTreeModel *model = NULL;
	GtkTreeIter iter;
	gboolean more;
	model = gtk_tree_view_get_model (GTK_TREE_VIEW(MW("tree_users")));

	g_return_if_fail (user != NULL && *user != '\0');
	g_return_if_fail (model != NULL);
	more = gtk_tree_model_get_iter_first (model, &iter);
	while (more)
	{
		gchar *cur_user, *cur_addr;
		gtk_tree_model_get(model, &iter,
				   COL_USR_USER, &cur_user,
				   COL_USR_HOST, &cur_addr,
				   -1);
		if (g_str_equal (cur_user, user) && g_str_equal (cur_addr, remote_addr))
		{
			gtk_tree_selection_select_iter (gtk_tree_view_get_selection
							(GTK_TREE_VIEW(MW("tree_users"))),
							&iter);
			break;
		}
		more = gtk_tree_model_iter_next (model, &iter);
	}
}

void init_gui_mainwindow (void)
{
	GtkWidget *pane;
	GdkPixbuf *pbuf;
	GList *list = NULL;
	gint i;
#define NUM_WINDOW_ICONS 4	
	const gchar *icons[NUM_WINDOW_ICONS] = { "pureadmin-16x16.png", "pureadmin-24x24.png",
						 "pureadmin-32x32.png", "pureadmin-48x48.png" };
	
	if (cfg.save_window_geometry == FALSE)
	{
		pur_log_nfo ("Will not restore window geometry, option turned off");
		return;
	}
	/* Set position */
	gtk_window_move (GTK_WINDOW (MW("main_window")), cfg.win_pos[0], cfg.win_pos[1]);
	/* Set size */
	gtk_window_resize (GTK_WINDOW (MW("main_window")), cfg.win_size[0], cfg.win_size[1]);
	/* Divider position */
	pane = MW("hpaned1");
	if (!pane) {
		pur_log_wrn ("Unable to get hpaned widget");
	}
	else
		gtk_paned_set_position (GTK_PANED (pane), cfg.div_pos);

	for (i = 0; i < NUM_WINDOW_ICONS; i++)
	{
		GError *err;
		gchar *fname;
		
		err = NULL;
		fname = g_build_filename (pixmapdir, icons[i], NULL);
		pbuf = gdk_pixbuf_new_from_file (fname, &err);
		if (!pbuf) {
			pur_log_wrn ("Couldn't load icon file %s: %s", icons[i], err->message);
			g_error_free (err);
		}
		list = g_list_append (list,(gpointer) pbuf);
		g_free (fname);
	}
	if (list)
	{
		GList *node = list;

		gtk_window_set_default_icon_list (list);
				
		while (node) {
			g_object_unref (G_OBJECT(node->data));
			node = g_list_next (node);
		}
		g_list_free (list);
	}
	else
		pur_log_wrn ("No icons existed in %s", pixmapdir);
	
	show_status_icon ("PureAdmin started successfully");
#undef NUM_WINDOW_ICONS
}

void init_gui_logwindow (void)
{
	GtkWidget *tmp, *lbl;
	GtkTextBuffer *text;
	pur_log_dbg ("Initializing gui_logwindow");
	tmp = MW("logview");
   
	text = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tmp));

	logview_tags[LOG_INFO] = gtk_text_buffer_create_tag (text, "info",
							     "foreground", "blue", NULL);
	logview_tags[LOG_WARNING] = gtk_text_buffer_create_tag (text, "warning",
								"foreground", "#2e9344", NULL);
	logview_tags[LOG_ERROR] = gtk_text_buffer_create_tag (text, "error",
							      "foreground", "red", NULL);
	logview_tags[LOG_NOTICE] = gtk_text_buffer_create_tag (text, "notice",
							       "foreground", "#a427a9", NULL);
	logview_tags[LOG_DEBUG] =  gtk_text_buffer_create_tag (text, "debug",
							       "foreground", "#899f22", NULL);
   
	lbl = MW("lbl_logfilename");
#ifdef HAVE_LIBFAM
	gtk_label_set_text (GTK_LABEL (lbl), cfg.logfile);
#else
	/* Disable logview controls and print a message explaining why! */
	log_display_error_text (0);
#endif
}

void init_gui_activities (void)
{
	GtkListStore *model;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;
	GtkTreeView *tree_activity;
	//GtkTreeSelection *selection;
	
	pur_log_dbg ("Initializing gui_activities");
	/* Set-up treeview */
	tree_activity = GTK_TREE_VIEW (MW("tree_activity"));
	
	g_return_if_fail (tree_activity != NULL);
	
	model  = gtk_list_store_new (N_ACT_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_UINT);
   
	gtk_tree_view_set_model (GTK_TREE_VIEW (tree_activity), GTK_TREE_MODEL (model));
	g_object_unref (G_OBJECT (model));
	
	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_activity)),
				     GTK_SELECTION_SINGLE);

   	renderer = gtk_cell_renderer_pixbuf_new();
	column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
							   "pixbuf", COL_ACT_ICON,
							   NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(tree_activity), column);
   
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes (NULL, renderer,
							   "text", COL_ACT_TEXT,
							   NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(tree_activity), column);

/* 	/\* Hook-up some signals *\/ */
/* 	selection = gtk_tree_view_get_selection (tree_activity); */
/* 	g_signal_connect ((gpointer) selection, "changed", */
/* 			  G_CALLBACK (booh), */
/* 			  NULL); */

}

void init_gui_online_users (void)
{
	gint i;
	gchar *titles[] =  {
	  " ", _("User"), _("Host"), _("Connections")
	}; 
	GtkListStore *model;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;
	GtkTreeView *tree_users;
   
	/* Set-up treeview */
	pur_log_dbg ("Initializing gui_online_users");

	tree_users = GTK_TREE_VIEW (MW("tree_users"));
	model  = gtk_list_store_new (N_USR_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT);
   
	gtk_tree_view_set_model (GTK_TREE_VIEW (tree_users), GTK_TREE_MODEL (model));
	g_object_unref (G_OBJECT (model));
	gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_users)),
				     GTK_SELECTION_SINGLE);

   
	renderer = gtk_cell_renderer_pixbuf_new();
	column = gtk_tree_view_column_new_with_attributes (titles[0], renderer,
							   "pixbuf", COL_USR_ICON,
							   NULL);
	gtk_tree_view_append_column (GTK_TREE_VIEW(tree_users), column);
   
	for (i = 1; i < N_USR_COLUMNS; i++)
	{
		renderer = gtk_cell_renderer_text_new();
		column = gtk_tree_view_column_new_with_attributes (titles[i], renderer,
								   "text", i,
								   NULL);
		gtk_tree_view_append_column (GTK_TREE_VIEW(tree_users), column);
	}
   
	/* Hook-up some signals */
	/*   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (main_treeview));
	     g_signal_connect ((gpointer) selection, "changed",
	     G_CALLBACK (on_list_selection_changed),
	     NULL);*/
}

gboolean timeout_update_activity (gpointer data)
{
	GList *act_list, *usr_list, *node;
	PActivity *activity;
	POnlineUser *user;
	struct usrlist_diff *diff = NULL;
	
	static GdkPixbuf *icon_up = NULL;
	static GdkPixbuf *icon_down = NULL;
	static GdkPixbuf *icon_user = NULL;
	static GtkTreeView *tree_activity = NULL, *tree_users = NULL;
	static GtkTreeModel *model = NULL, *model_users = NULL;
	static GtkTreeSelection *sel = NULL, *sel_users = NULL;
	static GList *prev_usr_list;
	static GList *prev_act_list;
	GtkTreeIter iter;
	GtkTreePath *act_path, *usr_path;
	PGuiActivityRow arow;
	PGuiUserRow urow;
	gchar *percent;
	gboolean restore_selection_act = FALSE, restore_selection_usr = FALSE;

	if (G_UNLIKELY (!has_initialized(FALSE)))
		return TRUE;

	if (G_UNLIKELY (prog_exiting)) 
	{
		g_print ("Timeout aborting\n");
		return FALSE;
	}
	if (G_UNLIKELY (!icon_up))
	{
		icon_up = create_pixbuf ("up.png");
		icon_down = create_pixbuf ("down.png");
		icon_user = create_pixbuf ("user.png");
	}
	if (G_UNLIKELY (!tree_activity))
	{
		tree_activity =  (GtkTreeView *) MW("tree_activity");
		model = gtk_tree_view_get_model (tree_activity);
		sel = gtk_tree_view_get_selection (tree_activity);
      
		tree_users = (GtkTreeView *) MW("tree_users");
		model_users = gtk_tree_view_get_model (tree_users);
		sel_users = gtk_tree_view_get_selection (tree_users);
	}
	restore_selection_act = gtk_tree_selection_get_selected (sel, &model, &iter);
	if (restore_selection_act)
		act_path = gtk_tree_model_get_path (model, &iter);
	else
		act_path = NULL;

	restore_selection_usr = gtk_tree_selection_get_selected (sel_users, &model_users, &iter);
	if (restore_selection_usr)
		usr_path = gtk_tree_model_get_path (model_users, &iter);
	else
		usr_path = NULL;
	
	gui_clear_activity_list ();
	gui_clear_users_list ();
   
	act_list = srv_get_activities ();
	node = g_list_first (act_list);
	while (node)
	{
		activity = (PActivity *) node->data;
		if (activity->state == FTPWHO_STATE_DOWNLOAD)
			arow.icon = icon_down;
		else if (activity->state == FTPWHO_STATE_UPLOAD)
			arow.icon = icon_up;
		else
		{
			/* Only append actual actions (DL/UL) */
			node = g_list_next (node);
			continue;
		}
		arow.id = activity->id;
      
		if (activity->download_total_size > 0)
			percent = g_strdup_printf ("%.2f", 100 * (double) activity->download_current_size / (double) activity->download_total_size);
		else
			percent = g_strdup ("??");
		arow.text = g_strdup_printf ("%s (%s):'%s' - (%s%%)", activity->username, activity->remote_addr, 
					     activity->filename, percent);
		gui_add_to_activities (arow);
		node = g_list_next (node);
		g_free (arow.text);
		g_free (percent);
	}
   
	if (restore_selection_act)
	{
		gtk_tree_selection_select_path (sel, act_path);
		
		g_signal_emit_by_name ((gpointer) tree_activity, "cursor_changed", tree_activity, NULL);
	}
   
	/* Update users list */
	usr_list = srv_get_connected_users (g_list_first (act_list));
	node = usr_list;
	urow.icon = icon_user;
	while (node)
	{
		user = (POnlineUser *) node->data;
		urow.user = string_from_vuser_locale (user->username);
		urow.host = g_strdup (user->remote_addr);
		urow.num_connections = user->num_connections;
		gui_add_to_online_users (urow);
		
		g_free (urow.user);
		g_free (urow.host);
		node = g_list_next (node);
	}
	
	diff = compare_user_lists (prev_usr_list, usr_list);
	if (diff->new_users || diff->rmd_users) {
		GString *msg = g_string_new ("");
		gint len = g_list_length (diff->new_users);
		
		if (len == 1) {
			gchar *tmp = pur_elipzise ((gchar *) diff->new_users->data, 20);
			g_string_append_printf (msg, "New connection from %s\n", tmp);
		}
		else if (len > 1)
			g_string_append_printf (msg, "%d new connections\n", len);

		len = g_list_length (diff->rmd_users);
		if (len == 1) {
			gchar *tmp = pur_elipzise ((gchar *) diff->rmd_users->data, 20);
			g_string_append_printf (msg, "%s has disconnected\n", tmp);
		}
		else if (len > 1)
			g_string_append_printf (msg, "%d users has disconnected\n", len);

		if (msg->str[msg->len-1] == '\n')
			g_string_truncate (msg, msg->len - 1);
		
		show_status_icon (msg->str);
		g_string_free (msg, TRUE);
		g_list_foreach (diff->new_users, (GFunc) g_free, NULL);
		g_list_foreach (diff->rmd_users, (GFunc) g_free, NULL);
		g_list_free (diff->new_users);
		g_list_free (diff->rmd_users);
		g_free (diff);
	}
	free_pouser_list (prev_usr_list);
	prev_usr_list = usr_list;

	if (cfg.always_notify_activities)
	{
		GList *done_activities = find_completed_activities(prev_act_list, act_list);
		node = done_activities;
		while (node)
		{
			PActivity *act = (PActivity *) node->data;
			gchar *body = g_strdup_printf (_("User '%s' has finished %s the file '%s'"), act->username,
						       act->state == FTPWHO_STATE_DOWNLOAD ? _("downloading") : _("uploading"),
						       act->filename);
			send_notification (_("FTP Activity Completed"), body);
			node = g_list_next (node);
		}
	}
	free_activity_list (prev_act_list);
	prev_act_list = act_list;

	if (restore_selection_usr)
		gtk_tree_selection_select_path (sel_users, usr_path);

	//	gui_update_menu_sensivity ();

	/* Always return TRUE */
	return TRUE;
}


syntax highlighted by Code2HTML, v. 0.9.1