/**
 *  @file
 *  Implementation of the result item class.
 *
 *  @author Jeremy A. Mowery jmowery@tresys.com
 *  @author Jason Tang jtang@tresys.com
 *
 *  Copyright (C) 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 "result_item.h"
#include "result_item_render.h"
#include "utilgui.h"

#include <assert.h>

typedef void (*destructor_fn_t) (result_item_t * item);
typedef void (*policy_changed_fn_t) (result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol);
typedef void (*poldiff_run_fn_t) (result_item_t * item, poldiff_t * diff, int incremental);
typedef GtkTextBuffer *(*get_buffer_fn_t) (result_item_t * item, poldiff_form_e form);
typedef int (*is_render_slow_fn_t) (result_item_t * item, poldiff_form_e form);
typedef void (*get_forms_fn_t) (result_item_t * item, int forms[5]);
typedef void (*set_current_sort_fn_t) (result_item_t * item, poldiff_form_e form, results_sort_e sort, results_sort_dir_e dir);
typedef void (*print_diff_fn_t) (result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form);

struct result_item
{
	const char *label;
	/** bit value corresponding to polidiff/poldiff.h defines */
	uint32_t bit_pos;
	/** if either policy does not support a particular policy
	    component then this will be zero */
	int supported;
	/* protected members below */
	poldiff_t *diff;
	size_t stats[5];
	gint offsets[5];
	results_sort_e sorts[5];
	results_sort_dir_e sort_dirs[5];
	/* below are required functions to get poldiff results */
	const apol_vector_t *(*get_vector) (const poldiff_t *);
	 poldiff_form_e(*get_form) (const void *);
	char *(*get_string) (const poldiff_t *, const void *);
	/* below is a virtual function table */
	destructor_fn_t destructor;
	/** if the result item does not care about the type of
	    policies are loaded then this can be NULL */
	policy_changed_fn_t policy_changed;
	poldiff_run_fn_t poldiff_run;
	get_buffer_fn_t get_buffer;
	is_render_slow_fn_t is_render_slow;
	get_forms_fn_t get_forms;
	/** if the result item cannot be sorted then this will be an array
	    of zeroes */
	set_current_sort_fn_t set_current_sort;
	/** data specific to subclasses of result_item */
	union
	{
		int type_can_modify;
		struct
		{
			int has_line_numbers[SEDIFFX_POLICY_NUM];
			int cached[5];
			GtkTextBuffer *buffers[5];
			apol_vector_t *items[5];
			print_diff_fn_t print_diff;
		} multi;
	} data;
};

/** map from a poldiff_form_e to an integer */
static const poldiff_form_e form_reverse_map[] = {
	-1, 0, 2, 4, 1, 3
};

/******************** single buffer functions ********************/

/* below is the implementation of the 'single buffer result item'
   class.  most policy components are instances of this class.*/

static GtkTextBuffer *single_buffer = NULL;

/**
 * For single items, results are always destroyed.  (Recalculating
 * results is very fast.)
 */
static void result_item_single_poldiff_run(result_item_t * item, poldiff_t * diff, int incremental __attribute__ ((unused)))
{
	item->diff = diff;
	memset(item->stats, 0, sizeof(item->stats));
}

/**
 * For single items, re-use the same buffer each time.
 */
static GtkTextBuffer *result_item_single_get_buffer(result_item_t * item, poldiff_form_e form)
{
	util_text_buffer_clear(single_buffer);
	if (form == POLDIFF_FORM_NONE) {
		result_item_print_summary(item, single_buffer);
	} else {
		result_item_print_header(item, single_buffer, form);
		result_item_print_diff(item, single_buffer, form);
	}
	return single_buffer;
}

/**
 * For single items, rendering is (supposed to be) very fast.
 */
static int result_item_single_is_render_slow(result_item_t * item __attribute__ ((unused)), poldiff_form_e form
					     __attribute__ ((unused)))
{
	return 0;
}

/**
 * For single items, re-use the same buffer each time.  This function
 * calls a modified rendering functions to be used explicitly by
 * rules.
 */
static GtkTextBuffer *result_item_single_get_rule_buffer(result_item_t * item, poldiff_form_e form)
{
	util_text_buffer_clear(single_buffer);
	if (form == POLDIFF_FORM_NONE) {
		result_item_print_summary(item, single_buffer);
	} else {
		result_item_print_header(item, single_buffer, form);
		result_item_print_rule_diff(item, single_buffer, form);
	}
	return single_buffer;
}

static void result_item_single_get_forms(result_item_t * item, int forms[5])
{
	int i, was_run = poldiff_is_run(item->diff, item->bit_pos);
	if (was_run) {
		poldiff_get_stats(item->diff, item->bit_pos, item->stats);
	}
	for (i = 0; i < 5; i++) {
		if (!result_item_is_supported(item) || i == form_reverse_map[POLDIFF_FORM_ADD_TYPE]
		    || i == form_reverse_map[POLDIFF_FORM_REMOVE_TYPE]) {
			/* single items do not have add-by-type and
			 * remove-by-type forms */
			forms[i] = -1;
		} else {
			forms[i] = was_run;
		}
	}
}

/**
 * Constructor for the abstract single buffer item class.
 */
static result_item_t *result_item_single_create(GtkTextTagTable * table)
{
	result_item_t *item = calloc(1, sizeof(*item));
	if (item == NULL) {
		return item;
	}
	if (single_buffer == NULL) {
		single_buffer = gtk_text_buffer_new(table);
	}
	item->supported = 1;
	item->policy_changed = NULL;
	item->poldiff_run = result_item_single_poldiff_run;
	item->get_buffer = result_item_single_get_buffer;
	item->is_render_slow = result_item_single_is_render_slow;
	item->get_forms = result_item_single_get_forms;
	return item;
}

/******************** constructors below ********************/

result_item_t *result_item_create_classes(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Classes";
	item->bit_pos = POLDIFF_DIFF_CLASSES;
	item->get_vector = poldiff_get_class_vector;
	item->get_form = poldiff_class_get_form;
	item->get_string = poldiff_class_to_string;
	return item;
}

result_item_t *result_item_create_commons(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Commons";
	item->bit_pos = POLDIFF_DIFF_COMMONS;
	item->get_vector = poldiff_get_common_vector;
	item->get_form = poldiff_common_get_form;
	item->get_string = poldiff_common_to_string;
	return item;
}

/**
 * Print an appropriate error message if levels are not supported.
 */
static GtkTextBuffer *result_item_level_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (!result_item_is_supported(item)) {
		gtk_text_buffer_set_text(single_buffer,
					 "Level diffs are not supported because neither policy is a MLS policy.", -1);
		return single_buffer;
	} else {
		return result_item_single_get_buffer(item, form);
	}
}

/**
 * Only support sensitivites and levels if either policy (can) has
 * them.
 */
static void result_item_level_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
	qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
	if (!qpol_policy_has_capability(oq, QPOL_CAP_MLS) && !qpol_policy_has_capability(mq, QPOL_CAP_MLS)) {
		item->supported = 0;
	} else {
		item->supported = 1;
	}
}

/* levels require at least one MLS policy */
result_item_t *result_item_create_levels(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Levels";
	item->bit_pos = POLDIFF_DIFF_LEVELS;
	item->get_vector = poldiff_get_level_vector;
	item->get_form = poldiff_level_get_form;
	item->get_string = poldiff_level_to_string;
	item->get_buffer = result_item_level_get_buffer;
	item->policy_changed = result_item_level_policy_changed;
	return item;
}

/**
 * Print an appropriate error message if categories are not supported.
 */
static GtkTextBuffer *result_item_category_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (!result_item_is_supported(item)) {
		gtk_text_buffer_set_text(single_buffer,
					 "Category diffs are not supported because neither policy is a MLS policy.", -1);
		return single_buffer;
	} else {
		return result_item_single_get_buffer(item, form);
	}
}

/**
 * The modified form is always unsupported.
 */
static void result_item_category_get_forms(result_item_t * item, int forms[5])
{
	result_item_single_get_forms(item, forms);
	forms[4] = -1;
}

/* categories have no modified form; they also require two MLS
   policies to be supported */
result_item_t *result_item_create_categories(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Categories";
	item->bit_pos = POLDIFF_DIFF_CATS;
	item->get_vector = poldiff_get_cat_vector;
	item->get_form = poldiff_cat_get_form;
	item->get_string = poldiff_cat_to_string;
	item->get_buffer = result_item_category_get_buffer;
	item->policy_changed = result_item_level_policy_changed;	/* [sic] */
	item->get_forms = result_item_category_get_forms;
	return item;
}

/**
 * Only show modified types if it makes sense -- i.e, when both
 * policies have meaningful attribute names.
 */
static void result_item_type_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
	qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
	if (!qpol_policy_has_capability(oq, QPOL_CAP_ATTRIB_NAMES) || !qpol_policy_has_capability(mq, QPOL_CAP_ATTRIB_NAMES)) {
		item->data.type_can_modify = 0;
	} else {
		item->data.type_can_modify = 1;
	}
}

static void result_item_type_get_forms(result_item_t * item, int forms[5])
{
	result_item_single_get_forms(item, forms);
	if (!item->data.type_can_modify) {
		forms[4] = -1;
	}
}

/* the type result item is a subclass of single item.  it differs in
   that it might not have a modified form */
result_item_t *result_item_create_types(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Types";
	item->bit_pos = POLDIFF_DIFF_TYPES;
	item->get_vector = poldiff_get_type_vector;
	item->get_form = poldiff_type_get_form;
	item->get_string = poldiff_type_to_string;
	item->policy_changed = result_item_type_policy_changed;
	item->get_forms = result_item_type_get_forms;
	return item;
}

/**
 * Only support attributes when both policies (can) have them.
 */
static void result_item_attribute_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
	qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
	if (!qpol_policy_has_capability(oq, QPOL_CAP_ATTRIB_NAMES) || !qpol_policy_has_capability(mq, QPOL_CAP_ATTRIB_NAMES)) {
		item->supported = 0;
	} else {
		item->supported = 1;
	}
}

/**
 * Print an appropriate error message if attributes are not supported.
 */
static GtkTextBuffer *result_item_attribute_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (!result_item_is_supported(item)) {
		gtk_text_buffer_set_text(single_buffer,
					 "Attribute diffs are not supported because one of the policies does not contain attribute names.",
					 -1);
		return single_buffer;
	} else {
		return result_item_single_get_buffer(item, form);
	}
}

/* the attribute result item is a subclass of single item.  it differs
   in that it might not exist if attributes are not supported in
   either policy */
result_item_t *result_item_create_attributes(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Attributes";
	item->bit_pos = POLDIFF_DIFF_ATTRIBS;
	item->get_vector = poldiff_get_attrib_vector;
	item->get_form = poldiff_attrib_get_form;
	item->get_string = poldiff_attrib_to_string;
	item->get_buffer = result_item_attribute_get_buffer;
	item->policy_changed = result_item_attribute_policy_changed;
	return item;
}

result_item_t *result_item_create_roles(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Roles";
	item->bit_pos = POLDIFF_DIFF_ROLES;
	item->get_vector = poldiff_get_role_vector;
	item->get_form = poldiff_role_get_form;
	item->get_string = poldiff_role_to_string;
	return item;
}

/**
 * Construct a string, similar to poldiff_user_to_string(), but with
 * the proper color coding enabled.
 */
static void result_item_user_print_modified(result_item_t * item, poldiff_user_t * user, GtkTextBuffer * tb, GtkTextIter * iter)
{
	GString *string = g_string_new("");
	g_string_printf(string, "* %s\n", poldiff_user_get_name(user));
	result_item_print_string(tb, iter, string->str, 1);

	const apol_vector_t *added = poldiff_user_get_added_roles(user);
	const apol_vector_t *removed = poldiff_user_get_removed_roles(user);
	const apol_vector_t *unmodified = poldiff_user_get_unmodified_roles(user);
	size_t i;
	char *s;
	if (apol_vector_get_size(added) > 0 || apol_vector_get_size(removed) > 0) {
		g_string_assign(string, "   roles {");
		for (i = 0; i < apol_vector_get_size(unmodified); i++) {
			s = (char *)apol_vector_get_element(unmodified, i);
			g_string_append_printf(string, " %s", s);
		}
		for (i = 0; i < apol_vector_get_size(added); i++) {
			s = (char *)apol_vector_get_element(added, i);
			g_string_append_printf(string, " +%s", s);
		}
		for (i = 0; i < apol_vector_get_size(removed); i++) {
			s = (char *)apol_vector_get_element(removed, i);
			g_string_append_printf(string, " -%s", s);
		}
		g_string_append(string, " }\n");
		result_item_print_string_inline(tb, iter, string->str, 1);
	}

	const poldiff_level_t *orig = poldiff_user_get_original_dfltlevel(user);
	const poldiff_level_t *mod = poldiff_user_get_modified_dfltlevel(user);
	if (orig != NULL) {
		result_item_print_string_inline(tb, iter, "   level:\n", 1);
		s = poldiff_level_to_string_brief(item->diff, orig);
		g_string_printf(string, "     %s", s);
		if (poldiff_level_get_form(orig) != POLDIFF_FORM_MODIFIED) {
			result_item_print_string(tb, iter, string->str, 1);
		} else {
			result_item_print_string_inline(tb, iter, string->str, 1);
		}
		free(s);
		if (mod != NULL) {
			s = poldiff_level_to_string_brief(item->diff, mod);
			g_string_printf(string, "     %s", s);
			if (poldiff_level_get_form(mod) != POLDIFF_FORM_MODIFIED) {
				result_item_print_string(tb, iter, string->str, 1);
			} else {
				result_item_print_string_inline(tb, iter, string->str, 1);
			}
			free(s);
		}
	}

	const poldiff_range_t *range = poldiff_user_get_range(user);
	if (range != NULL) {
		result_item_print_modified_range(item, range, tb, iter);
	}
	result_item_print_string(tb, iter, "\n", 0);
	g_string_free(string, TRUE);
}

/**
 * Printing a modified user is special, for it spans multiple lines
 * and has inline markers.
 */
static GtkTextBuffer *result_item_user_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (form != POLDIFF_FORM_MODIFIED) {
		return result_item_single_get_buffer(item, form);
	} else {
		util_text_buffer_clear(single_buffer);
		result_item_print_header(item, single_buffer, form);
		GtkTextIter iter;
		const apol_vector_t *v;
		size_t i;
		poldiff_user_t *user;

		gtk_text_buffer_get_end_iter(single_buffer, &iter);
		v = item->get_vector(item->diff);
		for (i = 0; i < apol_vector_get_size(v); i++) {
			user = (poldiff_user_t *) apol_vector_get_element(v, i);
			if (item->get_form(user) == form) {
				result_item_user_print_modified(item, user, single_buffer, &iter);
			}
		}
		return single_buffer;
	}
}

/* the user result item is a subclass of a single item. */
result_item_t *result_item_create_users(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Users";
	item->bit_pos = POLDIFF_DIFF_USERS;
	item->get_vector = poldiff_get_user_vector;
	item->get_form = poldiff_user_get_form;
	item->get_string = poldiff_user_to_string;
	item->get_buffer = result_item_user_get_buffer;
	return item;
}

/**
 * Only support booleans if either policy (can) has them.
 */
static void result_item_boolean_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
	qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
	if (!qpol_policy_has_capability(oq, QPOL_CAP_CONDITIONALS) && !qpol_policy_has_capability(mq, QPOL_CAP_CONDITIONALS)) {
		item->supported = 0;
	} else {
		item->supported = 1;
	}
}

result_item_t *result_item_create_booleans(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Booleans";
	item->bit_pos = POLDIFF_DIFF_BOOLS;
	item->get_vector = poldiff_get_bool_vector;
	item->get_form = poldiff_bool_get_form;
	item->get_string = poldiff_bool_to_string;
	item->policy_changed = result_item_boolean_policy_changed;
	return item;
}

/* role trans are a subclass of single buffer item, in that they have
   a special rendering function for modified items */
result_item_t *result_item_create_role_allows(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Role Allows";
	item->bit_pos = POLDIFF_DIFF_ROLE_ALLOWS;
	item->get_vector = poldiff_get_role_allow_vector;
	item->get_form = poldiff_role_allow_get_form;
	item->get_string = poldiff_role_allow_to_string;
	item->get_buffer = result_item_single_get_rule_buffer;
	return item;
}

static void result_item_role_trans_get_forms(result_item_t * item, int forms[5])
{
	int i, was_run = poldiff_is_run(item->diff, item->bit_pos);
	if (was_run) {
		poldiff_get_stats(item->diff, item->bit_pos, item->stats);
	}
	for (i = 0; i < 5; i++) {
		if (!result_item_is_supported(item)) {
			forms[i] = -1;
		} else {
			forms[i] = was_run;
		}
	}
}

/* role trans are a subclass of single buffer item, in that they have
   a special rendering function for modified items and that they have
   add-type and remove-type forms */
result_item_t *result_item_create_role_trans(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Role Transitions";
	item->bit_pos = POLDIFF_DIFF_ROLE_TRANS;
	item->get_vector = poldiff_get_role_trans_vector;
	item->get_form = poldiff_role_trans_get_form;
	item->get_string = poldiff_role_trans_to_string;
	item->get_forms = result_item_role_trans_get_forms;
	item->get_buffer = result_item_single_get_rule_buffer;
	return item;
}

/**
 * Construct a string, similar to poldiff_range_trans_to_string(), but
 * with the proper color coding enabled.
 */
static void result_item_range_trans_print_modified(result_item_t * item, const poldiff_range_trans_t * rt, GtkTextBuffer * tb,
						   GtkTextIter * iter)
{
	GString *string = g_string_new("");
	char *orig_s = poldiff_range_trans_to_string(item->diff, rt);
	char *next_s = orig_s;
	const poldiff_range_t *range = poldiff_range_trans_get_range(rt);

	/* first line should always be printed with normal font */
	char *s = strsep(&next_s, "\n");
	g_string_printf(string, "%s\n", s);
	result_item_print_string(tb, iter, string->str, 1);

	/* all subsequent lines are printed as normal (yes, this
	 * discards lines from poldiff_range_trans_to_string() */
	free(orig_s);
	result_item_print_modified_range(item, range, tb, iter);
}

/**
 * Printing a modified range_transition is special, for it spans
 * multiple lines and has inline markers.
 */
static GtkTextBuffer *result_item_range_trans_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (form != POLDIFF_FORM_MODIFIED) {
		return result_item_single_get_buffer(item, form);
	} else {
		util_text_buffer_clear(single_buffer);
		result_item_print_header(item, single_buffer, form);
		GtkTextIter iter;
		const apol_vector_t *v;
		size_t i;
		poldiff_range_trans_t *rt;

		gtk_text_buffer_get_end_iter(single_buffer, &iter);
		v = item->get_vector(item->diff);
		for (i = 0; i < apol_vector_get_size(v); i++) {
			rt = (poldiff_range_trans_t *) apol_vector_get_element(v, i);
			if (item->get_form(rt) == form) {
				result_item_range_trans_print_modified(item, rt, single_buffer, &iter);
			}
		}
		return single_buffer;
	}
}

/* range trans are a subclass of single buffer item, in that they have
   a special rendering function for modified items and that they have
   add-type and remove-type forms */
result_item_t *result_item_create_range_trans(GtkTextTagTable * table)
{
	result_item_t *item = result_item_single_create(table);
	if (item == NULL) {
		return item;
	}
	item->label = "Range Transitions";
	item->bit_pos = POLDIFF_DIFF_RANGE_TRANS;
	item->get_vector = poldiff_get_range_trans_vector;
	item->get_form = poldiff_range_trans_get_form;
	item->get_string = poldiff_range_trans_to_string;
	item->get_forms = result_item_role_trans_get_forms;	/* [sic] */
	item->get_buffer = result_item_range_trans_get_buffer;
	item->policy_changed = result_item_level_policy_changed;	/* [sic] */
	return item;
}

/******************** AV and Type rules below ********************/

/**
 * Show line numbers if the policy has them.
 */
static void result_item_multi_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
	qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
	item->data.multi.has_line_numbers[SEDIFFX_POLICY_ORIG] = qpol_policy_has_capability(oq, QPOL_CAP_LINE_NUMBERS);
	item->data.multi.has_line_numbers[SEDIFFX_POLICY_MOD] = qpol_policy_has_capability(mq, QPOL_CAP_LINE_NUMBERS);
}

/**
 * Clear the cache whenever poldiff is (re-)run.
 */
static void result_item_multi_poldiff_run(result_item_t * item, poldiff_t * diff, int incremental __attribute__ ((unused)))
{
	item->diff = diff;
	memset(item->stats, 0, sizeof(item->stats));
	int i;
	for (i = 0; i < 5; i++) {
		item->data.multi.cached[i] = 0;
		util_text_buffer_clear(item->data.multi.buffers[i]);
	}
}

/**
 * Render the buffer if it has not yet been cached, then return it.
 */
static GtkTextBuffer *result_item_multi_get_buffer(result_item_t * item, poldiff_form_e form)
{
	GtkTextBuffer *tb;
	if (form == POLDIFF_FORM_NONE) {
		/* just use the global single_buffer when printing the
		 * summary */
		util_text_buffer_clear(single_buffer);
		result_item_print_summary(item, single_buffer);
		tb = single_buffer;
	} else {
		tb = item->data.multi.buffers[form_reverse_map[form]];
		if (!item->data.multi.cached[form_reverse_map[form]]) {
			util_text_buffer_clear(tb);
			result_item_print_header(item, tb, form);
			item->data.multi.print_diff(item, tb, form);
			item->data.multi.cached[form_reverse_map[form]] = 1;
		}
	}
	return tb;
}

/**
 * If the item is cached or if there are less than 50 things to show,
 * then rendering is considered to be fast.
 */
static int result_item_multi_is_render_slow(result_item_t * item, poldiff_form_e form)
{
	if (form == POLDIFF_FORM_NONE || item->data.multi.cached[form_reverse_map[form]]
	    || result_item_get_num_differences(item, form) < 50) {
		return 0;
	}
	return 1;
}

static void result_item_multi_set_current_sort(result_item_t * item, poldiff_form_e form, results_sort_e sort,
					       results_sort_dir_e dir)
{
	if (item->sorts[form_reverse_map[form]] != sort || item->sort_dirs[form_reverse_map[form]] != dir) {
		item->sorts[form_reverse_map[form]] = sort;
		item->sort_dirs[form_reverse_map[form]] = dir;
		item->data.multi.cached[form_reverse_map[form]] = 0;
	}
}

static void result_item_multi_destructor(result_item_t * item)
{
	size_t i;
	for (i = 0; i < 5; i++) {
		apol_vector_destroy(&item->data.multi.items[i]);
	}
}

/**
 * Constructor for the abstract multi buffer item class.  Multi-buffer
 * items are capable of sorting and potentially take some time to
 * render their buffers.
 */
static result_item_t *result_item_multi_create(GtkTextTagTable * table)
{
	result_item_t *item = calloc(1, sizeof(*item));
	if (item == NULL) {
		return item;
	}
	if (single_buffer == NULL) {
		single_buffer = gtk_text_buffer_new(table);
	}
	item->supported = 1;
	item->destructor = result_item_multi_destructor;
	item->policy_changed = result_item_multi_policy_changed;
	item->poldiff_run = result_item_multi_poldiff_run;
	item->get_buffer = result_item_multi_get_buffer;
	item->is_render_slow = result_item_multi_is_render_slow;
	item->get_forms = result_item_role_trans_get_forms;	/* [sic] */
	item->set_current_sort = result_item_multi_set_current_sort;
	int i;
	for (i = 0; i < 5; i++) {
		item->data.multi.buffers[i] = gtk_text_buffer_new(table);
		item->sorts[i] = RESULTS_SORT_DEFAULT;
		item->sort_dirs[i] = RESULTS_SORT_ASCEND;
	}
	return item;
}

struct sort_opts
{
	poldiff_t *diff;
	results_sort_e field;
	results_sort_dir_e direction;
};

static int result_item_avrule_comp(const void *a, const void *b, void *data)
{
	const poldiff_avrule_t *a1 = a;
	const poldiff_avrule_t *a2 = b;
	struct sort_opts *opts = data;
	const char *s1, *s2;
	switch (opts->field) {
	case RESULTS_SORT_SOURCE:
	{
		s1 = poldiff_avrule_get_source_type(a1);
		s2 = poldiff_avrule_get_source_type(a2);
		break;
	}
	case RESULTS_SORT_TARGET:
	{
		s1 = poldiff_avrule_get_target_type(a1);
		s2 = poldiff_avrule_get_target_type(a2);
		break;
	}
	case RESULTS_SORT_CLASS:
	{
		s1 = poldiff_avrule_get_object_class(a1);
		s2 = poldiff_avrule_get_object_class(a2);
		break;
	}
	case RESULTS_SORT_COND:
	{
		const qpol_cond_t *q1, *q2;
		const apol_policy_t *p1, *p2;
		uint32_t w1, w2;
		poldiff_avrule_get_cond(opts->diff, a1, &q1, &w1, &p1);
		poldiff_avrule_get_cond(opts->diff, a2, &q2, &w2, &p2);
		if (q1 != q2) {
			return opts->direction * ((char *)q1 - (char *)q2);
		}
		return opts->direction * (w1 - w2);
		break;
	}
	default:
	{
		/* shouldn't get here */
		assert(0);
		return 0;
	}
	}
	return opts->direction * strcmp(s1, s2);
}

static apol_vector_t *result_item_avrule_sort(result_item_t * item, poldiff_form_e form)
{
	const apol_vector_t *orig_v;
	apol_vector_t *v;
	size_t i;
	void *elem;
	struct sort_opts opts = { item->diff, item->sorts[form_reverse_map[form]], item->sort_dirs[form_reverse_map[form]] };

	orig_v = item->get_vector(item->diff);
	if ((v = apol_vector_create(NULL)) == NULL) {
		return NULL;
	}
	for (i = 0; i < apol_vector_get_size(orig_v); i++) {
		elem = apol_vector_get_element(orig_v, i);
		if (poldiff_avrule_get_form(elem) == form && apol_vector_append(v, elem) < 0) {
			apol_vector_destroy(&v);
			return NULL;
		}
	}
	if (opts.field != RESULTS_SORT_DEFAULT) {
		apol_vector_sort(v, result_item_avrule_comp, &opts);
	}
	return v;
}

static void result_item_avrule_print_diff(result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form)
{
	GtkTextIter iter;
	size_t i;
	void *elem;
	char *s;
	GString *string = g_string_new("");
	const apol_vector_t *syn_linenos;
	apol_vector_t *rules = result_item_avrule_sort(item, form);
	char *orig_prefix;
	char *mod_prefix;

	apol_vector_destroy(&item->data.multi.items[form_reverse_map[form]]);
	item->data.multi.items[form_reverse_map[form]] = rules;
	gtk_text_buffer_get_end_iter(tb, &iter);
	for (i = 0; i < apol_vector_get_size(rules); i++) {
		elem = apol_vector_get_element(rules, i);
		if ((s = poldiff_avrule_to_string(item->diff, elem)) == NULL) {
			goto cleanup;
		}
		result_item_print_string_avrule(tb, &iter, s, 1);
		if (form != POLDIFF_FORM_MODIFIED) {
			orig_prefix = NULL;
			mod_prefix = NULL;
		} else {
			orig_prefix = "op: ";
			mod_prefix = "mp: ";
		}
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_ORIG] &&
		    (syn_linenos = poldiff_avrule_get_orig_line_numbers((poldiff_avrule_t *) elem)) != NULL) {
			result_item_print_linenos(tb, &iter, orig_prefix, syn_linenos, "line-pol_orig", string);
		}
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_MOD] &&
		    (syn_linenos = poldiff_avrule_get_mod_line_numbers((poldiff_avrule_t *) elem)) != NULL) {
			result_item_print_linenos(tb, &iter, mod_prefix, syn_linenos, "line-pol_mod", string);
		}
		free(s);
		gtk_text_buffer_insert(tb, &iter, "\n", -1);
	}
      cleanup:
	g_string_free(string, TRUE);
}

/**
 * Only support neverallows if either policy (can) has them.
 */
static void result_item_avrule_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	item->supported = 1;
	if (item->bit_pos == POLDIFF_DIFF_AVNEVERALLOW) {
		qpol_policy_t *oq = apol_policy_get_qpol(orig_pol);
		qpol_policy_t *mq = apol_policy_get_qpol(mod_pol);
		int orig_type = -1, mod_type = -1;
		// don't use capability to check if neverallows are
		// supported, because policies are always loaded
		// without them.  libpoldiff could then reload the
		// policies with neverallow, in which case they would
		// become capable (but item->supported still claims to
		// be 0)
		qpol_policy_get_type(oq, &orig_type);
		qpol_policy_get_type(mq, &mod_type);
		if (orig_type == QPOL_POLICY_KERNEL_BINARY && mod_type == QPOL_POLICY_KERNEL_BINARY) {
			item->supported = 0;
		}
	}
	result_item_multi_policy_changed(item, orig_pol, mod_pol);
}

/**
 * Print an appropriate error message if neverallows are not supported.
 */
static GtkTextBuffer *result_item_avrule_get_buffer(result_item_t * item, poldiff_form_e form)
{
	if (!result_item_is_supported(item)) {
		gtk_text_buffer_set_text(single_buffer,
					 "Neverallow diffs are not supported because both policies are kernel binaries.", -1);
		return single_buffer;
	} else {
		return result_item_multi_get_buffer(item, form);
	}
}

static result_item_t *result_item_create_from_flag(GtkTextTagTable * table, uint32_t flag)
{
	result_item_t *item = result_item_multi_create(table);
	if (item == NULL) {
		return item;
	}
	const poldiff_component_record_t *rec = poldiff_get_component_record(flag);
	item->label = poldiff_component_record_get_label(rec);
	item->bit_pos = flag;
	item->get_vector = poldiff_component_record_get_results_fn(rec);
	item->get_form = poldiff_component_record_get_form_fn(rec);
	item->get_string = poldiff_component_record_get_to_string_fn(rec);
	item->get_buffer = result_item_avrule_get_buffer;
	item->data.multi.print_diff = result_item_avrule_print_diff;
	item->policy_changed = result_item_avrule_policy_changed;
	return item;
}

result_item_t *result_item_create_avrules_allow(GtkTextTagTable * table)
{
	return result_item_create_from_flag(table, POLDIFF_DIFF_AVALLOW);
}

result_item_t *result_item_create_avrules_auditallow(GtkTextTagTable * table)
{
	return result_item_create_from_flag(table, POLDIFF_DIFF_AVAUDITALLOW);
}

result_item_t *result_item_create_avrules_dontaudit(GtkTextTagTable * table)
{
	return result_item_create_from_flag(table, POLDIFF_DIFF_AVDONTAUDIT);
}

result_item_t *result_item_create_avrules_neverallow(GtkTextTagTable * table)
{
	return result_item_create_from_flag(table, POLDIFF_DIFF_AVNEVERALLOW);
}

static int result_item_terule_comp(const void *a, const void *b, void *data)
{
	const poldiff_terule_t *a1 = a;
	const poldiff_terule_t *a2 = b;
	struct sort_opts *opts = data;
	const char *s1, *s2;
	switch (opts->field) {
	case RESULTS_SORT_SOURCE:
	{
		s1 = poldiff_terule_get_source_type(a1);
		s2 = poldiff_terule_get_source_type(a2);
		break;
	}
	case RESULTS_SORT_TARGET:
	{
		s1 = poldiff_terule_get_target_type(a1);
		s2 = poldiff_terule_get_target_type(a2);
		break;
	}
	case RESULTS_SORT_CLASS:
	{
		s1 = poldiff_terule_get_object_class(a1);
		s2 = poldiff_terule_get_object_class(a2);
		break;
	}
	case RESULTS_SORT_COND:
	{
		const qpol_cond_t *q1, *q2;
		const apol_policy_t *p1, *p2;
		uint32_t w1, w2;
		poldiff_terule_get_cond(opts->diff, a1, &q1, &w1, &p1);
		poldiff_terule_get_cond(opts->diff, a2, &q2, &w2, &p2);
		if (q1 != q2) {
			return opts->direction * ((char *)q1 - (char *)q2);
		}
		return opts->direction * (w1 - w2);
		break;
	}
	default:
	{
		/* shouldn't get here */
		assert(0);
		return 0;
	}
	}
	return opts->direction * strcmp(s1, s2);
}

static apol_vector_t *result_item_terule_sort(result_item_t * item, poldiff_form_e form)
{
	const apol_vector_t *orig_v;
	apol_vector_t *v;
	size_t i;
	void *elem;
	struct sort_opts opts = { item->diff, item->sorts[form_reverse_map[form]], item->sort_dirs[form_reverse_map[form]] };
	orig_v = item->get_vector(item->diff);
	if ((v = apol_vector_create(NULL)) == NULL) {
		return NULL;
	}
	for (i = 0; i < apol_vector_get_size(orig_v); i++) {
		elem = apol_vector_get_element(orig_v, i);
		if (poldiff_terule_get_form(elem) == form && apol_vector_append(v, elem) < 0) {
			apol_vector_destroy(&v);
			return NULL;
		}
	}
	if (opts.field != RESULTS_SORT_DEFAULT) {
		apol_vector_sort(v, result_item_terule_comp, &opts);
	}
	return v;
}

static void result_item_terule_print_diff(result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form)
{
	GtkTextIter iter;
	size_t i;
	void *elem;
	char *s;
	GString *string = g_string_new("");
	apol_vector_t *syn_linenos;
	apol_vector_t *rules = result_item_terule_sort(item, form);
	char *orig_prefix;
	char *mod_prefix;

	gtk_text_buffer_get_end_iter(tb, &iter);
	for (i = 0; i < apol_vector_get_size(rules); i++) {
		elem = apol_vector_get_element(rules, i);
		if ((s = poldiff_terule_to_string(item->diff, elem)) == NULL) {
			goto cleanup;
		}
		if (form != POLDIFF_FORM_MODIFIED) {
			orig_prefix = NULL;
			mod_prefix = NULL;
			result_item_print_string(tb, &iter, s, 1);
		} else {
			orig_prefix = "op: ";
			mod_prefix = "mp: ";
			result_item_print_string_inline(tb, &iter, s, 1);
		}
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_ORIG] &&
		    (syn_linenos = poldiff_terule_get_orig_line_numbers((poldiff_terule_t *) elem)) != NULL) {
			result_item_print_linenos(tb, &iter, orig_prefix, syn_linenos, "line-pol_orig", string);
		}
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_MOD] &&
		    (syn_linenos = poldiff_terule_get_mod_line_numbers((poldiff_terule_t *) elem)) != NULL) {
			result_item_print_linenos(tb, &iter, mod_prefix, syn_linenos, "line-pol_mod", string);
		}
		free(s);
		gtk_text_buffer_insert(tb, &iter, "\n", -1);
	}
      cleanup:
	apol_vector_destroy(&rules);
	g_string_free(string, TRUE);
}

static result_item_t *result_item_create_terules_from_flag(GtkTextTagTable * table, uint32_t flag)
{
	result_item_t *item = result_item_multi_create(table);
	if (item == NULL) {
		return item;
	}
	const poldiff_component_record_t *rec = poldiff_get_component_record(flag);
	item->label = poldiff_component_record_get_label(rec);
	item->bit_pos = flag;
	item->get_vector = poldiff_component_record_get_results_fn(rec);
	item->get_form = poldiff_component_record_get_form_fn(rec);
	item->get_string = poldiff_component_record_get_to_string_fn(rec);
	item->data.multi.print_diff = result_item_terule_print_diff;
	return item;
}

result_item_t *result_item_create_terules_change(GtkTextTagTable * table)
{
	return result_item_create_terules_from_flag(table, POLDIFF_DIFF_TECHANGE);
}

result_item_t *result_item_create_terules_member(GtkTextTagTable * table)
{
	return result_item_create_terules_from_flag(table, POLDIFF_DIFF_TEMEMBER);
}

result_item_t *result_item_create_terules_trans(GtkTextTagTable * table)
{
	return result_item_create_terules_from_flag(table, POLDIFF_DIFF_TETRANS);
}

/******************** public methods below ********************/

void result_item_destroy(result_item_t ** item)
{
	if (item != NULL && *item != NULL) {
		if ((*item)->destructor != NULL) {
			(*item)->destructor(*item);
		} else {
			free(*item);
		}
		*item = NULL;
	}
}

const char *result_item_get_label(const result_item_t * item)
{
	return item->label;
}

void result_item_policy_changed(result_item_t * item, apol_policy_t * orig_pol, apol_policy_t * mod_pol)
{
	if (item->policy_changed != NULL) {
		item->policy_changed(item, orig_pol, mod_pol);
	}
}

GtkTextBuffer *result_item_get_buffer(result_item_t * item, poldiff_form_e form)
{
	return item->get_buffer(item, form);
}

int result_item_is_render_slow(result_item_t * item, poldiff_form_e form)
{
	return item->is_render_slow(item, form);
}

void result_item_poldiff_run(result_item_t * item, poldiff_t * diff, int incremental)
{
	item->poldiff_run(item, diff, incremental);
}

int result_item_is_supported(const result_item_t * item)
{
	return item->supported;
}

void result_item_get_forms(result_item_t * item, int forms[5])
{
	item->get_forms(item, forms);
}

size_t result_item_get_num_differences(result_item_t * item, poldiff_form_e form)
{
	switch (form) {
	case POLDIFF_FORM_ADDED:
		return item->stats[0];
	case POLDIFF_FORM_REMOVED:
		return item->stats[1];
	case POLDIFF_FORM_MODIFIED:
		return item->stats[2];
	case POLDIFF_FORM_ADD_TYPE:
		return item->stats[3];
	case POLDIFF_FORM_REMOVE_TYPE:
		return item->stats[4];
	default:		       /* should never get here */
		assert(0);
		return 0;
	}
}

int result_item_get_current_sort(result_item_t * item, poldiff_form_e form, results_sort_e * sort, results_sort_dir_e * dir)
{
	if (item->set_current_sort == NULL || form == POLDIFF_FORM_NONE) {
		return 0;
	}
	*sort = item->sorts[form_reverse_map[form]];
	*dir = item->sort_dirs[form_reverse_map[form]];
	return 1;
}

void result_item_set_current_sort(result_item_t * item, poldiff_form_e form, results_sort_e sort, results_sort_dir_e dir)
{
	if (item->set_current_sort != NULL && form != POLDIFF_FORM_NONE) {
		item->set_current_sort(item, form, sort, dir);
	}
}

void result_item_save_current_line(result_item_t * item, poldiff_form_e form, gint offset)
{
	/* don't care about the summary page */
	if (form != POLDIFF_FORM_NONE) {
		item->offsets[form_reverse_map[form]] = offset;
	}
}

gint result_item_get_current_line(result_item_t * item, poldiff_form_e form)
{
	/* don't care about the summary page */
	if (form == POLDIFF_FORM_NONE) {
		return 0;
	}
	return item->offsets[form_reverse_map[form]];
}

static void result_item_on_orig_activate(GtkMenuItem * menuitem, gpointer user_data)
{
	toplevel_t *top = (toplevel_t *) user_data;
	GtkWidget *label = gtk_bin_get_child(GTK_BIN(menuitem));
	unsigned long line = atoi(gtk_label_get_label(GTK_LABEL(label))) - 1;
	toplevel_show_policy_line(top, SEDIFFX_POLICY_ORIG, line);
}

static void result_item_on_mod_activate(GtkMenuItem * menuitem, gpointer user_data)
{
	toplevel_t *top = (toplevel_t *) user_data;
	GtkWidget *label = gtk_bin_get_child(GTK_BIN(menuitem));
	unsigned long line = atoi(gtk_label_get_label(GTK_LABEL(label))) - 1;
	toplevel_show_policy_line(top, SEDIFFX_POLICY_MOD, line);
}

void result_item_inline_link_event(result_item_t * item, toplevel_t * top, GtkWidget * container, GdkEventButton * event,
				   poldiff_form_e form, int line_num, const char *s)
{
	/* for now, inline links only work for avrule items */
	assert(item->bit_pos & POLDIFF_DIFF_AVRULES);
	const char *perm;
	poldiff_form_e perm_form = form;
	if (*s == '+' || *s == '-' || *s == '*') {
		if (*s == '+') {
			perm_form = POLDIFF_FORM_ADDED;
		} else if (*s == '-') {
			perm_form = POLDIFF_FORM_REMOVED;
		}
		perm = s + 1;
	} else {
		perm = s;
	}
	apol_vector_t *rules = item->data.multi.items[form_reverse_map[form]];
	size_t i = line_num - 3;       /* subtract 3 because the header consume
				        * three lines in the text buffer */
	poldiff_avrule_t *rule = apol_vector_get_element(rules, i);
	assert(rule != NULL);

	GtkMenu *menu = GTK_MENU(gtk_menu_new());
	GtkWidget *menuitem;
	GString *string = g_string_new("");
	int button, event_time;
	gtk_menu_set_title(menu, perm);
	menuitem = gtk_menu_item_new_with_label(perm);
	gtk_widget_set_sensitive(menuitem, FALSE);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);

	menuitem = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	menuitem = gtk_menu_item_new_with_label("Original Policy");
	gtk_widget_set_sensitive(menuitem, FALSE);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	if (perm_form == POLDIFF_FORM_REMOVED || perm_form == POLDIFF_FORM_REMOVE_TYPE || perm_form == POLDIFF_FORM_MODIFIED) {
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_ORIG]) {
			apol_vector_t *v = poldiff_avrule_get_orig_line_numbers_for_perm(item->diff, rule, perm);
			if (v != NULL && apol_vector_get_size(v) > 0) {
				for (i = 0; i < apol_vector_get_size(v); i++) {
					unsigned long line = (unsigned long)apol_vector_get_element(v, i);
					g_string_printf(string, "  %lu", line);
					menuitem = gtk_menu_item_new_with_label(string->str);
					g_signal_connect(menuitem, "activate", G_CALLBACK(result_item_on_orig_activate), top);
					gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
				}
			}
		} else {
			menuitem = gtk_menu_item_new_with_label("  line numbers not available");
			gtk_widget_set_sensitive(menuitem, FALSE);
			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
		}
	}

	menuitem = gtk_separator_menu_item_new();
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	menuitem = gtk_menu_item_new_with_label("Modified Policy");
	gtk_widget_set_sensitive(menuitem, FALSE);
	gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
	if (perm_form == POLDIFF_FORM_ADDED || perm_form == POLDIFF_FORM_ADD_TYPE || perm_form == POLDIFF_FORM_MODIFIED) {
		if (item->data.multi.has_line_numbers[SEDIFFX_POLICY_MOD]) {
			apol_vector_t *v = poldiff_avrule_get_mod_line_numbers_for_perm(item->diff, rule, perm);
			if (v != NULL && apol_vector_get_size(v) > 0) {
				for (i = 0; i < apol_vector_get_size(v); i++) {
					unsigned long line = (unsigned long)apol_vector_get_element(v, i);
					g_string_printf(string, "  %lu", line);
					menuitem = gtk_menu_item_new_with_label(string->str);
					g_signal_connect(menuitem, "activate", G_CALLBACK(result_item_on_mod_activate), top);
					gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
				}
			}
		} else {
			menuitem = gtk_menu_item_new_with_label("  line numbers not available");
			gtk_widget_set_sensitive(menuitem, FALSE);
			gtk_menu_shell_append(GTK_MENU_SHELL(menu), menuitem);
		}
	}
	g_string_free(string, TRUE);
	if (event != NULL) {
		button = event->button;
		event_time = event->time;
	} else {
		button = 0;
		event_time = gtk_get_current_event_time();
	}
	gtk_menu_attach_to_widget(menu, container, NULL);
	gtk_widget_show_all(GTK_WIDGET(menu));
	gtk_menu_popup(menu, NULL, NULL, NULL, NULL, button, event_time);
}

/******************** friend methods below ********************/

poldiff_t *result_item_get_diff(result_item_t * item)
{
	return item->diff;
}

const apol_vector_t *result_item_get_vector(result_item_t * item)
{
	return item->get_vector(item->diff);
}

poldiff_form_e result_item_get_form(result_item_t * item, void *elem)
{
	return item->get_form(elem);
}

char *result_item_get_string(result_item_t * item, void *elem)
{
	return item->get_string(item->diff, elem);
}
