/**
 *  @file
 *  Run the dialog to select from lists of strings.
 *
 *  @author Jeremy A. Mowery jmowery@tresys.com
 *  @author Jason Tang jtang@tresys.com
 *
 *  Copyright (C) 2004-2007 Tresys Technology, LLC
 *
 *  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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <config.h>

#include "policy_components_view.h"

#include <assert.h>
#include <errno.h>
#include <string.h>
#include <apol/util.h>
#include <glade/glade.h>

struct polcomp_view
{
	GladeXML *xml;
	toplevel_t *top;
	apol_vector_t *log_items, *policy_items, *both_items, *included_items;
	GtkDialog *dialog;

	GtkRadioButton *log_radio, *policy_radio, *both_radio;

	GtkListStore *master_store;
	GtkTreeModel *inc_store, *exc_store;
	GtkTreeView *inc_view, *exc_view;
	GtkButton *inc_select_button, *inc_unselect_button, *exc_select_button, *exc_unselect_button;

	GtkButton *to_exc_button, *to_inc_button;
};

enum polcom_columns
{
	POINTER_COLUMN = 0, NAME_COLUMN, ISLOG_COLUMN, ISPOLICY_COLUMN, ISINC_COLUMN
};

static void policy_components_view_init_widgets(struct polcomp_view *pv)
{
	pv->dialog = GTK_DIALOG(glade_xml_get_widget(pv->xml, "PolicyComponentListsWindow"));
	assert(pv->dialog != NULL);
	gtk_window_set_transient_for(GTK_WINDOW(pv->dialog), toplevel_get_window(pv->top));

	pv->log_radio = GTK_RADIO_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompLogRadio"));
	pv->policy_radio = GTK_RADIO_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompPolicyRadio"));
	pv->both_radio = GTK_RADIO_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompBothRadio"));
	assert(pv->log_radio != NULL && pv->policy_radio != NULL && pv->both_radio != NULL);

	pv->inc_view = GTK_TREE_VIEW(glade_xml_get_widget(pv->xml, "PolicyCompIncView"));
	pv->exc_view = GTK_TREE_VIEW(glade_xml_get_widget(pv->xml, "PolicyCompExcView"));
	assert(pv->inc_view != NULL && pv->exc_view != NULL);

	pv->inc_select_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompIncSelectButton"));
	pv->inc_unselect_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompIncUnselectButton"));
	pv->exc_select_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompExcSelectButton"));
	pv->exc_unselect_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompExcUnselectButton"));
	assert(pv->inc_select_button != NULL && pv->inc_unselect_button &&
	       pv->exc_select_button != NULL && pv->exc_unselect_button);

	pv->to_exc_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompToExcButton"));
	pv->to_inc_button = GTK_BUTTON(glade_xml_get_widget(pv->xml, "PolicyCompToIncButton"));
	assert(pv->to_exc_button != NULL && pv->to_inc_button);
}

/******************** functions to build the lists ********************/

/**
 * Determine if a particular row should be visible based upon the
 * current radio button selection.  If the current item source is
 * 'Log' then return TRUE if the islog attribute is enabled.  If the
 * source is 'Policy' then return TRUE if ispolicy is enabled.
 * Otherwise the item is visible by default.
 */
static gboolean policy_components_view_is_visible_radio(struct polcomp_view *pv, gboolean islog, gboolean ispolicy)
{
	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pv->log_radio))) {
		return islog;
	} else if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(pv->policy_radio))) {
		return ispolicy;
	}
	return TRUE;
}

/**
 * Callback invoked to determine if a row should be visible to the
 * included view.
 */
static gboolean policy_components_view_is_visible_included(GtkTreeModel * model, GtkTreeIter * iter, gpointer data)
{
	struct polcomp_view *pv = (struct polcomp_view *)data;
	gboolean islog, ispolicy, isinc;
	gtk_tree_model_get(model, iter, ISLOG_COLUMN, &islog, ISPOLICY_COLUMN, &ispolicy, ISINC_COLUMN, &isinc, -1);
	if (!isinc) {
		return FALSE;
	}
	return policy_components_view_is_visible_radio(pv, islog, ispolicy);
}

/**
 * Callback invoked to determine if a row should be visible in the
 * included view.
 */
static gboolean policy_components_view_is_visible_excluded(GtkTreeModel * model, GtkTreeIter * iter, gpointer data)
{
	struct polcomp_view *pv = (struct polcomp_view *)data;
	gboolean islog, ispolicy, isinc;
	gtk_tree_model_get(model, iter, ISLOG_COLUMN, &islog, ISPOLICY_COLUMN, &ispolicy, ISINC_COLUMN, &isinc, -1);
	if (isinc) {
		return FALSE;
	}
	return policy_components_view_is_visible_radio(pv, islog, ispolicy);
}

static void policy_components_view_init_lists(struct polcomp_view *pv)
{
	GtkTreeIter iter;
	size_t i, j;
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *column;
	GtkTreeSelection *selection;

	if ((pv->both_items = apol_vector_create_from_vector(pv->log_items, NULL, NULL, NULL)) == NULL) {
		toplevel_ERR(pv->top, "Error generating union list: %s", strerror(errno));
		return;
	}
	if (pv->policy_items == NULL) {
		gtk_widget_set_sensitive(GTK_WIDGET(pv->policy_radio), FALSE);
		gtk_widget_set_sensitive(GTK_WIDGET(pv->both_radio), FALSE);
	} else {
		if (apol_vector_cat(pv->both_items, pv->policy_items) < 0) {
			toplevel_ERR(pv->top, "Error generating union list: %s", strerror(errno));
			return;
		}
		apol_vector_sort_uniquify(pv->both_items, apol_str_strcmp, NULL);
	}
	pv->master_store =
		gtk_list_store_new(ISINC_COLUMN + 1, G_TYPE_POINTER, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
	for (i = 0; i < apol_vector_get_size(pv->both_items); i++) {
		char *s = apol_vector_get_element(pv->both_items, i);
		gboolean is_log = FALSE, is_policy = FALSE, is_included = FALSE;
		if (apol_vector_get_index(pv->log_items, s, NULL, NULL, &j) == 0) {
			is_log = TRUE;
		}
		if (pv->policy_items != NULL && apol_vector_get_index(pv->policy_items, s, NULL, NULL, &j) == 0) {
			is_policy = TRUE;
		}
		if (apol_vector_get_index(pv->included_items, s, apol_str_strcmp, NULL, &j) == 0) {
			is_included = TRUE;
		}
		gtk_list_store_append(pv->master_store, &iter);
		gtk_list_store_set(pv->master_store, &iter,
				   POINTER_COLUMN, s,
				   NAME_COLUMN, s, ISLOG_COLUMN, is_log, ISPOLICY_COLUMN, is_policy, ISINC_COLUMN, is_included, -1);
	}

	pv->inc_store = gtk_tree_model_filter_new(GTK_TREE_MODEL(pv->master_store), NULL);
	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(pv->inc_store), policy_components_view_is_visible_included, pv,
					       NULL);
	pv->exc_store = gtk_tree_model_filter_new(GTK_TREE_MODEL(pv->master_store), NULL);
	gtk_tree_model_filter_set_visible_func(GTK_TREE_MODEL_FILTER(pv->exc_store), policy_components_view_is_visible_excluded, pv,
					       NULL);
	gtk_tree_view_set_model(pv->inc_view, pv->inc_store);
	gtk_tree_view_set_model(pv->exc_view, pv->exc_store);

	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes("Item names", renderer, "text", NAME_COLUMN, NULL);
	gtk_tree_view_column_set_clickable(column, FALSE);
	gtk_tree_view_column_set_resizable(column, FALSE);
	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
	gtk_tree_view_column_set_visible(column, TRUE);
	gtk_tree_view_append_column(pv->inc_view, column);
	renderer = gtk_cell_renderer_text_new();
	column = gtk_tree_view_column_new_with_attributes("Item names", renderer, "text", NAME_COLUMN, NULL);
	gtk_tree_view_column_set_clickable(column, FALSE);
	gtk_tree_view_column_set_resizable(column, FALSE);
	gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
	gtk_tree_view_column_set_visible(column, TRUE);
	gtk_tree_view_append_column(pv->exc_view, column);

	selection = gtk_tree_view_get_selection(pv->inc_view);
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
	selection = gtk_tree_view_get_selection(pv->exc_view);
	gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
}

/******************** signal handlers ********************/

static void policy_components_view_on_source_toggle(GtkToggleButton * widget, gpointer user_data)
{
	struct polcomp_view *pv = (struct polcomp_view *)user_data;
	/* clicking on the radio buttons emit two toggle signals, one for
	 * the original button and one for the new one.  thus only need to
	 * handle half of all signals */
	if (!gtk_toggle_button_get_active(widget)) {
		return;
	}
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->inc_store));
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->exc_store));
}

static void policy_components_gtk_tree_path_free(gpointer data, gpointer user_data __attribute__ ((unused)))
{
	gtk_tree_path_free((GtkTreePath *) data);
}

static void policy_components_gtk_tree_row_reference_free(gpointer data, gpointer user_data __attribute__ ((unused)))
{
	gtk_tree_row_reference_free((GtkTreeRowReference *) data);
}

static void policy_components_view_on_to_exc_click(GtkButton * widget __attribute__ ((unused)), gpointer user_data)
{
	struct polcomp_view *pv = (struct polcomp_view *)user_data;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(pv->inc_view);
	GList *r, *rows = gtk_tree_selection_get_selected_rows(selection, NULL);
	GList *refs = NULL;

	/* use references because the filtered store will be changing
	 * as master_store's ISINC_COLUMN value changes */
	for (r = rows; r != NULL; r = r->next) {
		GtkTreePath *path = (GtkTreePath *) r->data;
		GtkTreeRowReference *ref = gtk_tree_row_reference_new(pv->inc_store, path);
		refs = g_list_prepend(refs, ref);
	}

	for (r = refs; r != NULL; r = r->next) {
		GtkTreeRowReference *ref = (GtkTreeRowReference *) r->data;
		GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
		GtkTreeIter iter, child_iter;
		char *name;
		size_t i = 0;
		int retval;
		gtk_tree_model_get_iter(pv->inc_store, &iter, path);
		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(pv->inc_store), &child_iter, &iter);
		gtk_tree_model_get(GTK_TREE_MODEL(pv->master_store), &child_iter, NAME_COLUMN, &name, -1);
		gtk_list_store_set(pv->master_store, &child_iter, ISINC_COLUMN, FALSE, -1);
		retval = apol_vector_get_index(pv->included_items, name, apol_str_strcmp, NULL, &i);
		assert(retval == 0);
		name = apol_vector_get_element(pv->included_items, i);
		free(name);
		apol_vector_remove(pv->included_items, i);
	}

	g_list_foreach(refs, policy_components_gtk_tree_row_reference_free, NULL);
	g_list_free(refs);
	g_list_foreach(rows, policy_components_gtk_tree_path_free, NULL);
	g_list_free(rows);
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->inc_store));
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->exc_store));
}

static void policy_components_view_on_to_inc_click(GtkButton * widget __attribute__ ((unused)), gpointer user_data)
{
	struct polcomp_view *pv = (struct polcomp_view *)user_data;
	GtkTreeSelection *selection = gtk_tree_view_get_selection(pv->exc_view);
	GList *r, *rows = gtk_tree_selection_get_selected_rows(selection, NULL);

	/* use references because the filtered store will be changing
	 * as master_store's ISINC_COLUMN value changes */
	GList *refs = NULL;
	for (r = rows; r != NULL; r = r->next) {
		GtkTreePath *path = (GtkTreePath *) r->data;
		GtkTreeRowReference *ref = gtk_tree_row_reference_new(pv->exc_store, path);
		refs = g_list_prepend(refs, ref);
	}

	for (r = refs; r != NULL; r = r->next) {
		GtkTreeRowReference *ref = (GtkTreeRowReference *) r->data;
		GtkTreePath *path = gtk_tree_row_reference_get_path(ref);
		GtkTreeIter iter, child_iter;
		char *name;
		gtk_tree_model_get_iter(pv->exc_store, &iter, path);
		gtk_tree_model_filter_convert_iter_to_child_iter(GTK_TREE_MODEL_FILTER(pv->exc_store), &child_iter, &iter);
		gtk_tree_model_get(GTK_TREE_MODEL(pv->master_store), &child_iter, NAME_COLUMN, &name, -1);
		gtk_list_store_set(pv->master_store, &child_iter, ISINC_COLUMN, TRUE, -1);
		apol_vector_append(pv->included_items, strdup(name));
	}
	apol_vector_sort_uniquify(pv->included_items, apol_str_strcmp, NULL);
	g_list_foreach(refs, policy_components_gtk_tree_row_reference_free, NULL);
	g_list_free(refs);
	g_list_foreach(rows, policy_components_gtk_tree_path_free, NULL);
	g_list_free(rows);
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->inc_store));
	gtk_tree_model_filter_refilter(GTK_TREE_MODEL_FILTER(pv->exc_store));
}

static void policy_components_view_on_select_click(GtkButton * widget __attribute__ ((unused)), gpointer user_data)
{
	GtkTreeView *tv = GTK_TREE_VIEW(user_data);
	GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
	gtk_tree_selection_select_all(selection);
}

static void policy_components_view_on_unselect_click(GtkButton * widget __attribute__ ((unused)), gpointer user_data)
{
	GtkTreeView *tv = GTK_TREE_VIEW(user_data);
	GtkTreeSelection *selection = gtk_tree_view_get_selection(tv);
	gtk_tree_selection_unselect_all(selection);
}

static void policy_components_view_init_signals(struct polcomp_view *pv)
{
	g_signal_connect(pv->log_radio, "toggled", G_CALLBACK(policy_components_view_on_source_toggle), pv);
	g_signal_connect(pv->policy_radio, "toggled", G_CALLBACK(policy_components_view_on_source_toggle), pv);
	g_signal_connect(pv->both_radio, "toggled", G_CALLBACK(policy_components_view_on_source_toggle), pv);
	g_signal_connect(pv->to_exc_button, "clicked", G_CALLBACK(policy_components_view_on_to_exc_click), pv);
	g_signal_connect(pv->to_inc_button, "clicked", G_CALLBACK(policy_components_view_on_to_inc_click), pv);
	g_signal_connect(pv->inc_select_button, "clicked", G_CALLBACK(policy_components_view_on_select_click), pv->inc_view);
	g_signal_connect(pv->inc_unselect_button, "clicked", G_CALLBACK(policy_components_view_on_unselect_click), pv->inc_view);
	g_signal_connect(pv->exc_select_button, "clicked", G_CALLBACK(policy_components_view_on_select_click), pv->exc_view);
	g_signal_connect(pv->exc_unselect_button, "clicked", G_CALLBACK(policy_components_view_on_unselect_click), pv->exc_view);
}

apol_vector_t *policy_components_view_run(toplevel_t * top, GtkWindow * parent __attribute__ ((unused)), const char *title,
					  apol_vector_t * log_items, apol_vector_t * policy_items, apol_vector_t * included)
{
	struct polcomp_view pv;
	gint response;

	memset(&pv, 0, sizeof(pv));
	pv.top = top;
	pv.xml = glade_xml_new(toplevel_get_glade_xml(top), "PolicyComponentListsWindow", NULL);
	pv.log_items = log_items;
	pv.policy_items = policy_items;
	if (included == NULL) {
		pv.included_items = apol_vector_create(free);
	} else {
		pv.included_items = apol_vector_create_from_vector(included, apol_str_strdup, NULL, free);
	}
	apol_vector_destroy(&included);
	if (pv.included_items == NULL) {
		toplevel_ERR(pv.top, "Error creating dialog: %s", strerror(errno));
		return NULL;
	}

	policy_components_view_init_widgets(&pv);
	policy_components_view_init_lists(&pv);
	policy_components_view_init_signals(&pv);
	gtk_window_set_title(GTK_WINDOW(pv.dialog), title);
	do {
		response = gtk_dialog_run(pv.dialog);
	} while (response != GTK_RESPONSE_CLOSE);

	gtk_widget_destroy(GTK_WIDGET(pv.dialog));
	g_object_unref(pv.master_store);
	g_object_unref(pv.inc_store);
	g_object_unref(pv.exc_store);
	apol_vector_destroy(&pv.both_items);
	if (apol_vector_get_size(pv.included_items) == 0) {
		apol_vector_destroy(&pv.included_items);
		return NULL;
	}
	return pv.included_items;
}
