#include <config.h>
#include <gtk/gtk.h>
#include <assert.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#include <eid-viewer/oslayer.h>
#include <eid-viewer/verify_cert.h>
#include "viewer_glade.h"
#include "gettext.h"
#include "gtk_globals.h"

#include "gtkui.h"
#include "thread.h"
#include "photo.h"
#include "certs.h"
#include "state.h"
#include "glib_util.h"
#include "logging.h"
#include "prefs.h"
#include "print.h"
#include "gtk_main.h"
#include "dataverify.h"
#include "check_version.h"
#include "certs.h"

#if GTK_CHECK_VERSION(3, 96, 0)
#define gtk_style_context_get_color(ct, s, cr) gtk_style_context_get_color(ct, cr)
#define gtk_init(a, b) gtk_init()
#endif

#ifndef _
#define _(s) gettext(s)
#endif

#include <curl/curl.h>
#include <check_version.h>

typedef void(*bindisplayfunc)(const char*, const void*, int);
typedef void(*clearfunc)(char*);

static GHashTable* binhash;
static guint statusbar_context = 0;

GHashTable* touched_labels;
GtkBuilder* builder;

extern char** environ;

struct statusupdate {
	gboolean spin;
	char* text;
};

/* Helper function for uistatus() */
static gboolean realstatus(gpointer data) {
	GtkStatusbar* sb = GTK_STATUSBAR(gtk_builder_get_object(builder, "statusbar"));
	GtkSpinner* spinner = GTK_SPINNER(gtk_builder_get_object(builder, "busy_spinner"));
	struct statusupdate *update = (struct statusupdate*)data;

	if(G_UNLIKELY(!statusbar_context)) {
		statusbar_context = gtk_statusbar_get_context_id(sb, "useless");
	}
	gtk_statusbar_remove_all(sb, statusbar_context);
	gtk_widget_set_visible(GTK_WIDGET(spinner), update->spin);
	g_object_set(G_OBJECT(spinner), "active", update->spin, NULL);
	if(update->text) {
		gtk_statusbar_push(sb, statusbar_context, update->text);
		g_free(update->text);
	}
	g_free(update);

	return FALSE;
}

/* Update the status bar and the spinner with a new status message. */
static void uistatus(gboolean spin, char* data, ...) {
	va_list ap, ac;
	struct statusupdate* update = g_new0(struct statusupdate, 1);

	if(data != NULL) {
		va_start(ap, data);
		va_copy(ac, ap);
		update->text = g_strdup_vprintf(data, ac);
		va_end(ac);
		va_end(ap);
	}
	update->spin = spin;
	/* Don't change UI elements from a background thread */
	g_main_context_invoke(NULL, realstatus, update);
}

gboolean show_upgrade_message(void *user_data) {
	struct upgrade_info *info = (struct upgrade_info*)user_data;
	GtkWindow *mainwin = GTK_WINDOW(gtk_builder_get_object(builder, "mainwin"));
	GtkWidget *dialog = gtk_message_dialog_new(mainwin, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, _("New version of the eID viewer available: version %d.%d.%d at %s"), info->new_version.major, info->new_version.minor, info->new_version.build, info->upgrade_url);
	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
	return FALSE;
}

void check_update() {
	long length;
	void *handle;
	GError *err = NULL;
	GtkToggleButton *check = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "update_auto"));
	if(!gtk_toggle_button_get_active(check)) return;
	char *xml = (char*)perform_http_request("https://eid.belgium.be/sites/default/files/software/eidversions.xml", &length, &handle);
	if(!xml) return;
	GKeyFile *osrelease = g_key_file_new();
	GMappedFile *file = g_mapped_file_new("/etc/os-release", FALSE, &err);
	GBytes *bytes = g_mapped_file_get_bytes(file);
	GByteArray *ba = g_bytes_unref_to_array(bytes);
	ba = g_byte_array_prepend(ba, (const guint8*)"[foo]\n", 6);
	gsize len;
	bytes = g_byte_array_free_to_bytes(ba);
	g_mapped_file_unref(file);
	char *distrel = g_strdup("unknown");
	if(g_key_file_load_from_bytes(osrelease, bytes, 0, &err)) {
		gchar *group = g_key_file_get_start_group(osrelease);
		if(g_key_file_has_key(osrelease, group, "ID", NULL) && !g_key_file_has_key(osrelease, group, "VERSION_ID", NULL)) {
			distrel = g_strdup_printf("%s%s", g_key_file_get_string(osrelease, group, "ID", NULL), g_key_file_get_string(osrelease, group, "VERSION_ID", NULL));
		}
	} else {
		uilog(EID_VWR_LOG_COARSE, "Could not parse /etc/os-release: %s", err->message);
	}
	gchar **version_and_rest = g_strsplit(PACKAGE_VERSION, "-", 2);
	gchar **version_parts = g_strsplit(version_and_rest[0], ".", 4);
	struct upgrade_info *info = eid_vwr_upgrade_info(xml, (size_t)length, "linux", distrel, g_ascii_strtoll(version_parts[0], NULL, 10), g_ascii_strtoll(version_parts[1], NULL, 10), g_ascii_strtoll(version_parts[2], NULL, 10));
	if(info->have_upgrade) {
		g_main_context_invoke(NULL, (GSourceFunc)show_upgrade_message, (void*)info);
	}
	g_strfreev(version_and_rest);
}

/* Handle "state changed" elements */
static void newstate(enum eid_vwr_states s) {
	GObject *open, *save, *export_photo, *print, *close, *pintest, *pinchg, *validate;
#define want_verify (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "validate_always"))))
	open = gtk_builder_get_object(builder, "mi_file_open");
	save = gtk_builder_get_object(builder, "mi_file_saveas");
	export_photo = gtk_builder_get_object(builder, "mi_file_export_photo");
	print = gtk_builder_get_object(builder, "mi_file_print");
	close = gtk_builder_get_object(builder, "mi_file_close");
	pintest = gtk_builder_get_object(builder, "pintestbut");
	pinchg = gtk_builder_get_object(builder, "pinchangebut");
	validate = gtk_builder_get_object(builder, "validate_now");

	g_object_set_threaded(open, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(close, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(print, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(save, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(export_photo, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(pintest, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(pinchg, "sensitive", (void*)FALSE, NULL);
	g_object_set_threaded(validate, "sensitive", (void*)FALSE, NULL);
	g_object_set_data_threaded(validate, "want_active", (void*)FALSE, NULL);
	switch(s) {
		case STATE_LIBOPEN:
		case STATE_CALLBACKS:
			uistatus(TRUE, _("Initialising"));
			check_update();
			return;
		case STATE_READY:
			uistatus(FALSE, _("Ready to read identity card"));
			g_object_set_threaded(open, "sensitive", (void*)TRUE, NULL);
			disable_dnd();
			return;
		case STATE_NO_READER:
			uistatus(FALSE, _("No cardreader found"));
			g_object_set_threaded(open, "sensitive", (void*)TRUE, NULL);
			disable_dnd();
			return;
		case STATE_TOKEN:
			uistatus(TRUE, _("Card available"));
			return;
		case STATE_TOKEN_IDLE:
			uistatus(FALSE, "");
			g_object_set_threaded(print, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(save, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(export_photo, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(pintest, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(pinchg, "sensitive", (void*)TRUE, NULL);
			g_object_set_data_threaded(validate, "want_active", (void*)TRUE, NULL);
			if(want_verify) {
				validate_all(NULL, NULL);
			} else {
				g_object_set_threaded(validate, "sensitive", (void*)TRUE, NULL);
			}
			setup_dnd();
			return;
		case STATE_TOKEN_ID:
			uistatus(TRUE, _("Reading identity"));
			return;
		case STATE_TOKEN_CERTS:
			uistatus(TRUE, _("Reading certificates"));
			return;
		case STATE_TOKEN_ERROR:
			uistatus(FALSE, _("Failed to read identity data"));
			return;
		case STATE_TOKEN_PINOP:
			uistatus(TRUE, _("Performing a PIN operation"));
			return;
		case STATE_FILE_WAIT:
			uistatus(FALSE, "");
			g_object_set_threaded(print, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(export_photo, "sensitive", (void*)TRUE, NULL);
			g_object_set_threaded(close, "sensitive", (void*)TRUE, NULL);
			g_object_set_data_threaded(validate, "want_active", (void*)TRUE, NULL);
			if(want_verify) {
				validate_all(NULL, NULL);
			} else {
				g_object_set_threaded(validate, "sensitive", (void*)TRUE, NULL);
			}
			setup_dnd();
		default:
			return;
	}
}

/* Clear a string element from the UI */
static void stringclear(const char* l) {
	GtkLabel* label = GTK_LABEL(gtk_builder_get_object(builder, l));
	/* Should only appear in the hash table if we successfully found it
	   earlier... */
	assert(label != NULL);
	g_object_set_threaded(G_OBJECT(label), "label", "-", NULL);
	g_object_set_threaded(G_OBJECT(label), "sensitive", FALSE, NULL);
}

/* Add a new string to the UI */
static void newstringdata(const char* l, const char* data) {
	GtkLabel* label = GTK_LABEL(gtk_builder_get_object(builder, l));
	if(!label) {
		char* msg = g_strdup_printf(_("Could not display label '%s', data '%s': no GtkLabel found for data"), l, data);
		uilog(EID_VWR_LOG_DETAIL, msg);
		g_free(msg);
		return;
	}
	if(!data || strlen(data) == 0) {
		stringclear(l);
		return;
	}
	/* Remember which elements we've touched, so we can clear them
	 * again later on */
	g_hash_table_insert(touched_labels, g_strdup(l), stringclear);
	g_object_set_threaded(G_OBJECT(label), "label", g_strdup(data), g_free);
	g_object_set_threaded(G_OBJECT(label), "sensitive", (void*)TRUE, NULL);
}

/* Add a new binary data element to the UI */
static void newbindata(const char* label, const unsigned char* data, int datalen) {
	bindisplayfunc func;
	gchar* msg;

	if(!g_hash_table_contains(binhash, label)) {
		msg = g_strdup_printf(_("Could not display binary data with label '%s': not found in hashtable"), label);
		uilog(EID_VWR_LOG_DETAIL, msg);
		free(msg);
		return;
	}
	func = (bindisplayfunc)g_hash_table_lookup(binhash, label);
	func(label, data, datalen);
	return;
}

/* Helper function for newsrc() */
static gboolean cleardata(gpointer key, gpointer value, gpointer user_data G_GNUC_UNUSED) {
	clearfunc func = (clearfunc)value;
	char* k = key;
	func(k);
	return TRUE;
}

/* Handle the "new source" event from the state machine */
static void newsrc(enum eid_vwr_source src) {
	clear_certdata();
	g_hash_table_foreach(touched_labels, (GHFunc)cleardata, NULL);
	if(src == EID_VWR_SRC_CARD) {
		g_object_set_threaded(G_OBJECT(gtk_builder_get_object(builder, "mainwin")), "urgency-hint", (void*)TRUE, NULL);
	}
	// TODO: update display so we see which source we're using
}

/* Figure out what language we should use from environment variables */
enum eid_vwr_langs langfromenv() {
	char* p;
	char* all = NULL;
	char* msg = NULL;
	char* lang = NULL;
	int i;

	for(i=0, p=environ[0];p != NULL; i++, p=environ[i]) {
		if(!strncmp(p, "LC_ALL=", 7)) {
			all = p+7;
		}
		if(!strncmp(p, "LC_MESSAGES=", 12)) {
			msg = p+12;
		}
		if(!strncmp(p, "LANG=", 5)) {
			lang = p+5;
		}
	}
	if(all != NULL) {
		p = all;
	} else if(msg != NULL) {
		p = msg;
	} else if(lang != NULL) {
		p = lang;
	} else {
		p = "";
	}
	if(!strncmp(p, "de", 2)) {
		return EID_VWR_LANG_DE;
	}
	if(!strncmp(p, "fr", 2)) {
		return EID_VWR_LANG_FR;
	}
	if(!strncmp(p, "nl", 2)) {
		return EID_VWR_LANG_NL;
	}
	return EID_VWR_LANG_EN;
}

void do_settings(GtkMenuItem *item G_GNUC_UNUSED, gpointer user_data G_GNUC_UNUSED) {
	gtk_widget_show(GTK_WIDGET(gtk_builder_get_object(builder, "optwin")));
}

/* Connect UI event signals to callback functions */
static void connect_signals(GtkWidget* window) {
	GObject* signaltmp;
	GObject* photo;

	g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_open"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(file_open), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_saveas"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(file_save), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_export_photo"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(file_export_photo), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "reader_auto"));
	g_signal_connect(signaltmp, "toggled", G_CALLBACK(auto_reader), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_close"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(file_close), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_print"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(do_print), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_settings"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(do_settings), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_file_quit"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(gtk_main_quit), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "language_de"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(translate), "de_BE.UTF-8");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "language_en"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(translate), "en_US.UTF-8");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "language_fr"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(translate), "fr_BE.UTF-8");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "language_nl"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(translate), "nl_BE.UTF-8");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_help_about"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(showabout), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_help_faq"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(showurl), "faq");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_help_test"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(showurl), "test");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_cert_detail"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(certdetail), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_cert_export_der"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(certexport), "DER");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_cert_export_pem"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(certexport), "PEM");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "mi_cert_export_chain"));
	g_signal_connect(signaltmp, "activate", G_CALLBACK(certexport), "chain");
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "pintestbut"));
	g_signal_connect(signaltmp, "clicked", G_CALLBACK(pinop), (void*)EID_VWR_PINOP_TEST);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "pinchangebut"));
	g_signal_connect(signaltmp, "clicked", G_CALLBACK(pinop), (void*)EID_VWR_PINOP_CHG);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "photobox"));
	photo = G_OBJECT(gtk_builder_get_object(builder, "photo"));
#if GTK_CHECK_VERSION(4, 0, 0)
        GtkDragSource *dragsrc = gtk_drag_source_new();
        g_signal_connect(dragsrc, "prepare", G_CALLBACK(drag_data_get), dragsrc);
        gtk_widget_add_controller(photo, dragsrc);
#else
	g_signal_connect(signaltmp, "drag-data-get", G_CALLBACK(drag_data_get), photo);
#endif
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "validate_now"));
	g_signal_connect(signaltmp, "clicked", G_CALLBACK(validate_all), NULL);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "validate_always"));
	g_signal_connect(signaltmp, "toggled", G_CALLBACK(validate_toggle), NULL);

	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "optwin"));
	g_signal_connect(signaltmp, "delete-event", G_CALLBACK(gtk_widget_hide_on_delete), NULL);

	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "cert_paned"));
	g_settings_bind(get_prefs(), "cert-paned-pos", signaltmp, "position", 0);
	signaltmp = G_OBJECT(gtk_builder_get_object(builder, "update_auto"));
	g_settings_bind(get_prefs(), "check-update", signaltmp, "active", 0);
}

/* Set the colour of the "valid from" and "valid until" elements on the
 * Certificate page to red if they are incorrectly in the future or
 * past. */
static void show_date_state(char* label, void* data, int length G_GNUC_UNUSED) {
	gchar* labelname = g_strndup(label, strstr(label, ":") - label);
	GtkLabel* l = GTK_LABEL(gtk_builder_get_object(builder, labelname));
	PangoAttrList *attrs = pango_attr_list_new();
	PangoAttribute *attr;
	gboolean* is_invalid = (gboolean*)data;

	g_free(labelname);
	if(*is_invalid) {
		attr = pango_attr_foreground_new(G_MAXUINT16, 0, 0);
	} else {
		GdkRGBA color;
		GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(l));

		gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
		attr = pango_attr_foreground_new(color.red * G_MAXUINT16, color.green * G_MAXUINT16, color.blue * G_MAXUINT16);
	}
	pango_attr_list_insert(attrs, attr);
	gtk_label_set_attributes(l, attrs);
}

static void clear_basic_valid(const char *label G_GNUC_UNUSED) {
	PangoAttrList *attrs = pango_attr_list_new();
	PangoAttribute *attr;
	GdkRGBA color;
	GtkLabel *l = GTK_LABEL(gtk_builder_get_object(builder, "basic_key_hash"));
	GtkStyleContext *style = gtk_widget_get_style_context(GTK_WIDGET(l));

	gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
	attr = pango_attr_foreground_new(color.red * G_MAXUINT16, color.green * G_MAXUINT16, color.blue * G_MAXUINT16);
	pango_attr_list_insert(attrs, attr);
	gtk_label_set_attributes(l, attrs);
}

static void set_basic_valid(char* label, void* data, int length G_GNUC_UNUSED) {
	GtkLabel *l = GTK_LABEL(gtk_builder_get_object(builder, "basic_key_hash"));
	PangoAttrList *attrs = pango_attr_list_new();
	PangoAttribute *attr;
	int *ptr = data;

	gboolean is_valid = (*ptr) == 1 ? TRUE : FALSE;
	if(is_valid) {
		// set to green
		attr = pango_attr_foreground_new(0, G_MAXUINT16, 0);
	} else {
		// set to red
		attr = pango_attr_foreground_new(G_MAXUINT16, 0, 0);
	}
	pango_attr_list_insert(attrs, attr);
	gtk_label_set_attributes(l, attrs);
	g_hash_table_insert(touched_labels, g_strdup(label), clear_basic_valid);
}

static void toggleclear(const char* l) {
	GtkToggleButton* tb = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, l));
	gtk_toggle_button_set_active(tb, FALSE);
}

static void set_family(char* label G_GNUC_UNUSED, void* data G_GNUC_UNUSED, int length G_GNUC_UNUSED) {
	GtkCheckButton* cb = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "member_of_family"));
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE);
	g_hash_table_insert(touched_labels, g_strdup("member_of_family"), toggleclear);
}

static void set_hex(char* label, void* data, int length) {
	char string[length * 2 + 1];
	unsigned char *source = (unsigned char*)data;
	for(int i=0; i < length; i+=2) {
		snprintf(&string[i], 3, "%02x", (unsigned int)(source[i/2]));
	}
	string[length * 2] = '\0';
	newstringdata(label, string);
}

static void set_applvers(char* label, void* data, int length) {
	unsigned char *source = (unsigned char*)data;
	if(length != 1) {
		uilog(EID_VWR_LOG_ERROR, "Invalid value for %s: length %d, expected 1", label, length);
		return;
	}
	switch(*source) {
		case 0x17:
			newstringdata(label, "1.7");
			break;
		case 0x18:
			newstringdata(label, "1.8");
			break;
		default:
			newstringdata(label, "?");
	}
}

/* Initialize the hash table for binary data */
static void bindata_init() {
	binhash = g_hash_table_new(g_str_hash, g_str_equal);

	g_hash_table_insert(binhash, "PHOTO_FILE", displayphoto);
	g_hash_table_insert(binhash, "photo_hash", photohash);
	g_hash_table_insert(binhash, "CERT_RN_FILE", add_certificate);
	g_hash_table_insert(binhash, "Authentication", add_certificate);
	g_hash_table_insert(binhash, "CA", add_certificate);
	g_hash_table_insert(binhash, "Root", add_certificate);
	g_hash_table_insert(binhash, "Signature", add_certificate);
	g_hash_table_insert(binhash, "certvalfromval:past", show_date_state);
	g_hash_table_insert(binhash, "certvaltilval:future", show_date_state);
	g_hash_table_insert(binhash, "certimage", show_cert_image);
	g_hash_table_insert(binhash, "document_type_raw", update_doctype);
	g_hash_table_insert(binhash, "member_of_family", set_family);
	g_hash_table_insert(binhash, "basic_key_hash", set_hex);
	g_hash_table_insert(binhash, "basic_key_verify:valid", set_basic_valid);
	g_hash_table_insert(binhash, "carddata_appl_version", set_applvers);
}

/* Helper function for update_info() */
static void update_info_detail(GtkTreeModel* model, GtkTreePath *path G_GNUC_UNUSED, GtkTreeIter* iter, gpointer data G_GNUC_UNUSED) {
	gchar *from, *to, *use, *certdata, *trustval;
	gboolean past, future;
	GdkPixbuf *image;

	gtk_tree_model_get(model, iter,
			CERT_COL_VALIDFROM, &from,
			CERT_COL_VALIDTO, &to,
			CERT_COL_USE, &use,
			CERT_COL_DESC, &certdata,
			CERT_COL_VALIDFROM_PAST, &past,
			CERT_COL_VALIDTO_FUTURE, &future,
			CERT_COL_IMAGE, &image,
			CERT_COL_VALIDITY, &trustval,
			-1);
	newstringdata("certvalfromval", from);
	newstringdata("certvaltilval", to);
	newstringdata("certuseval", use);
	newstringdata("certdata", certdata);
	newstringdata("certtrustval", trustval);
	newbindata("certimage", (unsigned char*)image, -1);
	newbindata("certvalfromval:past", (unsigned char*)(&past), sizeof(gboolean));
	newbindata("certvaltilval:future", (unsigned char*)(&future), sizeof(gboolean));
}

/* Called when the user changes the selection of the treeview on the
 * certificates tab */
void update_info(GtkTreeSelection* sel, gpointer user_data G_GNUC_UNUSED) {
	gtk_tree_selection_selected_foreach(sel, update_info_detail, NULL);
}

/* Called when the user clicks on the treeview on the certificates tab */
static gboolean show_menu(GtkWidget* widget G_GNUC_UNUSED, GdkEvent* event, gpointer user_data G_GNUC_UNUSED) {
	GtkMenu* menu = GTK_MENU(gtk_builder_get_object(builder, "certmenu"));
	guint button;
	gdk_event_get_button(event, &button);
	if(button == 3) { // RMB click
#if GTK_CHECK_VERSION(3, 22, 0)
		gtk_menu_popup_at_pointer(menu, event);
#else
		gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button.button, event->button.time);
#endif
	}
	return FALSE;
}

/* Initialize the treeview on the certificates tab: connect the model,
 * and connect signal handlers */
static void setup_treeview() {
	GtkTreeView* tv = GTK_TREE_VIEW(gtk_builder_get_object(builder, "tv_cert"));
	GtkCellRenderer *renderer;
	GtkTreeViewColumn *col;
	GtkTreeSelection* sel = gtk_tree_view_get_selection(tv);

	gtk_tree_view_set_model(tv, certificates_get_model());
	renderer = gtk_cell_renderer_text_new();
	col = gtk_tree_view_column_new_with_attributes("label", renderer, "text", 0, NULL);
	gtk_tree_view_append_column(tv, col);

	g_signal_connect(G_OBJECT(sel), "changed", G_CALLBACK(update_info), NULL);
	g_signal_connect(G_OBJECT(tv), "button-press-event", G_CALLBACK(show_menu), NULL);
}

#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <beid_fuzz.h>

void init_directory(char* name) {
	DIR *dir = opendir(name);
	struct dirent *de;
	while((de = readdir(dir)) != NULL) {
		gchar *fullname = g_strdup_printf("%s/%s", name, de->d_name);
		if(de->d_type == DT_UNKNOWN) {
			struct stat st;
			lstat(fullname, &st);
			if(!(st.st_mode & S_IFREG)) {
				continue;
			}
		} else {
			if(de->d_type != DT_REG) {
				continue;
			}
		}
		uint8_t buf[65536];
		ssize_t data_read;
		int fd = open(fullname, O_RDONLY);
		data_read = read(fd, buf, sizeof buf);
		close(fd);
		beid_set_fuzz_data(buf, data_read, de->d_name);
	}
	beid_set_fuzz_only(0);
}
#endif

/* Main entry point */
int main(int argc, char** argv) {
	GtkWidget *window;
	GtkAccelGroup *group;
	struct eid_vwr_ui_callbacks* cb;
	GError* err = NULL;

#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
	if(argc > 1) {
		init_directory(argv[1]);
	}
	argc--; argv++;
#endif

	/* The GNU implementation of setlocale() ignores whatever we
	 * specify if the LANGUAGE environment variable has a value, so
	 * ensure that it doesn't
	 */
	putenv("LANGUAGE=");
	bindtextdomain("eid-viewer", DATAROOTDIR "/locale");
	textdomain("eid-viewer");

	eid_vwr_convert_set_lang(langfromenv());

	gtk_init(&argc, &argv);
	builder = gtk_builder_new();
	if(gtk_builder_add_from_string(builder, VIEWER_GLADE_STRING, strlen(VIEWER_GLADE_STRING), &err) == 0) {
		g_critical("Could not parse Glade XML: %s", err->message);
		exit(EXIT_FAILURE);
	}

	window = GTK_WIDGET(gtk_builder_get_object(builder, "mainwin"));
	group = gtk_accel_group_new();
	gtk_window_add_accel_group(GTK_WINDOW(window), group);

	touched_labels = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);

	bindata_init();
	connect_signals(window);
	setup_treeview();

	certs_init();

	cb = eid_vwr_cbstruct();
	cb->newsrc = newsrc;
	cb->newstringdata = newstringdata;
	cb->newbindata = newbindata;
	cb->logv = ui_log_init();
	uilog(EID_VWR_LOG_NORMAL, "eID Viewer version: %s", PACKAGE_VERSION);
	cb->newstate = newstate;
	cb->pinop_result = pinop_result;
	cb->readers_changed = readers_changed;
	if(cb->version >= 1) {
		cb->challenge_result = eid_vwr_challenge_result;
	} else {
		uilog(EID_VWR_LOG_DETAIL, "Unexpected version of libeidviewer: callback version incorrect. Ignoring challenge.");
	}
	eid_vwr_createcallbacks(cb);

	eid_vwr_init_crypto();

	gtk_window_set_default_icon_name("eid-viewer");

	gtk_widget_show(window);

	if(argc > 1) {
		eid_vwr_be_deserialize(argv[1]);
	}

	gtk_main();

	return 0;
}
