/*
 *    HardInfo - Displays System Information
 *    Copyright (C) 2003-2007 L. A. F. Pereira <l@tia.mat.br>
 *
 *    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.
 *
 *    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
 */

/*
 * Functions h_strdup_cprintf and h_strconcat are based on GLib version 2.4.6
 * Copyright (C) 1995-1997  Peter Mattis, Spencer Kimball and Josh MacDonald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 */

#include <config.h>

#include <report.h>
#include <string.h>
#include <shell.h>
#include <iconcache.h>
#include <hardinfo.h>
#include <gtk/gtk.h>

#include <binreloc.h>

#include <sys/stat.h>
#include <sys/types.h>

#define KiB 1024
#define MiB 1048576
#define GiB 1073741824
#define TiB 1099511627776
#define PiB 1125899906842624

static GSList *modules_list = NULL;

void sync_manager_clear_entries(void);

gchar *find_program(gchar *program_name)
{
    int i;
    char *temp;
    static GHashTable *cache = NULL;
    const char *path[] = { "/usr/local/bin", "/usr/local/sbin",
		                   "/usr/bin", "/usr/sbin",
		                   "/bin", "/sbin",
		                   NULL };

    /* we don't need to call stat() every time: cache the results */
    if (!cache) {
    	cache = g_hash_table_new(g_str_hash, g_str_equal);
    } else if ((temp = g_hash_table_lookup(cache, program_name))) {
    	return g_strdup(temp);
    }

    for (i = 0; path[i]; i++) {
    	temp = g_build_filename(path[i], program_name, NULL);

    	if (g_file_test(temp, G_FILE_TEST_IS_EXECUTABLE)) {
    		g_hash_table_insert(cache, program_name, g_strdup(temp));
		return temp;
    	}

    	g_free(temp);
    }

    /* our search has failed; use GLib's search (which uses $PATH env var) */
    if ((temp = g_find_program_in_path(program_name))) {
    	g_hash_table_insert(cache, program_name, g_strdup(temp));
    	return temp;
    }

    return NULL;
}

gchar *seconds_to_string(unsigned int seconds)
{
    unsigned int hours, minutes, days;
    const gchar *days_fmt, *hours_fmt, *minutes_fmt, *seconds_fmt;
    gchar *full_fmt, *ret = g_strdup("");

    minutes = seconds / 60;
    seconds %= 60;
    hours = minutes / 60;
    minutes %= 60;
    days = hours / 24;
    hours %= 24;

    days_fmt = ngettext("%d day", "%d days", days);
    hours_fmt = ngettext("%d hour", "%d hours", hours);
    minutes_fmt = ngettext("%d minute", "%d minutes", minutes);
    seconds_fmt = ngettext("%d second", "%d seconds", seconds);

    if (days) {
        full_fmt = g_strdup_printf("%s %s %s %s", days_fmt, hours_fmt, minutes_fmt, seconds_fmt);
        ret = g_strdup_printf(full_fmt, days, hours, minutes, seconds);
    } else if (hours) {
        full_fmt = g_strdup_printf("%s %s %s", hours_fmt, minutes_fmt, seconds_fmt);
        ret = g_strdup_printf(full_fmt, hours, minutes, seconds);
    } else if (minutes) {
        full_fmt = g_strdup_printf("%s %s", minutes_fmt, seconds_fmt);
        ret = g_strdup_printf(full_fmt, minutes, seconds);
    } else {
        ret = g_strdup_printf(seconds_fmt, seconds);
    }
    g_free(full_fmt);
    return ret;
}

gchar *size_human_readable(gfloat size)
{
    if (size < KiB)
        return g_strdup_printf(_("%.1f B"), size);
    if (size < MiB)
        return g_strdup_printf(_("%.1f KiB"), size / KiB);
    if (size < GiB)
        return g_strdup_printf(_("%.1f MiB"), size / MiB);
    if (size < TiB)
        return g_strdup_printf(_("%.1f GiB"), size / GiB);
    if (size < PiB)
        return g_strdup_printf(_("%.1f TiB"), size / TiB);

    return g_strdup_printf(_("%.1f PiB"), size / PiB);
}

char *strend(gchar * str, gchar chr)
{
    if (!str)
	return NULL;

    char *p;
    if ((p = g_utf8_strchr(str, -1, chr)))
	*p = 0;

    return str;
}

void remove_quotes(gchar * str)
{
    if (!str)
	return;

    while (*str == '"')
	*(str++) = ' ';

    strend(str, '"');
}

void remove_linefeed(gchar * str)
{
    strend(str, '\n');
}

void widget_set_cursor(GtkWidget * widget, GdkCursorType cursor_type)
{
    GdkCursor *cursor;
    GdkDisplay *display;
    GdkWindow *gdk_window;

    display = gtk_widget_get_display(widget);
    cursor = gdk_cursor_new_for_display(display, cursor_type);
    gdk_window = gtk_widget_get_window(widget);
    if (cursor) {
        gdk_window_set_cursor(gdk_window, cursor);
        gdk_display_flush(display);
#if GTK_CHECK_VERSION(3, 0, 0)
        g_object_unref(cursor);
#else
        gdk_cursor_unref(cursor);
#endif
    }

    while (gtk_events_pending())
        gtk_main_iteration();
}

static gboolean __nonblock_cb(gpointer data)
{
    gtk_main_quit();
    return FALSE;
}

void nonblock_sleep(guint msec)
{
    g_timeout_add(msec, (GSourceFunc) __nonblock_cb, NULL);
    gtk_main();
}

static void __expand_cb(GtkWidget * widget, gpointer data)
{
    if (GTK_IS_EXPANDER(widget)) {
	gtk_expander_set_expanded(GTK_EXPANDER(widget), TRUE);
    } else if (GTK_IS_CONTAINER(widget)) {
	gtk_container_foreach(GTK_CONTAINER(widget),
			      (GtkCallback) __expand_cb, NULL);
    }
}

void file_chooser_open_expander(GtkWidget * chooser)
{
    gtk_container_foreach(GTK_CONTAINER(chooser),
			  (GtkCallback) __expand_cb, NULL);
}

void file_chooser_add_filters(GtkWidget * chooser, FileTypes * filters)
{
    GtkFileFilter *filter;
    gint i;

    for (i = 0; filters[i].name; i++) {
	filter = gtk_file_filter_new();
	gtk_file_filter_add_mime_type(filter, filters[i].mime_type);
	gtk_file_filter_set_name(filter, filters[i].name);
	gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(chooser), filter);
    }
}

gchar *file_chooser_get_extension(GtkWidget * chooser, FileTypes * filters)
{
    GtkFileFilter *filter;
    const gchar *filter_name;
    gint i;

    filter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(chooser));
    filter_name = gtk_file_filter_get_name(filter);
    for (i = 0; filters[i].name; i++) {
	if (g_str_equal(filter_name, filters[i].name)) {
	    return filters[i].extension;
	}
    }

    return NULL;
}

gpointer file_types_get_data_by_name(FileTypes * filters, gchar * filename)
{
    gint i;

    for (i = 0; filters[i].name; i++) {
	if (g_str_has_suffix(filename, filters[i].extension)) {
	    return filters[i].data;
	}
    }

    return NULL;
}

gchar *file_chooser_build_filename(GtkWidget * chooser, gchar * extension)
{
    gchar *filename =
	gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
    gchar *retval;

    if (g_str_has_suffix(filename, extension)) {
	return filename;
    }

    retval = g_strconcat(filename, extension, NULL);
    g_free(filename);

    return retval;
}

gboolean binreloc_init(gboolean try_hardcoded)
{
    GError *error = NULL;
    gchar *tmp;

    DEBUG("initializing binreloc (hardcoded = %d)", try_hardcoded);

    /* If the runtime data directories we previously found, don't even try
       to find them again. */
    if (params.path_data && params.path_lib) {
	DEBUG("data and lib path already found.");
	return TRUE;
    }

    if (try_hardcoded || !gbr_init(&error)) {
	/* We were asked to try hardcoded paths or BinReloc failed to initialize. */
	params.path_data = g_strdup(PREFIX);
	params.path_lib = g_strdup(LIBPREFIX);

	if (error) {
	    g_error_free(error);
	}

	DEBUG("%strying hardcoded paths.",
	      try_hardcoded ? "" : "binreloc init failed. ");
    } else {
	/* If we were able to initialize BinReloc, build the default data
	   and library paths. */
	DEBUG("done, trying to use binreloc paths.");

	tmp = gbr_find_data_dir(PREFIX);
	params.path_data = g_build_filename(tmp, "hardinfo", NULL);
	g_free(tmp);

	tmp = gbr_find_lib_dir(PREFIX);
	params.path_lib = g_build_filename(tmp, "hardinfo", NULL);
	g_free(tmp);
    }

    DEBUG("searching for runtime data on these locations:");
    DEBUG("  lib: %s", params.path_lib);
    DEBUG(" data: %s", params.path_data);

    /* Try to see if the uidefs.xml file isn't missing. This isn't the
       definitive test, but it should do okay for most situations. */
    tmp = g_build_filename(params.path_data, "benchmark.data", NULL);
    if (!g_file_test(tmp, G_FILE_TEST_EXISTS)) {
	DEBUG("runtime data not found");

	g_free(params.path_data);
	g_free(params.path_lib);
	g_free(tmp);

	params.path_data = params.path_lib = NULL;

	if (try_hardcoded) {
	    /* We tried the hardcoded paths, but still was unable to find the
	       runtime data. Give up. */
	    DEBUG("giving up");
	    return FALSE;
	} else {
	    /* Even though BinReloc worked OK, the runtime data was not found.
	       Try the hardcoded paths. */
	    DEBUG("trying to find elsewhere");
	    return binreloc_init(TRUE);
	}
    }
    g_free(tmp);

    DEBUG("runtime data found!");
    /* We found the runtime data; hope everything is fine */
    return TRUE;
}

static void
log_handler(const gchar * log_domain,
	    GLogLevelFlags log_level,
	    const gchar * message, gpointer user_data)
{
    if (!params.gui_running) {
	/* No GUI running: spit the message to the terminal */
	g_print("\n\n*** %s: %s\n\n",
		(log_level & G_LOG_FLAG_FATAL) ? _("Error") : _("Warning"),
		message);
    } else {
	/* Hooray! We have a GUI running! */
	GtkWidget *dialog;

	dialog = gtk_message_dialog_new_with_markup(NULL, GTK_DIALOG_MODAL,
						    (log_level &
						     G_LOG_FLAG_FATAL) ?
						    GTK_MESSAGE_ERROR :
						    GTK_MESSAGE_WARNING,
						    GTK_BUTTONS_CLOSE,
						    "<big><b>%s</b></big>\n\n%s",
						    (log_level &
						     G_LOG_FLAG_FATAL) ?
						    _("Fatal Error") :
						    _("Warning"), message);

	gtk_dialog_run(GTK_DIALOG(dialog));
	gtk_widget_destroy(dialog);
    }
}

void parameters_init(int *argc, char ***argv, ProgramParameters * param)
{
    static gboolean create_report = FALSE;
    static gboolean force_all_details = FALSE;
    static gboolean show_version = FALSE;
    static gboolean list_modules = FALSE;
    static gboolean autoload_deps = FALSE;
    static gboolean run_xmlrpc_server = FALSE;
    static gboolean skip_benchmarks = FALSE;
    static gboolean quiet = FALSE;
    static gchar *report_format = NULL;
    static gchar *run_benchmark = NULL;
    static gchar *result_format = NULL;
    static gchar *bench_user_note = NULL;
    static gchar **use_modules = NULL;
    static gint max_bench_results = 10;

    static GOptionEntry options[] = {
	{
	 .long_name = "quiet",
	 .short_name = 'q',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &quiet,
	 .description = N_("do not print status messages to standard output")},
	{
	 .long_name = "generate-report",
	 .short_name = 'r',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &create_report,
	 .description = N_("creates a report and prints to standard output")},
	{
	 .long_name = "report-format",
	 .short_name = 'f',
	 .arg = G_OPTION_ARG_STRING,
	 .arg_data = &report_format,
	 .description = N_("chooses a report format ([text], html)")},
	{
	 .long_name = "run-benchmark",
	 .short_name = 'b',
	 .arg = G_OPTION_ARG_STRING,
	 .arg_data = &run_benchmark,
	 .description = N_("run benchmark; requires benchmark.so to be loaded")},
	{
	 .long_name = "user-note",
	 .short_name = 'u',
	 .arg = G_OPTION_ARG_STRING,
	 .arg_data = &bench_user_note,
	 .description = N_("note attached to benchmark results")},
	{
	 .long_name = "result-format",
	 .short_name = 'g',
	 .arg = G_OPTION_ARG_STRING,
	 .arg_data = &result_format,
	 .description = N_("benchmark result format ([short], conf, shell)")},
	{
	 .long_name = "max-results",
	 .short_name = 'n',
	 .arg = G_OPTION_ARG_INT,
	 .arg_data = &max_bench_results,
	 .description = N_("maximum number of benchmark results to include (-1 for no limit, default is 10)")},
	{
	 .long_name = "list-modules",
	 .short_name = 'l',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &list_modules,
	 .description = N_("lists modules")},
	{
	 .long_name = "load-module",
	 .short_name = 'm',
	 .arg = G_OPTION_ARG_STRING_ARRAY,
	 .arg_data = &use_modules,
	 .description = N_("specify module to load")},
	{
	 .long_name = "autoload-deps",
	 .short_name = 'a',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &autoload_deps,
	 .description = N_("automatically load module dependencies")},
#ifdef HAS_LIBSOUP
	{
	 .long_name = "xmlrpc-server",
	 .short_name = 'x',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &run_xmlrpc_server,
	 .description = N_("run in XML-RPC server mode")},
#endif	/* HAS_LIBSOUP */
	{
	 .long_name = "version",
	 .short_name = 'v',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &show_version,
	 .description = N_("shows program version and quit")},
	{
	 .long_name = "skip-benchmarks",
	 .short_name = 's',
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &skip_benchmarks,
	 .description = N_("do not run benchmarks")},
	{
	 .long_name = "very-verbose",
	 .short_name = 'w', /* like -vv */
	 .arg = G_OPTION_ARG_NONE,
	 .arg_data = &force_all_details,
	 .description = N_("show all details")},
	{NULL}
    };
    GOptionContext *ctx;

    ctx = g_option_context_new(_("- System Profiler and Benchmark tool"));
    g_option_context_set_ignore_unknown_options(ctx, FALSE);
    g_option_context_set_help_enabled(ctx, TRUE);

    g_option_context_add_main_entries(ctx, options, *(argv)[0]);
    g_option_context_parse(ctx, argc, argv, NULL);

    g_option_context_free(ctx);

    if (*argc >= 2) {
	g_print(_("Unrecognized arguments.\n"
		"Try ``%s --help'' for more information.\n"), *(argv)[0]);
	exit(1);
    }

    param->create_report = create_report;
    param->report_format = REPORT_FORMAT_TEXT;
    param->show_version = show_version;
    param->list_modules = list_modules;
    param->use_modules = use_modules;
    param->run_benchmark = run_benchmark;
    param->result_format = result_format;
    param->max_bench_results = max_bench_results;
    param->autoload_deps = autoload_deps;
    param->run_xmlrpc_server = run_xmlrpc_server;
    param->skip_benchmarks = skip_benchmarks;
    param->force_all_details = force_all_details;
    param->quiet = quiet;
    param->argv0 = *(argv)[0];

    if (report_format) {
        if (g_str_equal(report_format, "html"))
            param->report_format = REPORT_FORMAT_HTML;
        if (g_str_equal(report_format, "shell"))
            param->report_format = REPORT_FORMAT_SHELL;
    }

    /* clean user note */
    if (bench_user_note) {
        char *p = NULL;
        while(p = strchr(bench_user_note, ';'))  { *p = ','; }
        param->bench_user_note =
            gg_key_file_parse_string_as_value(bench_user_note, '|');
    }

    /* html ok?
     * gui: yes
     * report html: yes
     * report text: no
     * anything else? */
    param->markup_ok = TRUE;
    if (param->create_report && param->report_format != REPORT_FORMAT_HTML)
        param->markup_ok = FALSE;

    // TODO: fmt_opts: FMT_OPT_ATERM, FMT_OPT_HTML, FMT_OPT_PANGO...
    param->fmt_opts = FMT_OPT_NONE;

    gchar *confdir = g_build_filename(g_get_user_config_dir(), "hardinfo", NULL);
    if (!g_file_test(confdir, G_FILE_TEST_EXISTS)) {
        mkdir(confdir, 0744);
    }
    g_free(confdir);
}

gboolean ui_init(int *argc, char ***argv)
{
    DEBUG("initializing gtk+ UI");

    g_set_application_name("HardInfo");
    g_log_set_handler(NULL,
		      G_LOG_LEVEL_WARNING | G_LOG_FLAG_FATAL |
		      G_LOG_LEVEL_ERROR, log_handler, NULL);

    return gtk_init_check(argc, argv);
}

/* Copyright: Jens Låås, SLU 2002 */
gchar *strreplacechr(gchar * string, gchar * replace, gchar new_char)
{
    gchar *s;
    for (s = string; *s; s++)
	if (strchr(replace, *s))
	    *s = new_char;

    return string;
}

gchar *strreplace(gchar *string, gchar *replace, gchar *replacement)
{
    gchar **tmp, *ret;

    tmp = g_strsplit(string, replace, 0);
    ret = g_strjoinv(replacement, tmp);
    g_strfreev(tmp);

    return ret;
}

static GHashTable *__module_methods = NULL;

static void module_register_methods(ShellModule * module)
{
    const ShellModuleMethod *(*get_methods)(void);
    gchar *method_name;

    if (__module_methods == NULL) {
	__module_methods = g_hash_table_new(g_str_hash, g_str_equal);
    }

    if (g_module_symbol
	(module->dll, "hi_exported_methods", (gpointer)&get_methods)) {
	const ShellModuleMethod *methods;

	for (methods = get_methods(); methods->name; methods++) {
	    ShellModuleMethod method = *methods;
	    gchar *name = g_path_get_basename(g_module_name(module->dll));
	    gchar *simple_name = strreplace(name, "lib", "");

	    strend(simple_name, '.');

	    method_name = g_strdup_printf("%s::%s", simple_name, method.name);
	    g_hash_table_insert(__module_methods, method_name,
				method.function);
	    g_free(name);
	    g_free(simple_name);
	}
    }

}

gchar *module_call_method(gchar * method)
{
    gchar *(*function) (void);

    if (__module_methods == NULL) {
	return NULL;
    }

    function = g_hash_table_lookup(__module_methods, method);
    return function ? g_strdup(function()) : NULL;
}

/* FIXME: varargs? */
gchar *module_call_method_param(gchar * method, gchar * parameter)
{
    gchar *(*function) (gchar *param);

    if (__module_methods == NULL) {
	return NULL;
    }

    function = g_hash_table_lookup(__module_methods, method);
    return function ? g_strdup(function(parameter)) : NULL;
}

static gboolean remove_module_methods(gpointer key, gpointer value, gpointer data)
{
    return g_str_has_prefix(key, data);
}

static void module_unload(ShellModule * module)
{
    GSList *entry;

    if (module->dll) {
        gchar *name;

        if (module->deinit) {
        	DEBUG("cleaning up module \"%s\"", module->name);
		module->deinit();
	} else {
		DEBUG("module \"%s\" does not need cleanup", module->name);
	}

        name = g_path_get_basename(g_module_name(module->dll));
        g_hash_table_foreach_remove(__module_methods, remove_module_methods, name);

    	g_module_close(module->dll);
    	g_free(name);
    }

    g_free(module->name);
    g_object_unref(module->icon);

    for (entry = module->entries; entry; entry = entry->next) {
	ShellModuleEntry *e = (ShellModuleEntry *)entry->data;

	g_source_remove_by_user_data(e);
    	g_free(e);
    }

    g_slist_free(module->entries);
    g_free(module);
}


void module_unload_all(void)
{
    Shell *shell;
    GSList *module, *merge_id;

    shell = shell_get_main_shell();

    sync_manager_clear_entries();
    shell_clear_timeouts(shell);
    shell_clear_tree_models(shell);
    shell_clear_field_updates();
    shell_set_title(shell, NULL);

    for (module = shell->tree->modules; module; module = module->next) {
    	module_unload((ShellModule *)module->data);
    }

    for (merge_id = shell->merge_ids; merge_id; merge_id = merge_id->next) {
    	gtk_ui_manager_remove_ui(shell->ui_manager,
			         GPOINTER_TO_INT(merge_id->data));
    }
    g_slist_free(shell->tree->modules);
    g_slist_free(shell->merge_ids);

    shell->merge_ids = NULL;
    shell->tree->modules = NULL;
    shell->selected = NULL;
}

static ShellModule *module_load(gchar * filename)
{
    ShellModule *module;
    gchar *tmp;

    module = g_new0(ShellModule, 1);

    if (params.gui_running) {
	gchar *tmpicon, *dot, *simple_name;

	tmpicon = g_strdup(filename);
	dot = g_strrstr(tmpicon, "." G_MODULE_SUFFIX);

	*dot = '\0';

	simple_name = strreplace(tmpicon, "lib", "");

	tmp = g_strdup_printf("%s.png", simple_name);
	module->icon = icon_cache_get_pixbuf(tmp);

	g_free(tmp);
	g_free(tmpicon);
	g_free(simple_name);
    }

    tmp = g_build_filename(params.path_lib, "modules", filename, NULL);
    module->dll = g_module_open(tmp, G_MODULE_BIND_LAZY);
    DEBUG("gmodule resource for ``%s'' is %p (%s)", tmp, module->dll, g_module_error());
    g_free(tmp);

    if (module->dll) {
	void (*init) (void);
	ModuleEntry *(*get_module_entries) (void);
	gint(*weight_func) (void);
	gchar *(*name_func) (void);
	ModuleEntry *entries;
	gint i = 0;

	if (!g_module_symbol(module->dll, "hi_module_get_entries", (gpointer) & get_module_entries) ||
	    !g_module_symbol(module->dll, "hi_module_get_name",	(gpointer) & name_func)) {
	    DEBUG("cannot find needed symbols; is ``%s'' a real module?", filename);
	    goto failed;
	}

	if (g_module_symbol(module->dll, "hi_module_init", (gpointer) & init)) {
	    DEBUG("initializing module ``%s''", filename);
	    init();
	}

	g_module_symbol(module->dll, "hi_module_get_weight",
			(gpointer) & weight_func);

	module->weight = weight_func ? weight_func() : 0;
	module->name = name_func();

        g_module_symbol(module->dll, "hi_module_get_about",
   	 	        (gpointer) & (module->aboutfunc));
        g_module_symbol(module->dll, "hi_module_deinit",
   	 	        (gpointer) & (module->deinit));
        g_module_symbol(module->dll, "hi_module_get_summary",
   	 	        (gpointer) & (module->summaryfunc));

	entries = get_module_entries();
	while (entries[i].name) {
        if (*entries[i].name == '#') { i++; continue; } /* skip */

	    ShellModuleEntry *entry = g_new0(ShellModuleEntry, 1);

	    if (params.gui_running) {
		entry->icon = icon_cache_get_pixbuf(entries[i].icon);
	    }
	    entry->icon_file = entries[i].icon;

	    g_module_symbol(module->dll, "hi_more_info",
			    (gpointer) & (entry->morefunc));
	    g_module_symbol(module->dll, "hi_get_field",
			    (gpointer) & (entry->fieldfunc));
	    g_module_symbol(module->dll, "hi_note_func",
			    (gpointer) & (entry->notefunc));

	    entry->name = _(entries[i].name); //gettext unname N_() in computer.c line 67 etc...
	    entry->scan_func = entries[i].scan_callback;
	    entry->func = entries[i].callback;
	    entry->number = i;
	    entry->flags = entries[i].flags;

	    module->entries = g_slist_append(module->entries, entry);

	    i++;
	}

	DEBUG("registering methods for module ``%s''", filename);
	module_register_methods(module);
    } else {
    	DEBUG("cannot g_module_open(``%s''). permission problem?", filename);
      failed:
	DEBUG("loading module %s failed: %s", filename, g_module_error());

	g_free(module->name);
	g_free(module);
	module = NULL;
    }

    return module;
}

static gboolean module_in_module_list(gchar * module, gchar ** module_list)
{
    int i = 0;

    if (!module_list)
	return TRUE;

    for (; module_list[i]; i++) {
	if (g_str_equal(module_list[i], module))
	    return TRUE;
    }

    return FALSE;
}

static gint module_cmp(gconstpointer m1, gconstpointer m2)
{
    ShellModule *a = (ShellModule *) m1;
    ShellModule *b = (ShellModule *) m2;

    return a->weight - b->weight;
}

#if 0
static void module_entry_free(gpointer data, gpointer user_data)
{
    ShellModuleEntry *entry = (ShellModuleEntry *) data;

    if (entry) {
	g_free(entry->name);
	g_object_unref(entry->icon);

	g_free(entry);
    }
}
#endif

const ModuleAbout *module_get_about(ShellModule * module)
{
    if (module->aboutfunc) {
    	return module->aboutfunc();
    }

    return NULL;
}

static GSList *modules_check_deps(GSList * modules)
{
    GSList *mm;
    ShellModule *module;

    for (mm = modules; mm; mm = mm->next) {
	gchar **(*get_deps) (void);
	gchar **deps;
	gint i;

	module = (ShellModule *) mm->data;

	if (g_module_symbol(module->dll, "hi_module_get_dependencies",
			    (gpointer) & get_deps)) {
	    for (i = 0, deps = get_deps(); deps[i]; i++) {
		GSList *l;
		ShellModule *m;
		gboolean found = FALSE;

		for (l = modules; l && !found; l = l->next) {
		    m = (ShellModule *) l->data;
		    gchar *name = g_path_get_basename(g_module_name(m->dll));

		    found = g_str_equal(name, deps[i]);
		    g_free(name);
		}

		if (!found) {
		    if (params.autoload_deps) {
			ShellModule *mod = module_load(deps[i]);

			if (mod)
			    modules = g_slist_append(modules, mod);
			modules = modules_check_deps(modules);	/* re-check dependencies */

			break;
		    }

		    if (params.gui_running) {
			GtkWidget *dialog;

			dialog = gtk_message_dialog_new(NULL,
							GTK_DIALOG_DESTROY_WITH_PARENT,
							GTK_MESSAGE_QUESTION,
							GTK_BUTTONS_NONE,
							_("Module \"%s\" depends on module \"%s\", load it?"),
							module->name,
							deps[i]);
			gtk_dialog_add_buttons(GTK_DIALOG(dialog),
					       "_No",
					       GTK_RESPONSE_REJECT,
					       "_Open",
					       GTK_RESPONSE_ACCEPT, NULL);

			if (gtk_dialog_run(GTK_DIALOG(dialog)) ==
			    GTK_RESPONSE_ACCEPT) {
			    ShellModule *mod = module_load(deps[i]);

			    if (mod)
				modules = g_slist_prepend(modules, mod);
			    modules = modules_check_deps(modules);	/* re-check dependencies */
			} else {
			    g_error("HardInfo cannot run without loading the additional module.");
			    exit(1);
			}

			gtk_widget_destroy(dialog);
		    } else {
			g_error(_("Module \"%s\" depends on module \"%s\"."),
				module->name, deps[i]);
		    }
		}
	    }
	}
    }

    return modules;
}


GSList *modules_get_list()
{
    return modules_list;
}

static GSList *modules_load(gchar ** module_list)
{
    GDir *dir;
    GSList *modules = NULL;
    ShellModule *module;
    gchar *filename;

    filename = g_build_filename(params.path_lib, "modules", NULL);
    dir = g_dir_open(filename, 0, NULL);
    g_free(filename);

    if (dir) {
	while ((filename = (gchar *) g_dir_read_name(dir))) {
	    if (g_strrstr(filename, "." G_MODULE_SUFFIX) &&
		module_in_module_list(filename, module_list) &&
		((module = module_load(filename)))) {
		modules = g_slist_prepend(modules, module);
	    }
	}

	g_dir_close(dir);
    }

    modules = modules_check_deps(modules);

    if (g_slist_length(modules) == 0) {
	if (params.use_modules == NULL) {
	    g_error
		(_("No module could be loaded. Check permissions on \"%s\" and try again."),
		 params.path_lib);
	} else {
	    g_error
		(_("No module could be loaded. Please use hardinfo -l to list all avai"
		 "lable modules and try again with a valid module list."));

	}
    }

    modules_list = g_slist_sort(modules, module_cmp);
    return modules_list;
}

GSList *modules_load_selected(void)
{
    return modules_load(params.use_modules);
}

GSList *modules_load_all(void)
{
    return modules_load(NULL);
}

gint tree_view_get_visible_height(GtkTreeView * tv)
{
    GtkTreePath *path;
    GdkRectangle rect;
    GtkTreeIter iter;
    GtkTreeModel *model = gtk_tree_view_get_model(tv);
    gint nrows = 1;

    path = gtk_tree_path_new_first();
    gtk_tree_view_get_cell_area(GTK_TREE_VIEW(tv), path, NULL, &rect);

    /* FIXME: isn't there any easier way to tell the number of rows? */
    gtk_tree_model_get_iter_first(model, &iter);
    do {
	nrows++;
    } while (gtk_tree_model_iter_next(model, &iter));

    gtk_tree_path_free(path);

    return nrows * rect.height;
}

void module_entry_scan_all_except(ModuleEntry * entries, gint except_entry)
{
    ModuleEntry entry;
    gint i = 0;
    void (*scan_callback) (gboolean reload);
    gchar *text;

    shell_view_set_enabled(FALSE);

    for (entry = entries[0]; entry.name; entry = entries[++i]) {
	if (i == except_entry)
	    continue;

	text = g_strdup_printf(_("Scanning: %s..."), _(entry.name));
	shell_status_update(text);
	g_free(text);

	if ((scan_callback = entry.scan_callback)) {
	    scan_callback(FALSE);
	}
    }

    shell_view_set_enabled(TRUE);
    shell_status_update(_("Done."));
}

void module_entry_scan_all(ModuleEntry * entries)
{
    module_entry_scan_all_except(entries, -1);
}

void module_entry_reload(ShellModuleEntry * module_entry)
{

    if (module_entry->scan_func) {
	module_entry->scan_func(TRUE);
    }
}

void module_entry_scan(ShellModuleEntry * module_entry)
{
    if (module_entry->scan_func) {
	module_entry->scan_func(FALSE);
    }
}

gchar *module_entry_get_field(ShellModuleEntry * module_entry, gchar * field)
{
   if (module_entry->fieldfunc) {
   	return module_entry->fieldfunc(field);
   }

   return NULL;
}

gchar *module_entry_function(ShellModuleEntry * module_entry)
{
    if (module_entry->func) {
	return module_entry->func();
    }

    return NULL;
}

gchar *module_entry_get_moreinfo(ShellModuleEntry * module_entry, gchar * field)
{
    if (module_entry->morefunc) {
	return module_entry->morefunc(field);
    }

    return NULL;
}

const gchar *module_entry_get_note(ShellModuleEntry * module_entry)
{
    if (module_entry->notefunc) {
	return module_entry->notefunc(module_entry->number);
    }

    return NULL;
}

gchar *h_strdup_cprintf(const gchar * format, gchar * source, ...)
{
    gchar *buffer, *retn;
    va_list args;

    va_start(args, source);
    buffer = g_strdup_vprintf(format, args);
    va_end(args);

    if (source) {
	retn = g_strconcat(source, buffer, NULL);
	g_free(buffer);
        g_free(source);
    } else {
	retn = buffer;
    }

    return retn;
}

gchar *h_strconcat(gchar * string1, ...)
{
    gsize l;
    va_list args;
    gchar *s;
    gchar *concat;
    gchar *ptr;

    if (!string1)
	return NULL;

    l = 1 + strlen(string1);
    va_start(args, string1);
    s = va_arg(args, gchar *);
    while (s) {
	l += strlen(s);
	s = va_arg(args, gchar *);
    }
    va_end(args);

    concat = g_new(gchar, l);
    ptr = concat;

    ptr = g_stpcpy(ptr, string1);
    va_start(args, string1);
    s = va_arg(args, gchar *);
    while (s) {
	ptr = g_stpcpy(ptr, s);
	s = va_arg(args, gchar *);
    }
    va_end(args);

    g_free(string1);

    return concat;
}

static gboolean h_hash_table_remove_all_true(gpointer key, gpointer data, gpointer user_data)
{
    return TRUE;
}

void
h_hash_table_remove_all(GHashTable *hash_table)
{
    g_hash_table_foreach_remove(hash_table,
				h_hash_table_remove_all_true,
				NULL);
}

gfloat
h_sysfs_read_float(const gchar *endpoint, const gchar *entry)
{
	gchar *tmp, *buffer;
	gfloat return_value = 0.0f;

	tmp = g_build_filename(endpoint, entry, NULL);
	if (g_file_get_contents(tmp, &buffer, NULL, NULL))
		return_value = atof(buffer);

	g_free(tmp);
	g_free(buffer);

	return return_value;
}

gint
h_sysfs_read_int(const gchar *endpoint, const gchar *entry)
{
	gchar *tmp, *buffer;
	gint return_value = 0;

	tmp = g_build_filename(endpoint, entry, NULL);
	if (g_file_get_contents(tmp, &buffer, NULL, NULL))
		return_value = atoi(buffer);

	g_free(tmp);
	g_free(buffer);

	return return_value;
}

gint
h_sysfs_read_hex(const gchar *endpoint, const gchar *entry)
{
	gchar *tmp, *buffer;
	gint return_value = 0;

	tmp = g_build_filename(endpoint, entry, NULL);
	if (g_file_get_contents(tmp, &buffer, NULL, NULL))
		return_value = (gint) strtoll(buffer, NULL, 16);

	g_free(tmp);
	g_free(buffer);

	return return_value;
}

gchar *
h_sysfs_read_string(const gchar *endpoint, const gchar *entry)
{
	gchar *tmp, *return_value;

	tmp = g_build_filename(endpoint, entry, NULL);
	if (!g_file_get_contents(tmp, &return_value, NULL, NULL)) {
		g_free(return_value);

		return_value = NULL;
	} else {
		return_value = g_strstrip(return_value);
	}

	g_free(tmp);

	return return_value;
}

static GHashTable *_moreinfo = NULL;

void
moreinfo_init(void)
{
	if (G_UNLIKELY(_moreinfo)) {
		DEBUG("moreinfo already initialized");
		return;
	}
	DEBUG("initializing moreinfo");
	_moreinfo = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
}

void
moreinfo_shutdown(void)
{
	if (G_UNLIKELY(!_moreinfo)) {
		DEBUG("moreinfo not initialized");
		return;
	}
	DEBUG("shutting down moreinfo");
	g_hash_table_destroy(_moreinfo);
	_moreinfo = NULL;
}

void
moreinfo_add_with_prefix(gchar *prefix, gchar *key, gchar *value)
{
	if (G_UNLIKELY(!_moreinfo)) {
		DEBUG("moreinfo not initialized");
		return;
	}

	if (prefix) {
		gchar *hashkey = g_strconcat(prefix, ":", key, NULL);
		g_hash_table_insert(_moreinfo, hashkey, value);
		return;
	}

	g_hash_table_insert(_moreinfo, g_strdup(key), value);
}

void
moreinfo_add(gchar *key, gchar *value)
{
	moreinfo_add_with_prefix(NULL, key, value);
}

static gboolean
_moreinfo_del_cb(gpointer key, gpointer value, gpointer data)
{
	return g_str_has_prefix(key, data);
}

void
moreinfo_del_with_prefix(gchar *prefix)
{
	if (G_UNLIKELY(!_moreinfo)) {
		DEBUG("moreinfo not initialized");
		return;
	}

	g_hash_table_foreach_remove(_moreinfo, _moreinfo_del_cb, prefix);
}

void
moreinfo_clear(void)
{
	if (G_UNLIKELY(!_moreinfo)) {
		DEBUG("moreinfo not initialized");
		return;
	}
	h_hash_table_remove_all(_moreinfo);
}

gchar *
moreinfo_lookup_with_prefix(gchar *prefix, gchar *key)
{
	if (G_UNLIKELY(!_moreinfo)) {
		DEBUG("moreinfo not initialized");
		return 0;
	}

	if (prefix) {
		gchar *lookup_key = g_strconcat(prefix, ":", key, NULL);
		gchar *result = g_hash_table_lookup(_moreinfo, lookup_key);
		g_free(lookup_key);
		return result;
	}

	return g_hash_table_lookup(_moreinfo, key);
}

gchar *
moreinfo_lookup(gchar *key)
{
	return moreinfo_lookup_with_prefix(NULL, key);
}

#if !GLIB_CHECK_VERSION(2,44,0)
gboolean g_strv_contains(const gchar * const * strv, const gchar *str) {
    /* g_strv_contains() requires glib>2.44
     * fallback for older versions */
    gint i = 0;
    while(strv[i] != NULL) {
        if (g_strcmp0(strv[i], str) == 0)
            return 1;
        i++;
    }
    return 0;
}
#endif

gchar *hardinfo_clean_grpname(const gchar *v, int replacing) {
    gchar *clean, *p;

    p = clean = g_strdup(v);
    while (*p != 0) {
        switch(*p) {
            case '[':
                *p = '('; break;
            case ']':
                *p = ')'; break;
            default:
                break;
        }
        p++;
    }
    if (replacing)
        g_free((gpointer)v);
    return clean;
}

/* Hardinfo labels that have # are truncated and/or hidden.
 * Labels can't have $ because that is the delimiter in
 * moreinfo. */
gchar *hardinfo_clean_label(const gchar *v, int replacing) {
    gchar *clean, *p;

    p = clean = g_strdup(v);
    while (*p != 0) {
        switch(*p) {
            case '#': case '$':
                *p = '_';
                break;
            default:
                break;
        }
        p++;
    }
    if (replacing)
        g_free((gpointer)v);
    return clean;
}

/* hardinfo uses the values as {ht,x}ml, apparently */
gchar *hardinfo_clean_value(const gchar *v, int replacing) {
    gchar *clean, *tmp;
    gchar **vl;
    if (v == NULL) return NULL;

    vl = g_strsplit(v, "&", -1);
    if (g_strv_length(vl) > 1)
        clean = g_strjoinv("&amp;", vl);
    else
        clean = g_strdup(v);
    g_strfreev(vl);

    vl = g_strsplit(clean, "<", -1);
    if (g_strv_length(vl) > 1) {
        tmp = g_strjoinv("&lt;", vl);
        g_free(clean);
        clean = tmp;
    }
    g_strfreev(vl);

    vl = g_strsplit(clean, ">", -1);
    if (g_strv_length(vl) > 1) {
        tmp = g_strjoinv("&gt;", vl);
        g_free(clean);
        clean = tmp;
    }
    g_strfreev(vl);

    if (replacing)
        g_free((gpointer)v);
    return clean;
}

gboolean hardinfo_spawn_command_line_sync(const gchar *command_line,
                                          gchar **standard_output,
                                          gchar **standard_error,
                                          gint *exit_status,
                                          GError **error)
{
    shell_status_pulse();
    return g_spawn_command_line_sync(command_line, standard_output,
                                     standard_error, exit_status, error);
}
