/*
 * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 *
 * 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; version 2 of the License (not later!)
 *
 * 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>
 *
 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <fcntl.h>
#include <ctype.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <signal.h>
#include <unistd.h>
#include <pthread.h>
#include <gtk/gtk.h>
#include <errno.h>
#include <getopt.h>

#include "trace-cmd.h"
#include "trace-gui.h"
#include "trace-local.h"
#include "kernel-shark.h"
#include "version.h"

#define default_output_file "trace.dat"

#define PLUGIN_NONE "NONE"

#define DIALOG_WIDTH	820
#define DIALOG_HEIGHT	600

#define CAP_STOP	"Stop"

#define DEFAULT_MAX_BUF_SIZE 1000000

struct trace_capture {
	struct pevent		*pevent;
	struct shark_info	*info;
	GtkWidget		*main_dialog;
	GtkWidget		*command_entry;
	GtkWidget		*file_entry;
	GtkWidget		*output_text;
	GtkTextBuffer		*output_buffer;
	GtkWidget		*event_view;
	GtkWidget		*plugin_combo;
	GtkWidget		*settings_combo;
	GtkWidget		*run_button;
	GtkWidget		*max_num_entry;
	pthread_t		thread;
	gboolean		kill_thread;
	gboolean		capture_done;
	gboolean		load_file;
	int			command_input_fd;
	int			command_output_fd;
	int			command_pid;
};

static int is_just_ws(const char *str)
{
	int i;

	for (i = 0; str[i]; i++)
		if (!isspace(str[i]))
			break;
	return !str[i];
}

static GString *get_home_settings_new(void)
{
	char *path = getenv("HOME");
	GString *str;

	str = g_string_new(path);
	g_string_append(str, "/.trace-cmd/settings/");
	return str;
}

static int create_home_settings(void)
{
	char *path = getenv("HOME");
	GString *str;
	struct stat st;
	int ret;

	str = g_string_new(path);
	g_string_append(str, "/.trace-cmd");
	ret = stat(str->str, &st);
	if (ret < 0) {
		ret = mkdir(str->str, 0755);
		if (ret < 0) {
			warning("Can not create %s", str->str);
			goto out;
		}
	}

	g_string_append(str, "/settings");
	ret = stat(str->str, &st);
	if (ret < 0) {
		ret = mkdir(str->str, 0755);
		if (ret < 0) {
			warning("Can not create %s", str->str);
			goto out;
		}
	}

	ret = 0;
 out:
	g_string_free(str, TRUE);
	return ret;
}

static GtkTreeModel *create_settings_model(gpointer data)
{
	struct dirent *dent;
	GtkListStore *list;
	GtkTreeIter iter;
	struct stat st;
	GString *str;
	DIR *dir;
	int ret;

	list = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);

	gtk_list_store_append(list, &iter);
	gtk_list_store_set(list, &iter,
			   0, "Current",
			   1, "",
			   -1);

	/* Search for user settings first */
	str = get_home_settings_new();
	ret = stat(str->str, &st);
	if (ret < 0 || !S_ISDIR(st.st_mode))
		goto read_system;

	dir = opendir(str->str);
	if (!dir)
		goto read_system;

	while ((dent = readdir(dir))) {
		const char *name = dent->d_name;
		GString *file;
		gchar *item;


		if (strcmp(name, ".") == 0 ||
		    strcmp(name, "..") == 0)
			continue;

		if (strcmp(name + strlen(name) - 4, ".kss") != 0)
			continue;

		file = g_string_new(str->str);
		g_string_append_printf(file, "/%s", name);

		/* Save the file name but remove the .kss extention */
		item = g_strdup(name);
		item[strlen(name) - 4] = 0;

		gtk_list_store_append(list, &iter);
		gtk_list_store_set(list, &iter,
				   0, item,
				   1, file->str,
				   -1);
		g_free(item);
		g_string_free(file, TRUE);
	}

read_system:
	g_string_free(str, TRUE);

	return GTK_TREE_MODEL(list);
}

static void refresh_settings(struct trace_capture *cap)
{
	GtkTreeModel *model;

	model = create_settings_model(NULL);
	gtk_combo_box_set_model(GTK_COMBO_BOX(cap->settings_combo), model);
	g_object_unref(model);
}

static void ks_clear_capture_events(struct shark_info *info)
{
	info->cap_all_events = FALSE;

	tracecmd_free_list(info->cap_systems);
	info->cap_systems = NULL;

	free(info->cap_events);
	info->cap_events = NULL;
}

static void clear_capture_events(struct trace_capture *cap)
{
	ks_clear_capture_events(cap->info);
}

void kernel_shark_clear_capture(struct shark_info *info)
{
	ks_clear_capture_events(info);

	g_free(info->cap_plugin);
	info->cap_plugin = NULL;

	g_free(info->cap_settings_name);
	info->cap_settings_name = NULL;

	free(info->cap_file);
	info->cap_file = NULL;

	g_free(info->cap_buffer_output);
	info->cap_buffer_output = NULL;
}

static gboolean end_capture(struct trace_capture *cap)
{
	const char *filename;
	const char *val;
	int pid;

	val = gtk_button_get_label(GTK_BUTTON(cap->run_button));
	if (strcmp(val, CAP_STOP) != 0)
		return FALSE;

	gtk_button_set_label(GTK_BUTTON(cap->run_button), "Run");

	cap->capture_done = TRUE;

	pid = cap->command_pid;
	cap->command_pid = 0;
	if (pid) {
		kill(pid, SIGINT);
		gdk_threads_leave();
		waitpid(pid, NULL, 0);
		gdk_threads_enter();
	}

	if (cap->kill_thread) {
		gdk_threads_leave();
		pthread_join(cap->thread, NULL);
		gdk_threads_enter();
		cap->kill_thread = FALSE;
	}

	if (cap->command_input_fd) {
		close(cap->command_input_fd);
		cap->command_input_fd = 0;
	}

	if (cap->command_output_fd) {
		close(cap->command_output_fd);
		cap->command_output_fd = 0;
	}

	if (cap->load_file) {
		filename = gtk_entry_get_text(GTK_ENTRY(cap->file_entry));
		kernelshark_load_file(cap->info, filename);
		cap->load_file = FALSE;
	}

	return TRUE;
}

static int is_latency(char *plugin)
{
	return strcmp(plugin, "wakeup") == 0 ||
		strcmp(plugin, "wakeup_rt") == 0 ||
		strcmp(plugin, "irqsoff") == 0 ||
		strcmp(plugin, "preemptoff") == 0 ||
		strcmp(plugin, "preemptirqsoff") == 0;
}

static int calculate_trace_cmd_words(struct trace_capture *cap)
{
	int words = 4;		/* trace-cmd record -o file */
	int i;

	if (cap->info->cap_all_events)
		words += 2;
	else {
		if (cap->info->cap_systems) {
			for (i = 0; cap->info->cap_systems[i]; i++)
				words += 2;
		}

		if (cap->info->cap_events)
			for (i = 0; cap->info->cap_events[i] >= 0; i++)
				words += 2;
	}

	if (cap->info->cap_plugin)
		words += 2;

	return words;
}

static char *find_tracecmd(void)
{
	struct stat st;
	char *path = getenv("PATH");
	char *saveptr;
	char *str;
	char *loc;
	char *tracecmd = NULL;
	int len;
	int ret;

	if (!path)
		return NULL;

	path = strdup(path);

	for (str = path; ; str = NULL) {
		loc = strtok_r(str, ":", &saveptr);
		if (!loc)
			break;
		len = strlen(loc) + 11;
		tracecmd = malloc_or_die(len);
		snprintf(tracecmd, len, "%s/trace-cmd", loc);
		ret = stat(tracecmd, &st);

		if (ret >= 0 && S_ISREG(st.st_mode)) {
			/* Do we have execute permissions */
			if (st.st_uid == geteuid() &&
			    st.st_mode & S_IXUSR)
				break;
			if (st.st_gid == getegid() &&
			    st.st_mode & S_IXGRP)
				break;
			if (st.st_mode & S_IXOTH)
				break;
		}

		free(tracecmd);
		tracecmd = NULL;
	}
	free(path);

	return tracecmd;
}

static int add_trace_cmd_words(struct trace_capture *cap, char **args)
{
	struct event_format *event;
	char **systems = cap->info->cap_systems;
	const gchar *output;
	int *events = cap->info->cap_events;
	int words = 0;
	int len;
	int i;

	output = gtk_entry_get_text(GTK_ENTRY(cap->file_entry));

	args[words++] = find_tracecmd();
	if (!args[0])
		return -1;

	args[words++] = strdup("record");
	args[words++] = strdup("-o");
	args[words++] = strdup(output);

	if (cap->info->cap_plugin) {
		args[words++] = strdup("-p");
		args[words++] = strdup(cap->info->cap_plugin);
	}

	if (cap->info->cap_all_events) {
		args[words++] = strdup("-e");
		args[words++] = strdup("all");
	} else {
		if (systems) {
			for (i = 0; systems[i]; i++) {
				args[words++] = strdup("-e");
				args[words++] = strdup(systems[i]);
			}
		}

		if (events) {
			for (i = 0; events[i] >= 0; i++) {
				event = pevent_find_event(cap->pevent, events[i]);
				if (!event)
					continue;
				args[words++] = strdup("-e");
				len = strlen(event->name) + strlen(event->system) + 2;
				args[words] = malloc_or_die(len);
				snprintf(args[words++], len, "%s:%s",
					 event->system, event->name);
			}
		}
	}

	return words;
}

static gchar *get_combo_text(GtkComboBox *combo)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gchar *text;

	model = gtk_combo_box_get_model(combo);
	if (!model)
		return NULL;

	if (!gtk_combo_box_get_active_iter(combo, &iter))
		return NULL;

	gtk_tree_model_get(model, &iter,
			   0, &text,
			   -1);

	return text;
}

static void update_plugin(struct trace_capture *cap)
{
	cap->info->cap_plugin = get_combo_text(GTK_COMBO_BOX(cap->plugin_combo));
	if (strcmp(cap->info->cap_plugin, PLUGIN_NONE) == 0) {
		g_free(cap->info->cap_plugin);
		cap->info->cap_plugin = NULL;
	}

}

/*
 * The plugin and settings combo's are set by the first item
 * in the model. The can share the same code to set the model.
 *
 * Return TRUE if set, FALSE if name was not found.
 */
static int set_combo(GtkComboBox *combo, const char *name)
{
	GtkTreeModel *model;
	GtkTreeIter iter;
	gchar *text;
	gboolean ret;

	model = gtk_combo_box_get_model(combo);
	if (!model)
		return FALSE;

	if (!gtk_tree_model_get_iter_first(model, &iter))
		return FALSE;

	do {
		gtk_tree_model_get(model, &iter,
				   0, &text,
				   -1);
		if (strcmp(text, name) == 0) {
			g_free(text);
			break;
		}

		g_free(text);

		ret = gtk_tree_model_iter_next(model, &iter);
	} while (ret);

	if (ret) {
		/* Found */
		gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo),
					      &iter);
		return TRUE;
	}

	/* set to first item (default) */
	gtk_tree_model_get_iter_first(model, &iter);
	gtk_combo_box_set_active_iter(GTK_COMBO_BOX(combo),
				      &iter);

	return FALSE;
}

static void set_plugin(struct trace_capture *cap)
{
	GtkComboBox *combo = GTK_COMBO_BOX(cap->plugin_combo);
	const gchar *plugin = cap->info->cap_plugin;

	if (!plugin)
		plugin = PLUGIN_NONE;

	if (set_combo(combo, plugin))
		return;

	/* Not found? */
	g_free(cap->info->cap_plugin);
	cap->info->cap_plugin = NULL;
}

static void set_settings(struct trace_capture *cap)
{
	GtkComboBox *combo = GTK_COMBO_BOX(cap->settings_combo);
	const gchar *name = cap->info->cap_settings_name;

	if (!name)
		name = "";

	if (set_combo(combo, name))
		return;

	/* Not found? */
	g_free(cap->info->cap_settings_name);
	cap->info->cap_settings_name = NULL;
}

static void update_events(struct trace_capture *cap)
{
	struct shark_info *info = cap->info;

	if (!cap->event_view)
		return;

	clear_capture_events(cap);

	trace_extract_event_list_view(cap->event_view,
				      &info->cap_all_events,
				      &info->cap_systems,
				      &info->cap_events);
}

static void execute_command(struct trace_capture *cap)
{
	const gchar *ccommand;
	gchar *command;
	gchar **args;
	gboolean space;
	int words;
	int tc_words;
	int i;

	update_plugin(cap);
	update_events(cap);

	ccommand = gtk_entry_get_text(GTK_ENTRY(cap->command_entry));
	if (!ccommand || !strlen(ccommand) || is_just_ws(ccommand)) {
		words = 0;
		command = NULL;
	} else {

		command = strdup(ccommand);

		space = TRUE;
		words = 0;
		for (i = 0; command[i]; i++) {
			if (isspace(command[i]))
				space = TRUE;
			else {
				if (space)
					words++;
				space = FALSE;
			}
		}
	}

	tc_words = calculate_trace_cmd_words(cap);

	args = malloc_or_die(sizeof(*args) * (tc_words + words + 1));

	add_trace_cmd_words(cap, args);

	words = tc_words;
	space = TRUE;
	for (i = 0; command && command[i]; i++) {
		if (isspace(command[i])) {
			space = TRUE;
			command[i] = 0;
		} else {
			if (space) {
				args[words] = &command[i];
				words++;
			}
			space = FALSE;
		}
	}
	args[words] = NULL;

	write(1, "# ", 2);
	for (i = 0; args[i]; i++) {
		write(1, args[i], strlen(args[i]));
		write(1, " ", 1);
	}
	write(1, "\n", 1);

	execvp(args[0], args);
	perror("execvp");

	for (i = 0; args[i]; i++)
		free(args[i]);
	free(args);
	g_free(cap->info->cap_plugin);
}

static void *monitor_pipes(void *data)
{
	struct trace_capture *cap = data;
	GtkTextIter start_iter;
	GtkTextIter cut_iter;
	GtkTextIter iter;
	gchar buf[BUFSIZ+1];
	struct timeval tv;
	const char *val;
	fd_set fds;
	gboolean eof;
	int max_size;
	int total;
	int del;
	int ret;
	int r;

	gdk_threads_enter();
	/* get the max size */
	val = gtk_entry_get_text(GTK_ENTRY(cap->max_num_entry));
	max_size = atoi(val);

	/* Clear the buffer */
	gtk_text_buffer_get_start_iter(cap->output_buffer, &start_iter);
	gtk_text_buffer_get_end_iter(cap->output_buffer, &cut_iter);
	gtk_text_buffer_delete(cap->output_buffer, &start_iter, &cut_iter);
	total = 0;
	gdk_threads_leave();

	do {
		FD_ZERO(&fds);
		FD_SET(cap->command_input_fd, &fds);
		tv.tv_sec = 6;
		tv.tv_usec = 0;
		ret = select(cap->command_input_fd+1, &fds, NULL, NULL, &tv);
		if (ret < 0)
			break;

		eof = TRUE;
		while ((r = read(cap->command_input_fd, buf, BUFSIZ)) > 0) {
			eof = FALSE;
			buf[r] = 0;
			total += r;
			if (total > max_size)
				del = total - max_size;
			else
				del = 0;
			gdk_threads_enter();
			if (del) {
				gtk_text_buffer_get_start_iter(cap->output_buffer, &start_iter);
				gtk_text_buffer_get_start_iter(cap->output_buffer, &cut_iter);
				gtk_text_iter_forward_chars(&cut_iter, del);
				gtk_text_buffer_delete(cap->output_buffer, &start_iter, &cut_iter);
				total -= del;
			}
			gtk_text_buffer_get_end_iter(cap->output_buffer,
						     &iter);
			gtk_text_buffer_insert(cap->output_buffer, &iter, buf, -1);
			gdk_threads_leave();
		}
	} while (!cap->capture_done && !eof);

	if (eof) {
		gdk_threads_enter();
		end_capture(cap);
		gdk_threads_leave();
	}

	pthread_exit(NULL);
}

static void run_command(struct trace_capture *cap)
{
	int brass[2];
	int copper[2];
	int pid;

	if (pipe(brass) < 0) {
		warning("Could not create pipe");
		return;
	}

	if (pipe(copper) < 0) {
		warning("Could not create pipe");
		goto fail_pipe;
	}

	if ((pid = fork()) < 0) {
		warning("Could not fork process");
		goto fail_fork;
	}

	cap->command_pid = pid;

	if (!pid) {
		close(brass[0]);
		close(copper[1]);
		close(0);
		close(1);
		close(2);

		dup(copper[0]);
		dup(brass[1]);
		dup(brass[1]);

		execute_command(cap);

		close(1);
		exit(0);
	}
	close(brass[1]);
	close(copper[0]);

	/* these should never be 0 */
	if (!brass[1] || !copper[0])
		warning("Pipes have zero as file descriptor");

	cap->command_input_fd = brass[0];
	cap->command_output_fd = copper[1];

	/* Do not create a thread under the gdk lock */
	gdk_threads_leave();
	if (pthread_create(&cap->thread, NULL, monitor_pipes, cap) < 0)
		warning("Failed to create thread");
	else {
		cap->kill_thread = TRUE;
		cap->load_file = TRUE;
	}
	gdk_threads_enter();

	return;

 fail_fork:
	close(copper[0]);
	close(copper[1]);
 fail_pipe:
	close(brass[0]);
	close(brass[1]);
}

static int trim_plugins(char **plugins)
{
	int len = 0;
	int i;

	if (!plugins)
		return 0;

	for (i = 0; plugins[i]; i++) {
		if (is_latency(plugins[i]))
			continue;
		plugins[len++] = plugins[i];
	}
	plugins[len] = NULL;

	return len;
}

static void
file_clicked (GtkWidget *widget, gpointer data)
{
	struct trace_capture *cap = data;
	gchar *filename;

	filename = trace_get_file_dialog_filter("Trace File", "Save",
					 TRACE_DIALOG_FILTER_DATA, FALSE);
	if (!filename)
		return;

	gtk_entry_set_text(GTK_ENTRY(cap->file_entry), filename);
}

static void execute_button_clicked(struct trace_capture *cap)
{
	struct stat st;
	GtkResponseType ret;
	const char *filename;
	char *tracecmd;

	if (end_capture(cap))
		return;

	tracecmd = find_tracecmd();
	if (!tracecmd) {
		warning("trace-cmd not found in path");
		return;
	}
	free(tracecmd);

	filename = gtk_entry_get_text(GTK_ENTRY(cap->file_entry));

	if (stat(filename, &st) >= 0) {
		ret = trace_dialog(GTK_WINDOW(cap->main_dialog), TRACE_GUI_ASK,
				   "The file '%s' already exists.\n"
				   "Are you sure you want to replace it",
				   filename);
		if (ret == GTK_RESPONSE_NO)
			return;
	}

	gtk_button_set_label(GTK_BUTTON(cap->run_button), CAP_STOP);
	run_command(cap);
}

static int load_events(struct trace_capture *cap,
			   struct tracecmd_xml_handle *handle,
			   struct tracecmd_xml_system_node *node)
{
	struct shark_info *info = cap->info;
	struct tracecmd_xml_system_node *event_node;
	struct event_format *event;
	struct pevent *pevent = cap->pevent;
	const char *name;
	int *events = NULL;
	int event_len = 0;
	const char *system;
	const char *event_name;

	for (node = tracecmd_xml_node_child(node); node;
	     node = tracecmd_xml_node_next(node)) {
		name = tracecmd_xml_node_type(node);

		if (strcmp(name, "Event") != 0)
			continue;

		event_node = tracecmd_xml_node_child(node);
		if (!event_node)
			continue;

		name = tracecmd_xml_node_type(event_node);
		if (strcmp(name, "System") != 0)
			continue;
		system = tracecmd_xml_node_value(handle, event_node);

		event_node = tracecmd_xml_node_next(event_node);
		if (!event_node)
			continue;

		name = tracecmd_xml_node_type(event_node);
		if (strcmp(name, "Name") != 0)
			continue;
		event_name = tracecmd_xml_node_value(handle, event_node);

		event = pevent_find_event_by_name(pevent, system, event_name);

		if (!event)
			continue;

		events = tracecmd_add_id(events, event->id, event_len++);
	}

	info->cap_events = events;
	return 0;
}

static int load_cap_events(struct trace_capture *cap,
			   struct tracecmd_xml_handle *handle,
			   struct tracecmd_xml_system_node *node)
{
	struct shark_info *info = cap->info;
	const char *name;
	char **systems = NULL;
	int sys_len = 0;

	ks_clear_capture_events(info);

	for (node = tracecmd_xml_node_child(node); node;
	     node = tracecmd_xml_node_next(node)) {

		name = tracecmd_xml_node_type(node);

		if (strcmp(name, "CaptureType") == 0) {
			name = tracecmd_xml_node_value(handle, node);
			if (strcmp(name, "all events") == 0) {
				info->cap_all_events = TRUE;
				break;
			}
			continue;

		} else if (strcmp(name, "System") == 0) {
			name = tracecmd_xml_node_value(handle, node);
			systems = tracecmd_add_list(systems, name, sys_len++);

		} else if (strcmp(name, "Events") == 0)
			load_events(cap, handle, node);
	}

	info->cap_systems = systems;

	return 0;
}

static void load_settings(struct trace_capture *cap, gchar *filename)
{
	struct shark_info *info = cap->info;
	struct tracecmd_xml_system_node *syschild;
	struct tracecmd_xml_handle *handle;
	struct tracecmd_xml_system *system;
	const char *plugin;
	const char *name;

	handle = tracecmd_xml_open(filename);
	if (!handle) {
		warning("Could not open %s", filename);
		return;
	}

	system = tracecmd_xml_find_system(handle, "CaptureSettings");
	if (!system)
		goto out;

	syschild = tracecmd_xml_system_node(system);
	if (!syschild)
		goto out_free_sys;

	g_free(info->cap_plugin);
	info->cap_plugin = NULL;

	do {
		name = tracecmd_xml_node_type(syschild);
		if (strcmp(name, "Events") == 0) {
			load_cap_events(cap, handle, syschild);
			trace_update_event_view(cap->event_view,
						cap->pevent,
						NULL,
						info->cap_all_events,
						info->cap_systems,
						info->cap_events);
		}

		else if (strcmp(name, "Plugin") == 0) {
			plugin = tracecmd_xml_node_value(handle, syschild);
			info->cap_plugin = g_strdup(plugin);

		} else if (strcmp(name, "Command") == 0) {
			name = tracecmd_xml_node_value(handle, syschild);
			gtk_entry_set_text(GTK_ENTRY(cap->command_entry), name);

		} else if (strcmp(name, "File") == 0) {
			name = tracecmd_xml_node_value(handle, syschild);
			gtk_entry_set_text(GTK_ENTRY(cap->file_entry), name);
		}

		syschild = tracecmd_xml_node_next(syschild);
	} while (syschild);

	set_plugin(cap);

 out_free_sys:
	tracecmd_xml_free_system(system);

 out:
	tracecmd_xml_close(handle);
}

static void settings_changed(GtkComboBox *combo,
			     gpointer data)
{
	struct trace_capture *cap = data;
	GtkTreeModel *model;
	GtkTreeIter iter;
	gchar *text;

	model = gtk_combo_box_get_model(combo);
	if (!model)
		return;

	if (!gtk_combo_box_get_active_iter(combo, &iter))
		return;

	gtk_tree_model_get(model, &iter,
			   1, &text,
			   -1);

	if (text && strlen(text))
		load_settings(cap, text);

	g_free(text);
}

static void import_settings_clicked(GtkWidget *widget, gpointer data)
{
	struct trace_capture *cap = data;
	gchar *filename;

	filename = trace_get_file_dialog_filter("Import Settings", NULL,
					 TRACE_DIALOG_FILTER_SETTING, FALSE);
	if (!filename)
		return;

	load_settings(cap, filename);

	g_free(filename);
}

static void save_events(struct trace_capture *cap,
			struct tracecmd_xml_handle *handle)
{
	struct pevent *pevent = cap->pevent;
	struct event_format *event;
	char **systems = cap->info->cap_systems;
	int *events = cap->info->cap_events;
	int i;

	tracecmd_xml_write_element(handle, "CaptureType", "Events");

	for (i = 0; systems && systems[i]; i++)
		tracecmd_xml_write_element(handle, "System", systems[i]);

	if (!events || events[0] < 0)
		return;

	tracecmd_xml_start_sub_system(handle, "Events");
	for (i = 0; events[i] > 0; i++) {
		event = pevent_find_event(pevent, events[i]);
		if (event) {
			tracecmd_xml_start_sub_system(handle, "Event");
			tracecmd_xml_write_element(handle, "System", event->system);
			tracecmd_xml_write_element(handle, "Name", event->name);
			tracecmd_xml_end_sub_system(handle);
		}
	}

	tracecmd_xml_end_sub_system(handle);
}

static void save_settings(struct trace_capture *cap, const char *filename)
{
	struct shark_info *info = cap->info;
	struct tracecmd_xml_handle *handle;
	const char *file;
	const char *command;

	handle = tracecmd_xml_create(filename, VERSION_STRING);
	if (!handle) {
		warning("Could not create %s", filename);
		return;
	}

	update_events(cap);

	tracecmd_xml_start_system(handle, "CaptureSettings");

	tracecmd_xml_start_sub_system(handle, "Events");

	if (info->cap_all_events)
		tracecmd_xml_write_element(handle, "CaptureType", "all events");
	else if ((info->cap_systems && info->cap_systems[0]) ||
		 (info->cap_events && info->cap_events[0] >= 0)) {
		save_events(cap, handle);
	}

	tracecmd_xml_end_sub_system(handle);

	update_plugin(cap);
	if (info->cap_plugin)
		tracecmd_xml_write_element(handle, "Plugin", info->cap_plugin);

	command = gtk_entry_get_text(GTK_ENTRY(cap->command_entry));
	if (command && strlen(command) && !is_just_ws(command))
		tracecmd_xml_write_element(handle, "Command", command);

	file = gtk_entry_get_text(GTK_ENTRY(cap->file_entry));
	if (file && strlen(file) && !is_just_ws(file))
		tracecmd_xml_write_element(handle, "File", file);

	tracecmd_xml_end_system(handle);

	tracecmd_xml_close(handle);
}

static void save_settings_clicked(GtkWidget *widget, gpointer data)
{
	struct trace_capture *cap = data;
	struct stat st;
	GtkWidget *dialog;
	GtkWidget *hbox;
	GtkWidget *label;
	GtkWidget *entry;
	GString *file;
	const char *name;
	gint result;
	int ret;

	dialog = gtk_dialog_new_with_buttons("Save Settings",
					     NULL,
					     GTK_DIALOG_MODAL,
					     GTK_STOCK_OK,
					     GTK_RESPONSE_ACCEPT,
					     GTK_STOCK_CANCEL,
					     GTK_RESPONSE_REJECT,
					     NULL);

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	label = gtk_label_new("Settings Name: ");
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	entry = gtk_entry_new();
	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
	gtk_widget_show(entry);

 again:
	result = gtk_dialog_run(GTK_DIALOG(dialog));
	switch (result) {
	case GTK_RESPONSE_ACCEPT:
		name = gtk_entry_get_text(GTK_ENTRY(entry));
		if (!name || is_just_ws(name)) {
			warning("Must enter a name");
			goto again;
		}
		/* Make sure home settings exists */
		if (create_home_settings() < 0)
			break;
		file = get_home_settings_new();
		g_string_append_printf(file, "/%s.kss", name);
		ret = stat(file->str, &st);
		if (ret >= 0) {
			ret = trace_dialog(GTK_WINDOW(dialog), TRACE_GUI_ASK,
					   "The setting '%s' already exists.\n"
					   "Are you sure you want to replace it",
					   name);
			if (ret == GTK_RESPONSE_NO) {
				g_string_free(file, TRUE);
				goto again;
			}
		}
		save_settings(cap, file->str);

		refresh_settings(cap);
		g_free(cap->info->cap_settings_name);
		cap->info->cap_settings_name = g_strdup(name);
		set_settings(cap);

		g_string_free(file, TRUE);
		break;

	case GTK_RESPONSE_REJECT:
		break;
	default:
		break;
	};

	gtk_widget_destroy(dialog);
}

static void export_settings_clicked(GtkWidget *widget, gpointer data)
{
	struct trace_capture *cap = data;
	gchar *filename;

	filename = trace_get_file_dialog_filter("Save Settings", "Save",
					 TRACE_DIALOG_FILTER_SETTING, TRUE);
	if (!filename)
		return;

	save_settings(cap, filename);

	g_free(filename);
}

static GtkTreeModel *create_plugin_combo_model(gpointer data)
{
	char **plugins = data;
	GtkListStore *list;
	GtkTreeIter iter;
	int i;

	list = gtk_list_store_new(1, G_TYPE_STRING);

	gtk_list_store_append(list, &iter);
	gtk_list_store_set(list, &iter,
			   0, PLUGIN_NONE,
			   -1);

	for (i = 0; plugins && plugins[i]; i++) {
		gtk_list_store_append(list, &iter);
		gtk_list_store_set(list, &iter,
				   0, plugins[i],
				   -1);
	}

	return GTK_TREE_MODEL(list);
}

static void insert_text(GtkEditable *buffer,
			gchar *new_text,
			gint new_text_length,
			gint *position,
			gpointer data)
{
	int i;
	guint sigid;

	/* Only allow 0-9 to be written to the entry */
	for (i = 0; i < new_text_length; i++) {
		if (new_text[i] < '0' || new_text[i] > '9') {
			sigid = g_signal_lookup("insert-text",
						G_OBJECT_TYPE(buffer));
			g_signal_stop_emission(buffer, sigid, 0);
			return;
		}
	}
}

/*
 * Trace Capture Dialog Window
 *
 *    +--------------------------------------------------------------------+
 *    |  Dialog Window                                                     |
 *    |  +-------------------------------+-------------------------------+ |
 *    |  | Paned Window                  | +---------------------------+ | |
 *    |  | +---------------------------+ | | Scroll window             | | |
 *    |  | | Hbox                      | | | +-----------------------+ | | |
 *    |  | |  Label   Plugin Combo     | | | | Event Tree            | | | |
 *    |  | +---------------------------+ | | |                       | | | |
 *    |  |                               | | |                       | | | |
 *    |  |                               | | +-----------------------+ | | |
 *    |  |                               | +---------------------------+ | |
 *    |  +-------------------------------+-------------------------------+ |
 *    +--------------------------------------------------------------------+
 */
static void tracing_dialog(struct shark_info *info, const char *tracing)
{
	struct pevent *pevent;
	GtkWidget *dialog;
	GtkWidget *button;
	GtkWidget *combo;
	GtkWidget *label;
	GtkWidget *entry;
	GtkWidget *frame;
	GtkWidget *vbox;
	GtkWidget *scrollwin;
	GtkWidget *table;
	GtkWidget *table2;
	GtkWidget *event_tree;
	GtkWidget *viewport;
	GtkWidget *textview;
	GtkWidget *hbox;
	GtkTextBuffer *buffer;
	GtkTextIter start_iter;
	GtkTextIter end_iter;
	char **plugins;
	int nr_plugins;
	struct trace_capture cap;
	const gchar *file;
	const char *command;
	const char *val;
	GString *str;
	gint result;

	memset(&cap, 0, sizeof(cap));

	cap.info = info;
	plugins = tracecmd_local_plugins(tracing);

	/* Skip latency plugins */
	nr_plugins = trim_plugins(plugins);
	if (!nr_plugins && plugins) {
		tracecmd_free_list(plugins);
		plugins = NULL;
	}

	/* Send parse warnings to status display */
	trace_dialog_register_alt_warning(vpr_stat);

	pevent = tracecmd_local_events(tracing);
	trace_dialog_register_alt_warning(NULL);

	cap.pevent = pevent;

	if (!pevent && !nr_plugins) {
		warning("No events or plugins found");
		return;
	}

	dialog = gtk_dialog_new();
	gtk_window_set_title(GTK_WINDOW(dialog), "Capture");

	button = gtk_button_new_with_label("Run");
	gtk_dialog_add_action_widget(GTK_DIALOG(dialog), button,
				       GTK_RESPONSE_ACCEPT);
	gtk_widget_show(button);

	cap.run_button = button;

	gtk_dialog_add_button(GTK_DIALOG(dialog), GTK_STOCK_CLOSE,
			      GTK_RESPONSE_REJECT);

	cap.main_dialog = dialog;

	/* --- Top Level Hpaned --- */
	table = gtk_table_new(4, 2, FALSE);

	/* It is possible that no pevents exist. */
	if (pevent) {

		scrollwin = gtk_scrolled_window_new(NULL, NULL);
		gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
					       GTK_POLICY_AUTOMATIC,
					       GTK_POLICY_AUTOMATIC);

		gtk_table_attach(GTK_TABLE(table), scrollwin, 0, 1, 1, 2,
				 GTK_FILL, GTK_FILL|GTK_EXPAND, 0, 0);
		gtk_widget_show(scrollwin);

		event_tree = trace_create_event_list_view(pevent, NULL,
							  cap.info->cap_all_events,
							  cap.info->cap_systems,
							  cap.info->cap_events);

		gtk_container_add(GTK_CONTAINER(scrollwin), event_tree);
		gtk_widget_show(event_tree);

		cap.event_view = event_tree;

	} else {
		/* No events */
		label = gtk_label_new("No events enabled on system");
		gtk_table_attach(GTK_TABLE(table), label, 0, 1, 1, 2,
				 GTK_FILL, GTK_EXPAND|GTK_FILL,
				 0, 10);
		gtk_widget_show(label);
		cap.event_view = NULL;
	}

	gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), table, TRUE, TRUE, 0);
	gtk_widget_show(table);

	/*------------------ Frame Settings --------------------------- */

	frame = gtk_frame_new("Settings");
	gtk_table_attach(GTK_TABLE(table), frame, 0, 1, 0, 1,
			 GTK_FILL, 0, 0, 10);
	gtk_widget_show(frame);

	table2 = gtk_table_new(2, 3, FALSE);
	gtk_container_add(GTK_CONTAINER(frame), table2);
	gtk_widget_show(table2);

	gtk_table_set_col_spacings(GTK_TABLE(table2), 5);

	button = gtk_button_new_with_label("Save Settings");
	gtk_table_attach_defaults(GTK_TABLE(table2), button, 0, 1, 0, 1);
	gtk_widget_show(button);

	g_signal_connect (button, "clicked",
			  G_CALLBACK (save_settings_clicked),
			  (gpointer)&cap);

	button = gtk_button_new_with_label("Import Settings");
	gtk_table_attach_defaults(GTK_TABLE(table2), button, 1, 2, 0, 1);
	gtk_widget_show(button);

	g_signal_connect (button, "clicked",
			  G_CALLBACK (import_settings_clicked),
			  (gpointer)&cap);


	button = gtk_button_new_with_label("Export Settings");
	gtk_table_attach_defaults(GTK_TABLE(table2), button, 2, 3, 0, 1);
	gtk_widget_show(button);

	g_signal_connect (button, "clicked",
			  G_CALLBACK (export_settings_clicked),
			  (gpointer)&cap);

	if (cap.info->cap_settings_name)
		set_settings(&cap);

	label = gtk_label_new("Available Settings: ");
	gtk_table_attach_defaults(GTK_TABLE(table2), label, 0, 1, 1, 2);
	gtk_widget_show(label);

	combo = trace_create_combo_box(NULL, NULL,
				       create_settings_model, NULL);
	gtk_table_attach_defaults(GTK_TABLE(table2), combo, 1, 3, 1, 2);

	cap.settings_combo = combo;

	g_signal_connect (combo, "changed",
			  G_CALLBACK (settings_changed),
			  (gpointer)&cap);



	/*------------------ Frame Settings --------------------------- */

	frame = gtk_frame_new("Execute");
	gtk_table_attach(GTK_TABLE(table), frame, 0, 1, 3, 4,
			 GTK_FILL, 0, 0, 10);
	gtk_widget_show(frame);

	table2 = gtk_table_new(3, 3, FALSE);
	gtk_container_add(GTK_CONTAINER(frame), table2);
	gtk_widget_show(table2);

	label = gtk_label_new("Plugin: ");
	gtk_table_attach_defaults(GTK_TABLE(table2), label, 0, 1, 0, 1);
	gtk_widget_show(label);

	combo = trace_create_combo_box(NULL, NULL, create_plugin_combo_model, plugins);
	cap.plugin_combo = combo;

	gtk_table_attach_defaults(GTK_TABLE(table2), combo, 1, 3, 0, 1);

	if (cap.info->cap_plugin)
		set_plugin(&cap);


	label = gtk_label_new("Command:");
	gtk_table_attach_defaults(GTK_TABLE(table2), label, 0, 1, 1, 2);
	gtk_widget_show(label);

	entry = gtk_entry_new();
	gtk_table_attach_defaults(GTK_TABLE(table2), entry, 1, 3, 1, 2);
	gtk_widget_show(entry);

	cap.command_entry = entry;

	if (cap.info->cap_command)
		gtk_entry_set_text(GTK_ENTRY(entry), cap.info->cap_command);

	label = gtk_label_new("Output file: ");
	gtk_table_attach_defaults(GTK_TABLE(table2), label, 0, 1, 2, 3);
	gtk_widget_show(label);

	entry = gtk_entry_new();
	gtk_table_attach_defaults(GTK_TABLE(table2), entry, 1, 2, 2, 3);
	gtk_widget_show(entry);

	if (cap.info->cap_file)
		file = cap.info->cap_file;
	else
		file = default_output_file;

	gtk_entry_set_text(GTK_ENTRY(entry), file);
	cap.file_entry = entry;

	button = gtk_button_new_with_label("Browse");
	gtk_table_attach_defaults(GTK_TABLE(table2), button, 2, 3, 2, 3);
	gtk_widget_show(button);

	g_signal_connect (button, "clicked",
			  G_CALLBACK (file_clicked),
			  (gpointer)&cap);


	/*------------------ Command Output ------------------ */

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_table_attach_defaults(GTK_TABLE(table), vbox, 1, 2, 0, 4);
	gtk_widget_show(vbox);
	gtk_widget_set_size_request(GTK_WIDGET(vbox), 500, 0);


	label = gtk_label_new("Output Display:");
	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	scrollwin = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrollwin),
				       GTK_POLICY_AUTOMATIC,
				       GTK_POLICY_AUTOMATIC);
	gtk_box_pack_start(GTK_BOX(vbox), scrollwin, TRUE, TRUE, 0);
	gtk_widget_show(scrollwin);

	viewport = gtk_viewport_new(NULL, NULL);
	gtk_widget_show(viewport);

	gtk_container_add(GTK_CONTAINER(scrollwin), viewport);

	textview = gtk_text_view_new();
	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(textview));

	gtk_container_add(GTK_CONTAINER(viewport), textview);
	gtk_widget_show(textview);

	cap.output_text = textview;
	cap.output_buffer = buffer;

	/* set the buffer from its previous setting */
	if (info->cap_buffer_output)
		gtk_text_buffer_set_text(buffer, info->cap_buffer_output,
					 strlen(info->cap_buffer_output));

	hbox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
	gtk_widget_show(hbox);

	label = gtk_label_new("Max # of characters in output display: ");
	gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);
	gtk_widget_show(label);

	entry = gtk_entry_new();
	gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, FALSE, 0);
	gtk_widget_show(entry);

	cap.max_num_entry = entry;

	if (!info->cap_max_buf_size)
		info->cap_max_buf_size = DEFAULT_MAX_BUF_SIZE;

	str = g_string_new("");
	g_string_append_printf(str, "%d", info->cap_max_buf_size);
	gtk_entry_set_text(GTK_ENTRY(entry), str->str);
	g_string_free(str, TRUE);

	g_signal_connect (entry, "insert-text",
			  G_CALLBACK (insert_text),
			  (gpointer)&cap);


	gtk_widget_set_size_request(GTK_WIDGET(dialog),
				    DIALOG_WIDTH, DIALOG_HEIGHT);

	gtk_widget_show(dialog);

 cont:
	result = gtk_dialog_run(GTK_DIALOG(dialog));

	if (result == GTK_RESPONSE_ACCEPT) {
		execute_button_clicked(&cap);
		goto cont;
	}

	/* Make sure no capture is running */
	end_capture(&cap);

	/* Get the max buffer size */
	val = gtk_entry_get_text(GTK_ENTRY(entry));
	info->cap_max_buf_size = atoi(val);

	gtk_text_buffer_get_start_iter(cap.output_buffer, &start_iter);
	gtk_text_buffer_get_end_iter(cap.output_buffer, &end_iter);

	g_free(info->cap_buffer_output);
	info->cap_buffer_output = gtk_text_buffer_get_text(cap.output_buffer,
							   &start_iter,
							   &end_iter,
							   FALSE);

	/* save the plugin and file to reuse if we come back */
	update_plugin(&cap);

	free(info->cap_file);
	cap.info->cap_file = strdup(gtk_entry_get_text(GTK_ENTRY(cap.file_entry)));

	free(info->cap_command);
	command = gtk_entry_get_text(GTK_ENTRY(cap.command_entry));
	if (command && strlen(command) && !is_just_ws(command))
		cap.info->cap_command = strdup(command);
	else
		cap.info->cap_command = NULL;

	update_events(&cap);

	gtk_widget_destroy(dialog);

	if (pevent)
		pevent_free(pevent);

	if (plugins)
		tracecmd_free_list(plugins);
}

void tracecmd_capture_clicked(gpointer data)
{
	struct shark_info *info = data;
	const char *tracing;

	tracing = tracecmd_get_tracing_dir();

	if (!tracing) {
		warning("Can not find or mount tracing directory!\n"
			"Either tracing is not configured for this kernel\n"
			"or you do not have the proper permissions to mount the directory");
		return;
	}

	tracing_dialog(info, tracing);
}
