/********************************************************************
 *            dvpreview.c
 *
 *  Thu Sep 14 21:36:56 2006
 *  Copyright  2006, 2007  Neil Williams
 *  linux@codehelp.co.uk
 *******************************************************************/
/*
    This package 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif
#include <stdio.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <string.h>
#include "dvpreview.h"
#include "support.h"

/* location of key file in xdg_config */
#define DVKEYDIR ".deb-gview"
/* name of key file in location. */
#define DVKEYFILE "preview"
/* key name for glob pattern */
#define DVKEYGLOB "pattern"
/* key name for application to spawn */
#define DVKEYPROG "viewer"
/* key name for whether to use a terminal. */
#define DVKEYTERM "use_terminal"
/* default terminal application */
#define DVDEFAULT "xterm"
/* default execute command */
#define DVEXECUTE "-e"
/* key name for execute command */
#define DVXCOMMAND "execute_command"
/* default man command */
#define DVMAN "man"
/* package terminal command */
#define DVCOMMAND "terminal"
/* key name for whether content is binary */
#define DVBINARY "binary_file"
/* key name for suffix, if any */
#define DVSUFFIX "file_suffix"

#define TMPPREFIX "dvXXXXXX"

static const gchar * comment = NULL;
static gchar * terminal = NULL;
static gchar * execute = NULL;
static GList * preview_list = NULL;

typedef struct {
	gboolean use_term;
	gboolean use_binary;
	gchar * suffix;
	gchar * pattern;
	gchar * viewer;
	GPatternSpec * spec;
	const gchar * filename;
} DVPreview;

static void dv_push_error (DVContents * deb, GError *gerr) {
	GtkWidget *sbar;
	guint s_id;
	gchar * msg;

	sbar = lookup_widget(GTK_WIDGET(dv_get_parent(deb)), "dvstatusbar");
	s_id = gtk_statusbar_get_context_id (GTK_STATUSBAR(sbar), "errors");
	gtk_statusbar_pop (GTK_STATUSBAR(sbar), s_id);
	/* todo: fix this translation marker */
	msg = g_strdup_printf (_("Unable to preview '%s'. "
		"%s"), dv_get_selected_file (deb), gerr->message);
	gtk_statusbar_push (GTK_STATUSBAR(sbar), s_id, msg);
	g_clear_error (&gerr);
}

static gchar** create_groups (GKeyFile * kf, const gchar * keyfile) {
	gsize len, num;
	gchar * kfdata;
	gchar **groups;
	GError * gerr = NULL;

	/* Translators: This text is much easier to read with
	a space after each newline */
	comment = g_strconcat (_(" deb-gview can use this "
	"dot-file in your XDG config directory (default: \n "
	"~/.config/deb-gview/preview) to specify which applications "
	"should\n be used to preview files from the package.\n "
	"You are free to edit this file, deb-gview will\n "
	"not modify it, although it will be replaced if it is\n "
	"deleted.\n The first matching pattern will be used, so\n "
	"put the most general wildcards (like *) at the end of\n "
	"this file.\n\n "),
	DVKEYGLOB, " = ", _("glob-style pattern to match the "
	"location of\n the file in the package (not the filesystem).\n "),
	DVKEYPROG, " = ", _("the program to call to view the file "
	"content\n (which must exist in your system PATH).\n "),
	DVKEYTERM, " = ", _("whether to use a terminal, TRUE or FALSE.\n "),
	DVBINARY, " = ", _("whether the viewer needs a binary file"
	",\n TRUE or FALSE.\n "),
	DVSUFFIX, " = ", _("if the viewer needs a specific suffix, add "
	"it here\n "), NULL);
	g_key_file_set_comment (kf, NULL, NULL, comment, NULL);

	/* package defaults */
	comment = _(" Your choice of terminal application.\n");
	g_key_file_set_string  (kf, PACKAGE, DVCOMMAND, DVDEFAULT);
	g_key_file_set_comment (kf, PACKAGE, NULL, comment, NULL);
	g_key_file_set_string  (kf, PACKAGE, DVXCOMMAND, DVEXECUTE);

	/* man pages */
	g_key_file_set_string (kf, _("manual pages"), DVKEYGLOB, 
		"./usr/share/man/man*/*");
	g_key_file_set_string (kf, _("manual pages"), DVKEYPROG, DVMAN);
	g_key_file_set_boolean (kf, _("manual pages"), DVKEYTERM, TRUE);
	g_key_file_set_boolean (kf, _("manual pages"), DVBINARY, FALSE);
	g_key_file_set_string (kf, _("manual pages"), DVSUFFIX, "");

	/* html */
	g_key_file_set_string (kf, "HTML", DVKEYGLOB, "*.html");
	g_key_file_set_string (kf, "HTML", DVKEYPROG, "sensible-browser");
	g_key_file_set_boolean (kf, "HTML", DVKEYTERM,FALSE);
	g_key_file_set_boolean (kf, "HTML", DVBINARY, FALSE);
	g_key_file_set_string (kf, "HTML", DVSUFFIX, "html");

	/* png */
	g_key_file_set_string (kf, "PNG", DVKEYGLOB, "*.png");
	g_key_file_set_string (kf, "PNG", DVKEYPROG, "eog");
	g_key_file_set_boolean (kf, "PNG", DVKEYTERM, FALSE);
	g_key_file_set_boolean (kf, "PNG", DVBINARY, TRUE);
	g_key_file_set_string (kf, "PNG", DVSUFFIX, "png");

	/* text */
	g_key_file_set_string (kf, "text", DVKEYGLOB, "*");
	g_key_file_set_string (kf, "text", DVKEYPROG, "gedit");
	g_key_file_set_boolean (kf, "text", DVKEYTERM, FALSE);
	g_key_file_set_boolean (kf, "text", DVBINARY, FALSE);
	g_key_file_set_string (kf, "text", DVSUFFIX, "");

	kfdata = g_key_file_to_data(kf, &len, &gerr);
	if (gerr != NULL) {
		g_warning ("%s", gerr->message);
		g_clear_error (&gerr);
		return NULL;
	}
	g_file_set_contents (keyfile, kfdata, len, &gerr);
	if (gerr != NULL) {
		g_warning ("%s", gerr->message);
		g_clear_error (&gerr);
		return NULL;
	}
	groups = g_key_file_get_groups (kf, &num);
	return groups;
}

static gboolean parse_groups (GKeyFile * kf, gchar **groups, const gchar * keyfile) {
	guint num, c;
	gchar * file_glob, * prog, * path_prog;
	GError * gerr;
	gboolean use_term;
	DVPreview * prev;

	gerr = NULL;
	use_term = FALSE;
	num = g_strv_length(groups);
	if (num == 0) {
		groups = create_groups (kf, keyfile);
		num = g_strv_length(groups);
	}
	if (!groups) {
		return FALSE;
	}
	terminal = g_key_file_get_string (kf, PACKAGE, DVCOMMAND, &gerr);
	if (gerr != NULL) {
		g_warning ("%s", gerr->message);
		g_clear_error (&gerr);
	}
	execute  = g_key_file_get_string (kf, PACKAGE, DVXCOMMAND, &gerr);
	if (gerr != NULL) {
		g_warning ("%s", gerr->message);
		g_clear_error (&gerr);
	}
	for (c = 0;c < num; c++) {
		if (0 == g_ascii_strcasecmp(groups[c], PACKAGE))
			continue;
		file_glob = g_key_file_get_string (kf, groups[c], DVKEYGLOB, &gerr);
		if (!file_glob) {
			g_warning ("%s", gerr->message);
			g_clear_error (&gerr);
			continue;
		}
		use_term = g_key_file_get_boolean (kf, groups[c], DVKEYTERM, &gerr);
		if (gerr != NULL) {
			g_warning ("%s", gerr->message);
			g_clear_error (&gerr);
			continue;
		}
		prog = g_key_file_get_string (kf, groups[c], DVKEYPROG, &gerr);
		if (!prog) {
			g_warning ("%s", gerr->message);
			g_clear_error (&gerr);
			continue;
		}
		prev = g_new0 (DVPreview, 1);
		if (!use_term) {
			path_prog = g_find_program_in_path (prog);
			prev->viewer = path_prog;
		} else {
			prev->viewer = prog;
		}
		prev->use_binary = g_key_file_get_boolean (kf, groups[c], DVBINARY, &gerr);
		prev->suffix = g_key_file_get_string (kf, groups[c], DVSUFFIX, &gerr);
		prev->pattern = file_glob;
		prev->spec = g_pattern_spec_new (prev->pattern);
		prev->use_term = use_term;
		preview_list = g_list_append (preview_list, prev);
		g_free (file_glob);
	}
	return TRUE;
}

gboolean preview_init (void) {
	const gchar * home, * xdg;
	gchar * path, * file, * xdg_file;
	gchar **groups;
	GKeyFile * kf;
	GError * gerr;
	gboolean success;

	gerr = NULL;
	kf = g_key_file_new ();
	/* xdg is the new default location. */
	xdg = g_get_user_config_dir ();
	path = g_build_filename (xdg, PACKAGE, NULL);
	if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
		g_mkdir_with_parents (path, 0755);
	}
	xdg_file = g_build_filename (xdg, PACKAGE, DVKEYFILE, NULL);
	/* if our file exists in the old home, migrate to xdg. */
	home = g_get_home_dir ();
	file = g_strconcat (home, "/", DVKEYDIR, "/", DVKEYFILE, NULL);
	if (g_file_test (file, G_FILE_TEST_IS_REGULAR)) {
		g_rename (file, xdg_file);
	}
	g_free (file);
	file = g_build_filename (xdg, PACKAGE, DVKEYFILE, NULL);
	success = g_key_file_load_from_file (kf, file, G_KEY_FILE_KEEP_COMMENTS, &gerr);
	groups = g_key_file_get_groups (kf, NULL);
	success = parse_groups (kf, groups, file);
	if (g_strv_length(groups) > 0) {
		g_strfreev (groups);
	}
	g_free (path);
	g_free (file);
	return success;
}

static void clear_list (gpointer value, gpointer user_data) {
	DVPreview * prev = (DVPreview*)value;
	if (!prev) {
		return;
	}
	g_pattern_spec_free (prev->spec);
	if (prev->viewer) {
		g_free (prev->viewer);
	}
	g_free (prev);
}

void preview_shutdown (void) {
	if (g_list_length(preview_list) > 0) {
		g_list_foreach (preview_list, clear_list, NULL);
	}
}

static DVPreview *
glob_key_pattern (const gchar * file, gssize len, const gchar * rev) {
	GList * run;
	DVPreview * prev;
	gboolean match;

	match = FALSE;
	for (run = preview_list; run != NULL; run = g_list_next(run)) {
		prev = (DVPreview*)run->data;
		if (!prev) {
			return NULL;
		}
		match = g_pattern_match (prev->spec, len, file, rev);
		if (match) {
			return prev;
		}
	}
	return NULL;
}

static DVPreview * get_preview_data (DVContents * deb) {
	const gchar * file;
	DVPreview * globbed;
	gchar * file_rev;
	GError * gerr;
	gssize len;

	gerr = NULL;
	/* match the archive filename against the pattern
	but pass the uncompressed temporary file to the
	spawned app. */
	file = g_filename_to_utf8(dv_get_selected_file (deb), -1, NULL, NULL, &gerr);
	if (!file) {
		dv_push_error (deb, gerr);
		return NULL;
	}
	len = g_utf8_strlen (file, 255);
	if (len == 0) {
		return NULL;
	}
	file_rev = g_utf8_strreverse (file, len);
	/* look up filename in keyfile list
	and glob the pattern */
	globbed = glob_key_pattern (file, len, file_rev);
	if (!globbed) {
		return NULL;
	}
	globbed->filename = file;
	return globbed;
}

void dv_spawn_external (GtkWidget * widget, DVContents * deb) {
	gchar *temp_file, * tmp_name, **argv;
	gpointer content;
	DVPreview * prev;
	GError * gerr;
	gint argc, f;
	gint64 size;
	FILE * tmp;

	gerr = NULL;
	temp_file = NULL;
	deb = (DVContents*) lookup_widget (GTK_WIDGET(widget), DVCONTENTS);
	g_return_if_fail (deb);
	prev = get_preview_data (deb);
	if (!prev) {
		g_set_error (&gerr, 1001, 1002, _("No matching pattern in the key file."));
		dv_push_error (deb, gerr);
		return;
	}
	/* glib temp files don't cope well with suffixes. */
	f = g_file_open_tmp (TMPPREFIX, &tmp_name, &gerr);
	if (g_utf8_strlen(prev->suffix, -1) == 0) {
		temp_file = g_strconcat (tmp_name, ".", prev->suffix, NULL);
		g_remove (tmp_name);
	} else {
		temp_file = g_strdup (tmp_name);
	}
	close (f);
	if (gerr != NULL) {
		dv_push_error (deb, gerr);
	}
	if (prev->use_binary == TRUE) {
		content = dv_get_raw_content (deb);
		if (!content) {
			return;
		}
		size = dv_get_content_length (deb);
		if (size == 0) {
			return;
		}
		tmp = fopen (temp_file, "wb");
		fwrite (content, size, 1, tmp);
		fclose (tmp);
	} else {
		content = dv_get_data_content (prev->filename, GTK_WIDGET(widget));
		if (!content) {
			return;
		}
		tmp = fopen (temp_file, "w");
		fwrite ((gchar*)content, strlen ((gchar*)content), 1, tmp);
		fclose (tmp);
	}
	g_clear_error (&gerr);
	if (!prev->use_term) {
		gchar * gui_cmd;
		if (!prev->viewer) {
			g_set_error (&gerr, 1001, 1002, 
				_("Could not find a suitable viewer in the key file."));
			dv_push_error (deb, gerr);
			return;
		}
		gui_cmd = g_strconcat (prev->viewer, " ", temp_file, NULL);
		g_shell_parse_argv (gui_cmd, &argc, &argv, &gerr);
		if (gerr != NULL) {
			dv_push_error (deb, gerr);
		}
		g_free (gui_cmd);
	} else {
		gchar * term_cmd = g_strconcat (terminal, " ", execute, 
			" \"", prev->viewer, " ", temp_file, "\"", NULL);
		g_shell_parse_argv (term_cmd, &argc, &argv, &gerr);
		if (gerr != NULL) {
			dv_push_error (deb, gerr);
		}
		g_free (term_cmd);
	}
	g_free (temp_file);
	g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &gerr);
	if (gerr != NULL) {
		dv_push_error (deb, gerr);
		return;
	}
	return;
}

gboolean dv_show_our_manpage (DVContents * deb) {
	GError * gerr;
	gchar **argv;
	gchar * command, * file, * file_rev, *ourterm;
	gint argc;
	DVPreview * globbed;
	gssize len;

	gerr = NULL;
	globbed = NULL;
	ourterm = (g_utf8_strlen(terminal, -1) != 0) ? terminal : DVDEFAULT;
	file = "./usr/share/man/man1/deb-gview.1.gz";
	len = g_utf8_strlen (file, 255);
	file_rev = g_utf8_strreverse (file, len);
	globbed = glob_key_pattern (file, len, file_rev);
	if (!globbed) {
		command = g_strconcat (ourterm, " ", execute, " \"", 
			DVMAN, " ", PACKAGE, "\"", NULL);
	} else {
		command = g_strconcat (ourterm, " ", execute, " \"", 
			globbed->viewer, " ", PACKAGE, "\"", NULL);
	}
	g_shell_parse_argv (command, &argc, &argv, &gerr);
	g_free (command);
	g_spawn_async (NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &gerr);
	if (gerr != NULL) {
		dv_push_error (deb, gerr);
		return FALSE;
	}
	return TRUE;
}
