#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <getopt.h>
#include <string.h>
#include "sysfs.h"
#include "si.h"

struct si_pci_device {
	char			* d_name;
	struct record_col	d_rc_links;
	struct record_col	d_rc_attrs;
	struct record_col	d_rc_power;
	struct sysfs_attr_list	d_attr;
};

struct si_pci_driver {
	struct sysfs_link_list	d_devices;
	struct sysfs_attr_list	d_attrs;
	struct record_col	d_rc;
};

struct si_pci_data {
	char				* d_option;
	struct sysfs_object		d_so_pci;
	struct sysfs_object_list	d_drivers;
	struct sysfs_link_list		d_devices;

	int				d_num_devices;
	struct si_pci_device		* d_devs;
	struct si_pci_driver		* d_drvs;

	struct record_col		d_rc;
	struct record_col		d_rc_devices;
	struct record_col		d_rc_drivers;
};

static int get_driver_list(struct si_pci_data * data)
{
	struct sysfs_object so;
	int error;

	error = sysfs_object_init(&so, "drivers");
	if (error)
		return error;

	error = sysfs_list_children(&data->d_drivers);

	sysfs_object_exit(&so);
	return error;
}

static int get_device_list(struct si_pci_data * data)
{
	struct sysfs_object so;
	int error;

	error = sysfs_object_init(&so, "devices");
	if (error)
		return error;

	error = sysfs_list_link(&data->d_devices);

	sysfs_object_exit(&so);
	return error;
}

static int get_object_lists(struct si_pci_data * data)
{
	int error;
	
	error = get_driver_list(data);
	if (error)
		return error;

	error = get_device_list(data);
	return error;
}

/**
 *	pci_init - Check arguments and allocate private data structures.
 *	@a:	Session data.
 */

static int pci_init(struct si_action * a)
{
	struct si_pci_data * data;
	int error = 0;

	if (a->a_argc > 4)
		return -EINVAL;

	data = calloc(1, sizeof(struct si_pci_data));
	if (!data)
		return -ENOMEM;
	
	error = sysfs_object_init(&data->d_so_pci, "bus/pci");
	if (error)
		return error;

	error = get_object_lists(data);
	if (error)
		goto ObjectExit;

	if (a->a_argc == 1)
		data->d_option = a->a_argv[0];

	a->a_data = data;
	return 0;

 ObjectExit:
	a->a_data = NULL;
	sysfs_object_exit(&data->d_so_pci);
	return error;
}

static int record_devices(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error;

	error = rc_init(&data->d_rc_devices, data->d_devices.sll_num);
	if (error)
		return error;

	error = record_sll(&data->d_devices, &data->d_rc_devices);
	if (!error) {
		record_first(&data->d_rc_devices, "[devices]");
		rc_add(a, &data->d_rc_devices);
	} else
		rc_exit(&data->d_rc_devices);
	return error;
}

static int record_drivers(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error;
	
	error = rc_init(&data->d_rc_drivers, data->d_drivers.sol_num);
	if (error)
		return error;

	error = record_sol(&data->d_drivers, &data->d_rc_drivers);
	if (!error) {
		record_first(&data->d_rc_drivers, "[drivers]");
		rc_add(a, &data->d_rc_drivers);
	} else
		rc_exit(&data->d_rc_drivers);
	return error;

}

static int record_default(struct si_action * a)
{
	int error;

	error = record_devices(a);
	if (!error)
		error = record_drivers(a);
	return error;
}

static int record_device_attr(struct si_action * a, struct si_pci_device * dev)
{
	struct si_pci_data * data = a->a_data;
	struct sysfs_attr * sa_one, * sa_two;
	int error;

	error = sysfs_list_attr(&dev->d_attr);
	if (error)
		return error;

	error = sysfs_read_attr(&dev->d_attr);
	if (error)
		goto Unlist;

	error = rc_init(&dev->d_rc_attrs, dev->d_attr.sal_num);
	if (error)
		goto Unlist;

	record_first(&dev->d_rc_attrs, "[%s]", dev->d_name);
	/* 
	 * Show Vendor/Device info first
	 */
	sa_one = sysfs_find_attr(&dev->d_attr, "vendor");
	sa_two = sysfs_find_attr(&dev->d_attr, "device");
	if (sa_one && sa_two) {
		const char * vd_header = "Vendor / Device";

		sysfs_chop_attr(sa_one);
		sysfs_chop_attr(sa_two);
		error = record_add(&dev->d_rc_attrs, "%s%*c%s / %s",
				   vd_header, 30 - strlen(vd_header), ' ', 
				   sa_one->a_data, sa_two->a_data);
		sa_one->a_flag = 1;
		sa_two->a_flag = 1;
	}

	/*
	 * Now show Subsystem Vendor / Device
	 */
	sa_one = sysfs_find_attr(&dev->d_attr, "subsystem_vendor");
	sa_two = sysfs_find_attr(&dev->d_attr, "subsystem_device");
	if (sa_one && sa_two) {
		const char * svd_header = "Subsystem Vendor / Device";
		
		sysfs_chop_attr(sa_one);
		sysfs_chop_attr(sa_two);
		error = record_add(&dev->d_rc_attrs, "%s%*c%s / %s",
				   svd_header, 30 - strlen(svd_header), ' ',
				   sa_one->a_data, sa_two->a_data);
		sa_one->a_flag = 1;
		sa_two->a_flag = 1;
	}

	/*
	 * Handle 'config' and 'resource' specially.
	 */
	sa_one = sysfs_find_attr(&dev->d_attr, "config");
	sa_two = sysfs_find_attr(&dev->d_attr, "resource");
	if (sa_one && sa_two) {
		sa_one->a_flag = 1;
		sa_two->a_flag = 1;
	}

	sa_one = sysfs_find_attr(&dev->d_attr, "pools");
	if (sa_one) {
		sa_one->a_flag = 1;
	}

	format_attributes(a, &dev->d_rc_attrs, &dev->d_attr);
	rc_add(a, &dev->d_rc_attrs);
 Unlist:
	sysfs_unlist_attr(&dev->d_attr);
	return error;
}

static int record_one_device(struct si_action * a, struct si_pci_device * dev)
{
	struct sysfs_object dev_so;
	struct sysfs_object power_so;
	char dev_path[PATH_MAX];
	int error;
	int len;

	len = snprintf(dev_path, PATH_MAX, "devices/%s", dev->d_name);
	if (len > PATH_MAX)
		return -EOVERFLOW;

	error = sysfs_object_init(&dev_so, dev_path);
	if (error)
		return error;

	error = record_links(a, &dev->d_rc_links);
	if (error)
		goto Exit;

	error = record_device_attr(a, dev);
	if (error)
		goto LinkExit;

	error = sysfs_object_init(&power_so, "power");
	if (error)
		goto AttrExit;

	error = read_attributes(a, &dev->d_rc_power);
	
	sysfs_object_exit(&power_so);
	if (error)
		goto AttrExit;

 Exit:
	sysfs_object_exit(&dev_so);
	return error;

 AttrExit:
	rc_exit(&dev->d_rc_attrs);
 LinkExit:
	rc_exit(&dev->d_rc_links);
	goto Exit;
}

static void unrecord_one_device(struct si_pci_device * dev)
{
	rc_exit(&dev->d_rc_links);
	rc_exit(&dev->d_rc_attrs);
	rc_exit(&dev->d_rc_power);
}

static void unrecord_all_devices(struct si_pci_data * data)
{
	int i;

	if (data->d_devs) {
		for (i = 0; i < data->d_num_devices; i++) 
			unrecord_one_device(&data->d_devs[i]);
	}
}

static int record_all_devices(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error = 0;
	int i;

	data->d_devs = calloc(data->d_devices.sll_num, 
			      sizeof(struct si_pci_device));
	if (!data->d_devs)
		return -ENOMEM;
	
	for (i = 0; i < data->d_devices.sll_num; i++) {
		data->d_devs[i].d_name = data->d_devices.sll_name[i];
		error = record_one_device(a, &data->d_devs[i]);
		if (error)
			goto RecordFail;
	}

	return error;

 RecordFail:
	while (--i >= 0) {
		unrecord_one_device(&data->d_devs[i]);
	}
	free(data->d_devs);
	data->d_devs = NULL;
	return error;
}

static int record_all_drivers(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error;

	

	return error;
}

static int record_option(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error;

	if (!strcmp(data->d_option, "dev"))
		error = record_all_devices(a);
	else if (!strcmp(data->d_option, "drv"))
		error = record_all_drivers(a);
	else
		error = -EINVAL;

	return error;
}

static int pci_exec(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;
	int error;

	if (data->d_option)
		error = record_option(a);
	else
		error = record_default(a);

	return error;
}

static void pci_exit(struct si_action * a)
{
	struct si_pci_data * data = a->a_data;

	a->a_data = NULL;

	unrecord_all_devices(data);
	rc_exit(&data->d_rc_devices);
	rc_exit(&data->d_rc_drivers);
	sysfs_object_exit(&data->d_so_pci);
	free(data);
}

static const char * pci_help = "Show PCI Information.";
static const char * pci_usage = "[devices] | [drivers] | [<device> | <driver>]";


decl_cmd(pci);

