/*
 *    HardInfo - Displays System Information
 *    Copyright (C) 2003-2006 Leandro A. F. Pereira <leandro@hardinfo.org>
 *
 *    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
 */

/*
 * TODO: This thing must be rewritten. We really should have a struct with all the
 *       PCI stuff we'll present to the user, and hash them by the PCI ID
 *       (domain:bus:device.function).
 *       This way we'll have ways to better organize the output, instead of relying
 *       on the order the information appears on lspci's output.
 *       Also, the "Resources" thing might be better implemented (and we won't need
 *       copies of information scattered everywhere like we do today).
 */

#include <string.h>

#include "hardinfo.h"
#include "devices.h"

GHashTable *_pci_devices = NULL;

void
scan_pci_do(void)
{
    FILE *lspci;
    gchar buffer[256], *buf, *strhash = NULL, *strdevice = NULL;
    gchar *category = NULL, *name = NULL, *icon, *lspci_path, *command_line = NULL;
    gint n = 0, x = 0;

    if ((lspci_path = find_program("lspci")) == NULL) {
      goto pci_error;
    } else {
      command_line = g_strdup_printf("%s -v", lspci_path);
    }

    if (!_pci_devices) {
      _pci_devices = g_hash_table_new(g_str_hash, g_str_equal);
    }

    buf = g_build_filename(g_get_home_dir(), ".hardinfo", "pci.ids", NULL);
    if (!g_file_test(buf, G_FILE_TEST_EXISTS)) {
      DEBUG("using system-provided PCI IDs");
      g_free(buf);
      if (!(lspci = popen(command_line, "r"))) {
        goto pci_error;
      }
    } else {
      gchar *tmp;

      tmp = g_strdup_printf("%s -i '%s'", command_line, buf);
      g_free(buf);
      buf = tmp;

      DEBUG("using updated PCI IDs (from %s)", buf);
      if (!(lspci = popen(tmp, "r"))) {
        g_free(buf);
        goto pci_error;
      } else {
        g_free(buf);
      }
    }

    while (fgets(buffer, 256, lspci)) {
	buf = g_strstrip(buffer);

	if (!strncmp(buf, "Flags", 5)) {
	    gint irq = 0, freq = 0, latency = 0, i;
	    gchar **list;
	    gboolean bus_master;

	    buf += 7;

	    bus_master = FALSE;

	    list = g_strsplit(buf, ", ", 10);
	    for (i = 0; i <= 10; i++) {
		if (!list[i])
		    break;

		if (!strncmp(list[i], "IRQ", 3))
		    sscanf(list[i], "IRQ %d", &irq);
		else if (strstr(list[i], "Mhz"))
		    sscanf(list[i], "%dMhz", &freq);
		else if (!strncmp(list[i], "bus master", 10))
		    bus_master = TRUE;
		else if (!strncmp(list[i], "latency", 7))
		    sscanf(list[i], "latency %d", &latency);
	    }
	    g_strfreev(list);

	    if (irq)
		strdevice = h_strdup_cprintf("%s=%d\n", strdevice, _("IRQ"), irq);
	    if (freq)
		strdevice = h_strdup_cprintf("%s=%d %s\n", strdevice, _("Frequency"), freq, _("MHz") );
	    if (latency)
		strdevice = h_strdup_cprintf("%s=%d\n", strdevice, _("Latency"), latency);

	    strdevice = h_strdup_cprintf("%s=%s\n", strdevice, _("Bus Master"), bus_master ? _("Yes") : _("No") );
	} else if (!strncmp(buf, "Kernel modules", 14)) {
	    WALK_UNTIL(' ');
	    WALK_UNTIL(':');
	    buf++;

	    strdevice = h_strdup_cprintf("%s=%s\n", strdevice, _("Kernel modules"), buf);
	} else if (!strncmp(buf, "Subsystem", 9)) {
	    WALK_UNTIL(' ');
	    buf++;
	    const gchar *oem_vendor_url = vendor_get_url(buf);
            if (oem_vendor_url)
                strdevice = h_strdup_cprintf(_("%s=%s (%s)\n"),
                                            strdevice,
                                            _("OEM Vendor"),
                                            vendor_get_name(buf),
                                            oem_vendor_url);
	} else if (!strncmp(buf, "Capabilities", 12)
		   && !strstr(buf, "only to root") &&
		      !strstr(buf, "access denied")) {
	    WALK_UNTIL(' ');
	    WALK_UNTIL(']');
	    buf++;
	    strdevice = h_strdup_cprintf("Capability#%d=%s\n", strdevice, ++x, buf);
	} else if (!strncmp(buf, "Memory at", 9) && strstr(buf, "[size=")) {
	    gint mem;
	    gchar unit;
	    gboolean prefetch;
	    gboolean _32bit;

	    prefetch = strstr(buf, "non-prefetchable") ? FALSE : TRUE;
	    _32bit = strstr(buf, "32-bit") ? TRUE : FALSE;

	    WALK_UNTIL('[');
	    sscanf(buf, "[size=%d%c", &mem, &unit);

	    strdevice = h_strdup_cprintf("%s#%d=%d%cB (%s%s)\n",
					strdevice, _("Memory"), ++x,
					mem,
					(unit == ']') ? ' ' : unit,
					_32bit ? "32-bit, " : "",
					prefetch ? _("prefetchable") :
					_("non-prefetchable") );

	} else if (!strncmp(buf, "I/O ports at", 12)) {
	    guint io_addr, io_size;

	    sscanf(buf, "I/O ports at %x [size=%d]", &io_addr, &io_size);

	    strdevice =
		h_strdup_cprintf("%s#%d=0x%x - 0x%x\n",
				strdevice, _("I/O ports at"), ++x, io_addr,
				io_addr + io_size - 1);
	} else if ((buf[0] >= '0' && buf[0] <= '9') && (buf[4] == ':' || buf[2] == ':')) {
	    gint bus, device, function, domain;
	    gpointer start, end;

	    if (strdevice != NULL && strhash != NULL) {
                moreinfo_add_with_prefix("DEV", strhash, strdevice);
                g_free(strhash);
                g_free(category);
                g_free(name);
	    }

	    if (buf[4] == ':') {
		sscanf(buf, "%x:%x:%x.%d", &domain, &bus, &device, &function);
	    } else {
	    	/* lspci without domain field */
	    	sscanf(buf, "%x:%x.%x", &bus, &device, &function);
	    	domain = 0;
	    }

	    WALK_UNTIL(' ');

	    start = buf;

	    WALK_UNTIL(':');
	    end = buf + 1;
	    *buf = 0;

	    buf = start + 1;
	    category = g_strdup(buf);

	    buf = end;

            if (strstr(category, "RAM memory")) icon = "mem";
            else if (strstr(category, "Multimedia")) icon = "media";
            else if (strstr(category, "USB")) icon = "usb";
            else icon = "pci";

	    name = g_strdup(buf);
            g_hash_table_insert(_pci_devices,
                                g_strdup_printf("0000:%02x:%02x.%x", bus, device, function),
                                name);

        strhash = g_strdup_printf("PCI%d", n);
        strdevice = g_strdup_printf("[%s]\n"
               /* Name */   "%s=%s\n"
               /* Class */  "%s=%s\n"
               /* Domain */ "%s=%d\n"
               /* Bus, device, function */
                            "%s=%d, %d, %d\n",
                    _("Device Information"),
                    _("Name"), name,
                    _("Class"), category,
                    _("Domain"), domain,
                    _("Bus, device, function"),
                    bus, device, function);

            const gchar *url = vendor_get_url(name);
            if (url) {
                strdevice = h_strdup_cprintf("%s=%s (%s)\n",
                                            strdevice,
                                            _("Vendor"),
                                            vendor_get_name(name),
                                            url);
            }

            g_hash_table_insert(_pci_devices,
                                g_strdup_printf("0000:%02x:%02x.%x", bus, device, function),
                                g_strdup(name));

	    pci_list = h_strdup_cprintf("$PCI%d$%s=%s\n", pci_list, n, category, name);

	    n++;
	}
    }

    if (pclose(lspci)) {
pci_error:
        /* error (no pci, perhaps?) */
        pci_list = g_strconcat(pci_list, _("No PCI devices found"), "=\n", NULL);
    } else if (strhash) {
        /* insert the last device */
        moreinfo_add_with_prefix("DEV", strhash, strdevice);
        g_free(strhash);
        g_free(category);
        g_free(name);
    }

    g_free(lspci_path);
    g_free(command_line);
}
