/*
 * Distribuition detection routines
 * Copyright (c) 2003 Leandro Pereira <leandro@linuxmag.com.br>
 *
 * May be distributed under the terms of GNU General Public License version 2.
 */

#include "hardinfo.h"
#include "computer.h"

#include <gtk/gtk.h>
#include <sys/utsname.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

static struct {
    gchar *file, *codename;
} distro_db[] = {
    {    DB_PREFIX "debian_version",		"deb"	},
    {    DB_PREFIX "slackware-version",		"slk"	},
    {    DB_PREFIX "mandrake-release",		"mdk"	}, 
    {    DB_PREFIX "gentoo-release",		"gnt"	}, 
    {    DB_PREFIX "conectiva-release",		"cnc"	}, 
    {    DB_PREFIX "versão-conectiva",		"cnc"	}, 
    {    DB_PREFIX "turbolinux-release",	"tl"	}, 
    {    DB_PREFIX "yellowdog-release", 	"yd"	}, 
    {    DB_PREFIX "SuSE-release",		"suse"	},
	/*
	 * RedHat must be the *last* one to be checked, since
	 * some distros (like Mandrake) includes a redhat-relase
	 * file too.
	 */
    {    DB_PREFIX "redhat-release",		"rh"	},
    {    NULL, NULL				        }
};

#define get_int_val(var)  { \
		walk_until_inclusive(':'); buf++; \
		var = atoi(buf); \
		continue; \
	}

#define get_str_val(var)  { \
		walk_until_inclusive(':'); buf++; \
		var = g_strdup(buf); \
		continue; \
	}

#ifdef ARCH_i386
static struct {
    char *small, *large;
} small2large[] = {
	{    "fpu",	"Floating Point Unit"				},
	{    "vme",	"Virtual Mode Extension"			},
	{    "de",	"Debugging Extensions"				},
	{    "pse",	"Page Size Extensions"				},
	{    "tsc",	"Time Stamp Counter"				},
	{    "msr",	"Model Specific Registers"			},
	{    "pae",	"Physical Address Extensions"			},
	{    "mce",	"Machine Check Architeture"			},
	{    "cx8",	"CMPXCHG8 instruction"				},
	{    "apic",	"Advanced Programmable Interrupt Controller"	},
	{    "sep",	"Fast System Call"				},
	{    "mtrr",	"Memory Type Range Registers"			},
	{    "pge",	"Page Global Enable"				},
	{    "cmov",	"Conditional Move instruction"			},
	{    "pat",	"Page Attribute Table"				},
	{    "pse36",	"36bit Page Size Extensions"			},
	{    "psn",	"96 bit Processor Serial Number"		},
	{    "mmx",	"MMX technology"				},
	{    "fxsr",	"fxsr"						},
	{    "kni",	"Streaming SIMD instructions"			},
	{    "xmm",	"Streaming SIMD instructions"			},
	{    "ht",      "HyperThreading"                                },
	{    NULL,	NULL						}
};

static GtkWidget *get_features_widget(CPUDevice * device)
{
    GtkWidget *widget, *scroll;
    GtkTextBuffer *buffer;
    GtkTextIter iter;
    gchar **flags;
    gint i, j;

    if (!device->flags)
	return NULL;

    buffer = gtk_text_buffer_new(FALSE);
    gtk_text_buffer_set_text(buffer, "", -1);
    gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0);

    flags = g_strsplit(device->flags, " ", G_N_ELEMENTS(small2large));
    for (i = 0; *(flags + i); i++) {
	for (j = 0; j < G_N_ELEMENTS(small2large); j++) {
	    if (small2large[j].small &&
		!strncmp(small2large[j].small, *(flags + i),
			 strlen(small2large[j].small))) {
//		gtk_text_buffer_insert(buffer, &iter, small2large[j].small,
//				       -1);
		gtk_text_buffer_insert(buffer, &iter, "● ", -1);
		gtk_text_buffer_insert(buffer, &iter, small2large[j].large,
				       -1);
		gtk_text_buffer_insert(buffer, &iter, "\n", -1);
		break;
	    }
	}
    }
    g_strfreev(flags);

    scroll = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
				   GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_container_set_border_width(GTK_CONTAINER(scroll), 4);
    gtk_widget_show(scroll);

    widget = gtk_text_view_new();
    gtk_text_view_set_buffer(GTK_TEXT_VIEW(widget), buffer);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
    gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(widget), FALSE);
    gtk_text_view_set_indent(GTK_TEXT_VIEW(widget), 5);
    gtk_widget_show(widget);

    gtk_container_add(GTK_CONTAINER(scroll), widget);

    return scroll;
}
#endif

void hi_show_cpu_info(MainWindow * mainwindow, CPUDevice * device)
{
    gchar *buf;

    if (!device)
	return;

    gtk_window_set_title(GTK_WINDOW(mainwindow->det_window->window),
			 device->processor);
    detail_window_set_icon(mainwindow->det_window, IMG_PREFIX "cpu.png");
    detail_window_set_dev_name(mainwindow->det_window, device->processor);
    detail_window_set_dev_type(mainwindow->det_window, device->machine);

    detail_window_append_info_int(mainwindow->det_window, _("Number"),
				  device->procno, FALSE);
    buf = g_strdup_printf("%dMHz", device->frequency);
    detail_window_append_info(mainwindow->det_window, _("Frequency"), buf);
    g_free(buf);

    detail_window_append_separator(mainwindow->det_window);
#ifdef ARCH_i386
    detail_window_append_info_int(mainwindow->det_window, _("Family"),
				  device->family, FALSE);
    detail_window_append_info_int(mainwindow->det_window, _("Model"),
				  device->model, FALSE);
    detail_window_append_info_int(mainwindow->det_window, _("Stepping"),
				  device->stepping, FALSE);
    detail_window_append_separator(mainwindow->det_window);
#endif

    if (device->cachel2) {
	buf = g_strdup_printf("%dkB", device->cachel2);
	detail_window_append_info(mainwindow->det_window, _("Cache L2"),
				  buf);
	g_free(buf);
    }

    buf = g_strdup_printf("%d bogomips", device->bogomips);
    detail_window_append_info(mainwindow->det_window, _("Bogomips"), buf);
    g_free(buf);

#ifdef ARCH_i386
    {
	GtkWidget *features, *label;

	label = gtk_label_new(_("Features"));
	gtk_widget_show(label);

	features = get_features_widget(device);
	gtk_notebook_append_page(GTK_NOTEBOOK
				 (mainwindow->det_window->notebook),
				 features, label);
    }
#endif

}

MemoryInfo *memory_get_info(void)
{
    MemoryInfo *mi;
    FILE *procmem;
    gchar buffer[128];
    gint memfree = 0, memused;

    mi = g_new0(MemoryInfo, 1);

    procmem = fopen("/proc/meminfo", "r");
    while (fgets(buffer, 128, procmem)) {
	gchar *buf = buffer;

	buf = g_strstrip(buf);

	if (!strncmp(buf, "MemTotal", 8))
	    get_int_val(mi->total)
	else if (!strncmp(buf, "MemFree", 7))
	    get_int_val(memfree)
	else if (!strncmp(buf, "Cached", 6))
	    get_int_val(mi->cached)
    }
    fclose(procmem);

    mi->used = mi->total - memfree;

    mi->total  /= 1000;
    mi->cached /= 1000;
    mi->used   /= 1000;
    memfree    /= 1000;

    memused = mi->total - mi->used + mi->cached;

    mi->ratio = 1 - (gdouble) memused / mi->total;

    return mi;
}

#if defined(ARCH_i386) || defined(ARCH_x86_64) || defined(ARCH_PARISC)
#define PARSE_PROC_CPU()					\
		if(!strncmp(buf, "bogomips", 8))		\
			get_int_val(ci->bogomips)		\
		else if(!strncmp(buf, "cpu family", 10))	\
			get_int_val(ci->family)			\
		else if(!strncmp(buf, "model name", 10))	\
			get_str_val(ci->processor)		\
		else if(!strncmp(buf, "flags", 5))		\
			get_str_val(ci->flags)			\
		else if(!strncmp(buf, "vendor_id", 8))		\
			get_str_val(ci->machine)		\
		else if(!strncmp(buf, "stepping", 8))		\
			get_int_val(ci->stepping)		\
		else if(!strncmp(buf, "cpu MHz", 7))		\
			get_int_val(ci->frequency)		\
		else if(!strncmp(buf, "cache size", 10))	\
			get_int_val(ci->cachel2)		\
		else if(!strncmp(buf, "model", 5))		\
			get_int_val(ci->model)
#endif
#ifdef ARCH_PPC
#define PARSE_PROC_CPU()					\
		if(!strncmp(buf, "bogomips", 8))		\
			get_int_val(ci->bogomips)		\
		else if(!strncmp(buf, "cpu", 3))		\
			get_str_val(ci->processor)		\
		else if(!strncmp(buf, "machine", 7))		\
			get_str_val(ci->machine)		\
		else if(!strncmp(buf, "clock", 5))		\
			get_int_val(ci->frequency)		\
		else if(!strncmp(buf, "L2 cache", 8))		\
			get_int_val(ci->cachel2)
#endif
#ifdef ARCH_m68k
#define	PARSE_PROC_CPU()					\
		if (!strncmp(buf, "CPU", 3))			\
			get_str_val(ci->processor)		\
		else if (!strncmp(buf, "BogoMips", 8))		\
			get_int_val(ci->bogomips)		\
		else if (!strncmp(buf, "Clocking", 8))		\
			get_int_val(ci->frequency)
#endif
#ifdef ARCH_MIPS
#define	PARSE_PROC_CPU()					\
                if (!strncmp(buf, "cpu model", 9))		\
                        get_str_val(ci->processor)		\
                else if (!strncmp(buf, "BogoMIPS", 8))		\
                        get_int_val(ci->bogomips)		\
                else if (!strncmp(buf, "system type", 11))	\
                        get_str_val(ci->machine)		
#endif

#ifndef PARSE_PROC_CPU
#error PARSE_PROC_CPU not defined! Maybe your arch is not supported yet;
#error please send me your /proc/cpuinfo and/or 'uname -a' output to
#error leandro@linuxmag.com.br; thanks.
#endif

static void computer_processor_info(ComputerInfo * ci)
{
    FILE *proccpu;
    gchar buffer[128];

    proccpu = fopen("/proc/cpuinfo", "r");
    while (fgets(buffer, 128, proccpu)) {
	gchar *buf = buffer;

	buf = g_strstrip(buf);

	PARSE_PROC_CPU();
    }
    fclose(proccpu);

#ifdef ARCH_PPC
    {
	gchar *proctemp;

	proctemp = g_strdup_printf("PowerPC %s", ci->processor);
	g_free(ci->processor);
	ci->processor = proctemp;
    }
#endif
#ifdef ARCH_m68k
    {
	gchar *proctemp;

	proctemp = g_strdup_printf("Motorola %s", ci->processor);
	g_free(ci->processor);
	ci->processor = proctemp;
    }
#endif

}

ComputerInfo *computer_get_info(void)
{
    gint i;
    struct stat st;
    ComputerInfo *ci;
    struct utsname utsbuf;

    ci = g_new0(ComputerInfo, 1);

    for (i = 0;; i++) {
	if (distro_db[i].file == NULL) {
	    ci->distrocode = g_strdup("unk");
	    ci->distroinfo = g_strdup(_("Unknown distribution"));
	    break;
	}

	if (!stat(distro_db[i].file, &st)) {
	    FILE *distro_ver;
	    char buf[128];

	    distro_ver = fopen(distro_db[i].file, "r");
	    fgets(buf, 128, distro_ver);
	    fclose(distro_ver);

	    buf[strlen(buf) - 1] = 0;

	    /*
	     * HACK: Some Debian systems doesn't include
	     * the distribuition name in /etc/debian_release,
	     * so add them here. 
	     */
	    if (!strncmp(distro_db[i].codename, "deb", 3) &&
		((buf[0] >= '0' && buf[0] <= '9') || buf[0] != 'D')) {
			ci->distroinfo = g_strdup_printf
			    ("Debian GNU/Linux %s", buf);
	    } else {
		ci->distroinfo = g_strdup(buf);
	    }

	    ci->distrocode = g_strdup(distro_db[i].codename);

	    break;
	}
    }

    uname(&utsbuf);
    ci->kernel = g_strdup_printf("%s %s (%s)", utsbuf.sysname,
				 utsbuf.release, utsbuf.machine);

    ci->hostname = g_strdup(utsbuf.nodename);

    computer_processor_info(ci);

    return ci;
}

/*
 * Code stolen from GKrellM <http://www.gkrellm.net>
 * Copyright (c) 1999-2002 Bill Wilson <bill@gkrellm.net>
 */
gboolean uptime_update(gpointer data)
{
    MainWindow *mainwindow = (MainWindow *) data;
    gchar *buf;
    gint days, hours;
    FILE *procuptime;
    gulong minutes = 0;

    if (!mainwindow)
	return FALSE;

#define plural(a) (a == 1) ? "" : "s"

    if ((procuptime = fopen("/proc/uptime", "r")) != NULL) {
	fscanf(procuptime, "%lu", &minutes);
	minutes /= 60;
	fclose(procuptime);
    } else
	return FALSE;

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

    if (days < 1) {
	buf = g_strdup_printf(_("%d hour%s and %ld minute%s"), hours,
			      plural(hours), minutes, plural(minutes));
    } else {
	buf =
	    g_strdup_printf(_("%d day%s, %d hour%s and %ld minute%s"),
			    days, plural(days), hours, plural(hours),
			    minutes, plural(minutes));
    }

    gtk_label_set_text(GTK_LABEL(mainwindow->uptime), buf);
    g_free(buf);

    return TRUE;
}

GtkWidget *os_get_widget(MainWindow * mainwindow)
{
    GtkWidget *label, *hbox;
    GtkWidget *table;
    GtkWidget *pixmap;
    gchar *buf;
    ComputerInfo *info;

    if (!mainwindow)
	return NULL;

    info = computer_get_info();

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_widget_show(hbox);

    buf =
	g_strdup_printf("%s/distro/%s.png", IMG_PREFIX, info->distrocode);
    pixmap = gtk_image_new_from_file(buf);
    gtk_widget_set_usize(GTK_WIDGET(pixmap), 64, 0);
    gtk_widget_show(pixmap);
    gtk_box_pack_start(GTK_BOX(hbox), pixmap, FALSE, FALSE, 0);
    g_free(buf);

    table = gtk_table_new(4, 2, FALSE);
    gtk_widget_show(table);
    gtk_box_pack_start(GTK_BOX(hbox), table, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(table), 10);
    gtk_table_set_row_spacings(GTK_TABLE(table), 4);
    gtk_table_set_col_spacings(GTK_TABLE(table), 4);

    /*
     * Table headers
     */
    label = gtk_label_new(_("<b>Computer name:</b>"));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 0, 1);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(_("<b>Distribution:</b>"));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 1, 2);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(_("<b>Kernel:</b>"));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 2, 3);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(_("<b>Uptime:</b>"));
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 0, 1, 3, 4);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);


    /*
     * Table content 
     */
    label = gtk_label_new(info->hostname);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 0, 1);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(info->distroinfo);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 1, 2);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(info->kernel);
    gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 2, 3);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    label = gtk_label_new(_("Updating..."));
    gtk_table_attach_defaults(GTK_TABLE(table), label, 1, 2, 3, 4);
    gtk_widget_show(label);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    mainwindow->uptime = label;

    uptime_update(mainwindow);
    gtk_timeout_add(30000, uptime_update, mainwindow);

    g_free(info);

    return hbox;
}

gboolean memory_update(gpointer data)
{
    MainWindow *mainwindow = (MainWindow *) data;
    MemoryInfo *mi;

    if (!mainwindow)
	return FALSE;

    mi = memory_get_info();

    gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(mainwindow->membar),
				  mi->ratio);

    g_free(mi);

    return TRUE;
}

GtkWidget *memory_get_widget(MainWindow * mainwindow)
{
    GtkWidget *label, *vbox, *hbox, *hbox2, *progress;
    GtkWidget *pixmap;
    MemoryInfo *mi;
    gchar *buf;

    mi = memory_get_info();

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_widget_show(hbox);

    buf = g_strdup_printf("%s/mem.png", IMG_PREFIX);
    pixmap = gtk_image_new_from_file(buf);
    gtk_widget_set_usize(GTK_WIDGET(pixmap), 64, 0);
    gtk_widget_show(pixmap);
    gtk_box_pack_start(GTK_BOX(hbox), pixmap, FALSE, FALSE, 0);
    g_free(buf);

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_widget_show(vbox);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
    gtk_box_set_spacing(GTK_BOX(vbox), 4);

    hbox2 = gtk_hbox_new(FALSE, 5);
    gtk_widget_show(hbox2);
    gtk_box_pack_start(GTK_BOX(vbox), hbox2, TRUE, TRUE, 0);

    label = gtk_label_new("0MB");
    gtk_widget_show(label);
    gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);

    buf = g_strdup_printf("%dMB", mi->total);
    label = gtk_label_new(buf);
    gtk_widget_show(label);
    gtk_box_pack_end(GTK_BOX(hbox2), label, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    g_free(buf);

    progress = gtk_progress_bar_new();
    mainwindow->membar = progress;
    gtk_widget_show(progress);
    gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 0);

    memory_update(mainwindow);

    gtk_timeout_add(2000, memory_update, mainwindow);

    g_free(mi);
    return hbox;
}

GtkWidget *processor_get_widget(void)
{
    GtkWidget *label, *vbox, *hbox;
    GtkWidget *pixmap;
    ComputerInfo *info;
    gchar *buf;

    info = computer_get_info();

    hbox = gtk_hbox_new(FALSE, 0);
    gtk_widget_show(hbox);

    buf = g_strdup_printf("%s/cpu.png", IMG_PREFIX);
    pixmap = gtk_image_new_from_file(buf);
    gtk_widget_set_usize(GTK_WIDGET(pixmap), 64, 0);
    gtk_widget_show(pixmap);
    gtk_box_pack_start(GTK_BOX(hbox), pixmap, FALSE, FALSE, 0);
    g_free(buf);

    vbox = gtk_vbox_new(FALSE, 0);
    gtk_widget_show(vbox);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 10);
    gtk_box_set_spacing(GTK_BOX(vbox), 4);

    buf = g_strdup_printf(_("<b>%s</b> at %d MHz"),
			  info->processor, info->frequency);

    label = gtk_label_new(buf);
    gtk_widget_show(label);
    gtk_label_set_use_markup(GTK_LABEL(label), TRUE);
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    g_free(buf);

#ifdef ARCH_i386
    buf = g_strdup_printf(_("Family %d, model %d, stepping %d"),
			  info->family, info->model, info->stepping);
    label = gtk_label_new(buf);
    gtk_widget_show(label);
    gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
    gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
    g_free(buf);
#endif

    if (info->cachel2) {
	buf = g_strdup_printf(_("%d KB L2 cache"), info->cachel2);
	label = gtk_label_new(buf);
	gtk_widget_show(label);

	gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);
	gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5);
	g_free(buf);
    }

    g_free(info);
    return hbox;
}