/**
 *  @file
 *  Common rendering routines for result items.
 *
 *  @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_render.h"

#include <assert.h>

static const char *form_name_map[] = {
	"Added", "Added New Type", "Removed", "Removed Missing Type", "Modified"
};
static const char *form_name_long_map[] = {
	"Added", "Added because of new type", "Removed", "Removed because of missing type", "Modified"
};
static const char *tag_map[] = {
	"added-header", "added-header", "removed-header", "removed-header", "modified-header"
};
static const poldiff_form_e form_map[] = {
	POLDIFF_FORM_ADDED, POLDIFF_FORM_ADD_TYPE,
	POLDIFF_FORM_REMOVED, POLDIFF_FORM_REMOVE_TYPE,
	POLDIFF_FORM_MODIFIED
};

/**
 * Show a single diff item string.  This will add the appropriate
 * color tags based upon the item's first non-space character.
 */
void result_item_print_string(GtkTextBuffer * tb, GtkTextIter * iter, const char *s, unsigned int indent_level)
{
	const char *c;
	unsigned int i;
	size_t start = 0, end = 0;
	static const char *indent = "\t";
	const gchar *tag = NULL;
	for (i = 0; i < indent_level; i++) {
		gtk_text_buffer_insert(tb, iter, indent, -1);
	}
	for (c = s; *c && tag == NULL; c++) {
		switch (*c) {
		case '+':
		{
			tag = "added";
			break;
		}
		case '-':
		{
			tag = "removed";
			break;
		}
		case ' ':
		case '\t':
		case '\n':
		{
			break;
		}
		default:
		{
			tag = "modified";
			break;
		}
		}
	}
	for (c = s; *c; c++, end++) {
		if (*c == '\n' && *(c + 1) != '\0') {
			gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start + 1, tag, NULL);
			for (i = 0; i < indent_level; i++) {
				gtk_text_buffer_insert(tb, iter, indent, -1);
			}
			start = end + 1;
		}
	}
	if (start < end) {
		gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start, tag, NULL);
	}
}

void result_item_print_string_inline(GtkTextBuffer * tb, GtkTextIter * iter, const char *s, unsigned int indent_level)
{
	const char *c = s;
	unsigned int i;
	size_t start = 0, end = 0;
	static const char *indent = "\t";
	const gchar *current_tag = "modified";
	for (i = 0; i < indent_level; i++) {
		gtk_text_buffer_insert(tb, iter, indent, -1);
	}
	for (; *c; c++, end++) {
		switch (*c) {
		case '+':
		{
			if (end > 0) {
				gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start, current_tag, NULL);
			}
			start = end;
			current_tag = "added";
			break;
		}
		case '-':
		{
			if (end > 0) {
				gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start, current_tag, NULL);
			}
			start = end;
			current_tag = "removed";
			break;
		}
		case '\n':
		{
			if (*(c + 1) != '\0') {
				gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start + 1, current_tag, NULL);
				for (i = 0; i < indent_level; i++) {
					gtk_text_buffer_insert(tb, iter, indent, -1);
				}
				start = end + 1;
			}
			break;
		}
		case ' ':
		{
			if (current_tag != "modified") {
				gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start + 1, current_tag, NULL);
				start = end + 1;
				current_tag = "modified";
			}
			break;
		}
		}
	}
	if (start < end) {
		gtk_text_buffer_insert_with_tags_by_name(tb, iter, s + start, end - start, current_tag, NULL);
	}
}

void result_item_print_string_avrule(GtkTextBuffer * tb, GtkTextIter * iter, const char *s, unsigned int indent_level)
{
	const char *c, *next_c;
	const char *tag = NULL, *init_tag;
	unsigned int i;
	static const char *indent = "\t";
	for (i = 0; i < indent_level; i++) {
		gtk_text_buffer_insert(tb, iter, indent, -1);
	}
	for (c = s; *c && tag == NULL; c++) {
		switch (*c) {
		case '+':
		{
			tag = "added";
			break;
		}
		case '-':
		{
			tag = "removed";
			break;
		}
		case ' ':
		case '\t':
		case '\n':
		{
			break;
		}
		default:
		{
			tag = "modified";
			break;
		}
		}
	}
	init_tag = tag;
	c = strchr(s, '{');
	assert(c != NULL);
	/* advance past the opening brace (which designates start of
	 * the permissions list) and the following space character */
	c += 2;
	gtk_text_buffer_insert_with_tags_by_name(tb, iter, s, c - s, init_tag, NULL);
	while (*c != '}') {
		assert(*c != '\0');
		next_c = strchr(c, ' ');
		assert(next_c != NULL);
		switch (*c) {
		case '+':
		{
			tag = "added";
			break;
		}
		case '-':
		{
			tag = "removed";
			break;
		}
		default:
		{
			tag = init_tag;
		}
		}
		gtk_text_buffer_insert_with_tags_by_name(tb, iter, c, next_c - c, tag, "inline-link", NULL);
		gtk_text_buffer_insert_with_tags_by_name(tb, iter, " ", 1, init_tag, NULL);
		/* advance past the space */
		c = next_c + 1;
	}
	gtk_text_buffer_insert_with_tags_by_name(tb, iter, c, -1, init_tag, NULL);
}

void result_item_print_diff(result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form)
{
	GtkTextIter iter;
	const apol_vector_t *v;
	size_t i;
	void *elem;
	char *s = NULL;

	gtk_text_buffer_get_end_iter(tb, &iter);
	v = result_item_get_vector(item);
	for (i = 0; i < apol_vector_get_size(v); i++) {
		elem = apol_vector_get_element(v, i);
		if (result_item_get_form(item, elem) == form) {
			s = result_item_get_string(item, elem);
			result_item_print_string(tb, &iter, s, 1);
			free(s);
			gtk_text_buffer_insert(tb, &iter, "\n", -1);
		}
	}
}

void result_item_print_rule_diff(result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form)
{
	GtkTextIter iter;
	const apol_vector_t *v;
	size_t i;
	void *elem;
	char *s = NULL;

	gtk_text_buffer_get_end_iter(tb, &iter);
	v = result_item_get_vector(item);
	for (i = 0; i < apol_vector_get_size(v); i++) {
		elem = apol_vector_get_element(v, i);
		if (result_item_get_form(item, elem) == form) {
			s = result_item_get_string(item, elem);
			if (form != POLDIFF_FORM_MODIFIED) {
				result_item_print_string(tb, &iter, s, 1);
			} else {
				result_item_print_string_inline(tb, &iter, s, 1);
			}
			free(s);
			gtk_text_buffer_insert(tb, &iter, "\n", -1);
		}
	}
}

void result_item_print_summary(result_item_t * item, GtkTextBuffer * tb)
{
	GtkTextIter iter;
	int i, forms[5];
	GString *string = g_string_new("");

	gtk_text_buffer_get_end_iter(tb, &iter);
	g_string_printf(string, "%s:\n", result_item_get_label(item));
	gtk_text_buffer_insert_with_tags_by_name(tb, &iter, string->str, -1, "subheader", NULL);

	int was_run = 0;
	result_item_get_forms(item, forms);
	for (i = 0; i < 5; i++) {
		if (forms[i] > 0) {
			g_string_printf(string, "\t%s: %zd\n",
					form_name_long_map[i], result_item_get_num_differences(item, form_map[i]));
			gtk_text_buffer_insert_with_tags_by_name(tb, &iter, string->str, -1, tag_map[i], NULL);
			was_run = 1;
		}
	}
	if (!was_run && result_item_is_supported(item)) {
		gtk_text_buffer_set_text(tb, "This component has not yet been diffed.", -1);
	}
	g_string_free(string, TRUE);
}

/**
 * Show a common header when printing a policy component diff.
 */
void result_item_print_header(result_item_t * item, GtkTextBuffer * tb, poldiff_form_e form)
{
	GtkTextIter iter;
	int i, forms[5];
	GString *string = g_string_new("");
	char *tag = NULL;
	const char *label = result_item_get_label(item);
	int add_separator = 0;

	gtk_text_buffer_get_end_iter(tb, &iter);
	result_item_get_forms(item, forms);
	g_string_printf(string, "%s (", label);
	for (i = 0; i < 5; i++) {
		if (forms[i] > 0) {
			g_string_append_printf(string, "%s%zd %s",
					       (add_separator ? ", " : ""),
					       result_item_get_num_differences(item, form_map[i]), form_name_map[i]);
			add_separator = 1;
		}
	}
	g_string_append_printf(string, ")\n\n");
	gtk_text_buffer_insert_with_tags_by_name(tb, &iter, string->str, -1, "header", NULL);

	switch (form) {
	case POLDIFF_FORM_ADDED:
	{
		g_string_printf(string, "Added %s:", label);
		tag = "added-header";
		break;
	}
	case POLDIFF_FORM_ADD_TYPE:
	{
		g_string_printf(string, "Added %s because of new type:", label);
		tag = "added-header";
		break;
	}
	case POLDIFF_FORM_REMOVED:
	{
		g_string_printf(string, "Removed %s:", label);
		tag = "removed-header";
		break;
	}
	case POLDIFF_FORM_REMOVE_TYPE:
	{
		g_string_printf(string, "Removed %s because of missing type:", label);
		tag = "removed-header";
		break;
	}
	case POLDIFF_FORM_MODIFIED:
	{
		g_string_printf(string, "Modified %s:", label);
		tag = "modified-header";
		break;
	}
	default:
	{
		assert(0);
		tag = NULL;
	}
	}
	g_string_append_printf(string, " %zd\n", result_item_get_num_differences(item, form));
	gtk_text_buffer_insert_with_tags_by_name(tb, &iter, string->str, -1, tag, NULL);
	g_string_free(string, TRUE);
}

void result_item_print_linenos(GtkTextBuffer * tb, GtkTextIter * iter,
			       const gchar * prefix, const apol_vector_t * linenos, const gchar * tag, GString * string)
{
	size_t i;
	unsigned long lineno;
	gtk_text_buffer_insert(tb, iter, "  [", -1);
	if (prefix != NULL) {
		gtk_text_buffer_insert(tb, iter, prefix, -1);
	}
	for (i = 0; i < apol_vector_get_size(linenos); i++) {
		lineno = (unsigned long)apol_vector_get_element(linenos, i);
		if (i > 0) {
			gtk_text_buffer_insert(tb, iter, ", ", -1);
		}
		g_string_printf(string, "%lu", lineno);
		gtk_text_buffer_insert_with_tags_by_name(tb, iter, string->str, -1, tag, NULL);
	}
	gtk_text_buffer_insert(tb, iter, "]", -1);
}

void result_item_print_modified_range(result_item_t * item, const poldiff_range_t * range, GtkTextBuffer * tb, GtkTextIter * iter)
{
	poldiff_t *diff = result_item_get_diff(item);
	char *orig_s = poldiff_range_to_string_brief(diff, range);
	char *next_s = orig_s;
	GString *string = g_string_new("");

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

	/* if the next line is minimum category set differences then
	 * display it */
	if (strncmp(next_s, "     minimum categories:", strlen("     minimum categories:")) == 0) {
		s = strsep(&next_s, "\n");
		result_item_print_string_inline(tb, iter, s, 1);
		gtk_text_buffer_insert(tb, iter, "\n", -1);
	}
	/* all subsequent lines are printed as normal (yes, this
	 * discards lines from poldiff_range_to_string_brief() */
	free(orig_s);
	apol_vector_t *levels = poldiff_range_get_levels(range);
	size_t i;
	for (i = 0; i < apol_vector_get_size(levels); i++) {
		poldiff_level_t *l = apol_vector_get_element(levels, i);
		s = poldiff_level_to_string_brief(diff, l);
		g_string_printf(string, "     %s", s);
		if (poldiff_level_get_form(l) != 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);
	}
	g_string_free(string, TRUE);
}
