/*
 *	sysinfo - A program for getting and setting system parameters.
 *
 *	Copyright (C) 2005 Patrick Mochel
 *
 *	This file is released under the GPLv2.
 */

#include <stdio.h>
#include <getopt.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#include "sysfs.h"
#include "util.h"
#include "si.h"

#define SI_VERSION "0.01"


/*
 * FIXME
 */

extern struct si_cmd ls_cmd;
extern struct si_cmd subsys_cmd;
extern struct si_cmd cls_cmd;
extern struct si_cmd net_cmd;
extern struct si_cmd cat_cmd;
extern struct si_cmd write_cmd;
extern struct si_cmd mod_cmd;
extern struct si_cmd watch_cmd;
extern struct si_cmd wait_cmd;
extern struct si_cmd pci_cmd;
extern struct si_cmd block_cmd;

static struct si_cmd * si_cmd_list[] = {
	&ls_cmd,
	&subsys_cmd,
	&cls_cmd, 
	&net_cmd,
	&cat_cmd,
	&write_cmd,
	&mod_cmd,
	&watch_cmd,
	&wait_cmd,
	&pci_cmd,
	&block_cmd,
};

static int si_cmd_num = sizeof(si_cmd_list) / sizeof(si_cmd_list[0]);


static int si_init(void)
{
	int i;

	dbg("Configured Commands:\n");
	for (i = 0; i < si_cmd_num; i++) {
		dbg("\t%s\n", si_cmd_list[i]->c_name);
	};
	dbg("%d / %d Total\n", si_cmd_num, SI_CMD_MAX);
	return 0;
}

static void show_help(void)
{
	int i;

	printf("si Usage:\n");
	printf("\tsi [ <options> ... ] <command> [ <command options> ]\n");
	printf("\n");
	printf("Options:\n");
	printf("\t-D | --debug                Turn on Debugging\n");
	printf("\t-H | --help                 Print help and exit\n");
	printf("\t-v | --verbose              Enable verbose output\n");
	printf("\t-V | --version              Show version information\n");
	printf("\n");
	printf("\t-h | --header               Show header information\n");
	printf("\t-s | --space                Use ' ' instead of '\\n' when printing\n");
	printf("\t-q | --quiet                Don't output anything\n");
	printf("\n");
	printf("\t-a | --attrs                Only show attributes (when applicable)\n");
	printf("\t-o | --objs                 Only show objects (when applicable)\n");
	printf("\t-l | --links                Only show links (when applicable)\n");
	printf("\n\n");
	for (i = 0; i < si_cmd_num; i++) {
		struct si_cmd * c = si_cmd_list[i];
		printf("\t%s%*c%s\n", c->c_name, 28 - strlen(c->c_name), ' ',
		       c->c_help ? *c->c_help : "Do Something Unknown");
		printf("\t%28c%s\n\n", ' ', 
		       c->c_usage ? *c->c_usage : "<unknown>");
	}
	printf("\n");
}

static void show_version(void)
{
	printf("si version %s\n", SI_VERSION);
	printf("\n\n");
}


static int parse_args(struct si_action * a, int argc, char ** argv)
{
	char * shortopt = "+DHvVhsqaol";
	struct option longopt[] = {
		{ "debug",	0,	NULL,	'D' },
		{ "help",	0,	NULL,	'H' },
		{ "verbose",	0,	NULL,	'v' },
		{ "version",	0,	NULL,	'V' },

		{ "header",	0,	NULL,	'h' },
		{ "space",	0,	NULL,	's' },
		{ "quiet",	0,	NULL,	'q' },

		{ "attrs",	0,	NULL,	'a' },
		{ "objs",	0,	NULL,	'o' },
		{ "links",	0,	NULL,	'l' },

		{ NULL },
	};
	int c, idx;
	int error = 0;

	while ((c = getopt_long(argc, argv, shortopt, longopt, &idx)) != -1) {
		switch (c) {
		case 'D':
			printf("* Debugging Enabled\n");
			si_debug = 1;
			break;
		case 'H':
			show_help();
			exit(0);
		case 'v':
			printf("* Verbose Reporting Enabled\n");
			si_verbose = 1;
			break;
		case 'V':
			show_version();
			exit(0);

		case 'h':
			a->a_header = 1;
			break;

		case 's':
			a->a_delim = ' ';
			break;

		case 'q':
			a->a_quiet = 1;
			a->a_show_objs = a->a_show_attrs = a->a_show_links = 0;
			break;

		case 'a':
			a->a_show_attrs = 1;
			a->a_show_objs = a->a_show_links = 0;
			break;

		case 'o':
			a->a_show_objs = 1;
			a->a_show_attrs = a->a_show_links = 0;
			break;
	
		case 'l':
			a->a_show_links = 1;
			a->a_show_objs = a->a_show_attrs = 0;
			break;

		case '?':
		default:
			show_help();
			error = -EINVAL;
			goto Done;
		}
	}
 Done:
	return error;
}

static void init_action(struct si_action * a)
{
	memset(a, 0, sizeof(struct si_action));
	a->a_delim = '\n';
	a->a_show_objs = a->a_show_attrs = a->a_show_links = 1;
}


static int cmd_lookup(char * cmd_name, struct si_action * a)
{
	int i;

	dbg("Looking Up '%s'\n", cmd_name);
	for (i = 0; i < si_cmd_num; i++) {
		if (!strcmp(si_cmd_list[i]->c_name, cmd_name))
			break;
	}
	if (i >= si_cmd_num) {
		vrb("Command '%s' Not Found\n", cmd_name);
		return ENOENT;
	}
	a->a_cmd = si_cmd_list[i];
	return 0;
}

static int cmd_init(int argc, char ** argv, struct si_action * a)
{
	struct si_cmd * c;
	int error;

	a->a_argc = argc - optind - 1;
	a->a_argv = argv + optind + 1;

	error = cmd_lookup(argv[optind], a);

	if (error)
		return error;

	c = a->a_cmd;
	
	dbg("Initializing '%s Command: %d args\n", c->c_name, a->a_argc);
	return c->c_init ? c->c_init(a) : 0;
}

static int cmd_exec(struct si_action * a)
{
	struct si_cmd * c = a->a_cmd;
	dbg("Executing '%s' Command\n", c->c_name);
	return c->c_exec ? c->c_exec(a) : 0;
}

static void cmd_exit(struct si_action * a)
{
	struct si_cmd * c = a->a_cmd;
	dbg("Cleaning up '%s' Command\n", c->c_name);
	if (c->c_exit)
		c->c_exit(a);
}

static void print_records(struct si_action * a)
{
	struct record_col * rc = a->a_records;
	int i;

	if (a->a_quiet)
		return;

	if (!rc)
		return;
	do {
		if (a->a_header)
			printf("%d %s%s\n", rc->rc_used, rc->rc_name, 
			       rc->rc_num > 1 ? rc->rc_suf : "");

		if (rc->rc_first)
			printf("    %s\n", rc->rc_first);
		for (i = 0; i < rc->rc_num; i++) {
			struct record * r = &rc->rc_records[i];
			if (r->r_str)
				printf("    %s\n", r->r_str);
		}
		printf("\n");
		rc = rc->rc_next;
	} while (rc != a->a_records);
}

int main(int argc, char ** argv)
{
	struct sysfs_info si;
	struct si_action a;
	int error;

	init_action(&a);

	if ((error = parse_args(&a, argc, argv)))
		goto Done;

	if ((error = si_init()))
		goto Done;

	if ((error = sysfs_init(&si)))
		goto Done;

	if (optind < argc) {
		error = cmd_init(argc, argv, &a);

		if (error)
			goto SysfsExit;
	} else {
		vrb("No Command Given\n");
		error = -EINVAL;
		goto SysfsExit;
	}
	
	error = cmd_exec(&a);

	if (!error)
		print_records(&a);

	cmd_exit(&a);
 SysfsExit:
	sysfs_exit(&si);
 Done:
	if (error) {
		errno = -error;
		error = -1;
		perror("si");
	}
	return error;
}
