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

struct si_mod_data {
	char			* d_module;
	char			* d_option;
	char			* d_param;
	char			* d_value;
	struct record_col	d_rc_link;
	struct record_col	d_rc_refcnt;
	struct record_col	d_rc_param;
	struct record_col	d_rc_sections;

	struct record_col	d_rc_modules;
};

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

static int mod_init(struct si_action * a)
{
	struct si_mod_data * data;
	int error = 0;

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

	data = calloc(1, sizeof(struct si_mod_data));
	if (!data)
		return -ENOMEM;

	switch (a->a_argc) {
	case 4:
		data->d_value = a->a_argv[3];
	case 3:
		data->d_param = a->a_argv[2];
	case 2:
		data->d_option = a->a_argv[1];
	case 1:
		data->d_module = a->a_argv[0];
	default:
		break;
	}

	a->a_data = data;
	return error;
}


static int record_refcnt(struct si_action * a, struct record_col * rc)
{
	char buffer[PAGE_SIZE];
	int error;

	error = rc_init(rc, 1);
	if (error)
		return error;

	error = sysfs_read_file("refcnt", buffer);
	if (error < 0)
		goto RecordFail;

	error = record_add(rc, "refcnt%20s", buffer);
	if (error)
		goto RecordFail;
	rc_add(a, rc);
	return 0;

 RecordFail:
	rc_exit(rc);
	return error;
}

static int record_sections(struct si_action * a, struct record_col * rc)
{
	struct sysfs_object so;
	int error;

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

	error = read_attributes(a, rc);
	if (!error)
		record_first(rc, "[sections]");

	sysfs_object_exit(&so);
	return error;
}

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

	error = record_refcnt(a, &data->d_rc_refcnt);
	if (error)
		return error;

	error = read_attributes(a, &data->d_rc_param);
	if (error)
		goto RefcntExit;

	error = record_sections(a, &data->d_rc_sections);
	if (error)
		goto ParamExit;

	return 0;

 ParamExit:
	rc_exit(&data->d_rc_param);
 RefcntExit:
	rc_exit(&data->d_rc_refcnt);
	return error;
}

static int record_option(struct si_action * a)
{
	struct si_mod_data * data = a->a_data;
	int error;
	
	if (!strcmp(data->d_option, "refcnt"))
		error = record_refcnt(a, &data->d_rc_refcnt);
	else if (!strcmp(data->d_option, "param"))
		error = read_attributes(a, &data->d_rc_param);
	else if (!strcmp(data->d_option, "sections"))
		error = record_sections(a, &data->d_rc_sections);
	else if (!strcmp(data->d_option, "all"))
		error = record_all(a);
	else
		error = -EINVAL;
	return error;
}

static int write_param(struct si_action * a)
{
	struct si_mod_data * data = a->a_data;
	int error;
	int len;

	len = strlen(data->d_value);

	error = sysfs_write_file(data->d_param, data->d_value, len);
	if (error == len)
		error = 0;
	else if (error > 0)
		error = -EFAULT;
	return error;
}

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

	error = record_links(a, &data->d_rc_link);
	if (error)
		return error;

	error = read_attributes(a, &data->d_rc_param);
	if (error)
		goto FreeLinks;

	/*
	 * Prune the refcnt attribute
	 */
	record_prune(&data->d_rc_param, "refcnt");
	return 0;

 FreeLinks:
	rc_exit(&data->d_rc_link);
	return error;
}

static int record_one_module(struct si_action * a)
{
	struct si_mod_data * data = a->a_data;
	struct sysfs_object so;
	char mod_path[PATH_MAX];
	int error = 0;
	int len;

	len = snprintf(mod_path, PATH_MAX, "module/%s", data->d_module);
	if (len > PATH_MAX)
		return -EOVERFLOW;
	else if (len < 0) {
		error = len;
		goto Done;
	}

	error = sysfs_object_init(&so, mod_path);
	if (error)
		goto Done;

	/*
	 * If we have ->d_option AND ->d_value, then we assume that
	 * ->d_option is the name of a module parameter.
	 *
	 * Otherwise we assume that it's a class of attributes, one of:
	 *	- refcnt
	 *	- param
	 *	- sections
	 *	- all
	 */
	if (data->d_option) {
		if (data->d_value)
			error = write_param(a);
		else
			error = record_option(a);
	} else
		error = record_basics(a);

	sysfs_object_exit(&so);
 Done:
	return error;
}

static int read_all_refcnts(struct sysfs_object_list * sol, 
			      struct record_col * rc, int * p_maxlen)
{
	char buffer[PAGE_SIZE];
	char path[PATH_MAX];
	int maxlen = 0;
	int error = 0;
	int i;

	for (i = 0; i < sol->sol_num; i++) {
		int len;

		len = snprintf(path, PATH_MAX, "%s/refcnt", sol->sol_list[i]);
		if (len > PATH_MAX)
			goto ReadFailed;
		if (len > maxlen)
			maxlen = len;
		error = sysfs_read_file(path, buffer);
		if (error > 0 && error < 16)
			error = 0;
		else if (error > 16) {
			error = -EOVERFLOW;
			goto ReadFailed;
		} else
			goto ReadFailed;

		record_add(rc, buffer);
	}
	*p_maxlen = maxlen;
	return error;

 ReadFailed:
	while (--i > 0)
		record_del(rc);
	rc->rc_num = 0;
	return error;
}

static int record_all_modules(struct si_action * a)
{
	struct si_mod_data * data = a->a_data;
	struct sysfs_object so;
	struct sysfs_object_list sol;
	struct record_col rc_ref;
	int maxlen;
	int error;
	int i;

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

	error = sysfs_list_children(&sol);
	if (error)
		goto ObjectExit;

	if (!sol.sol_num)
		goto UnlistObject;

	dbg("Whoa, Found %d modules!\n", sol.sol_num);

	error = rc_init(&rc_ref, sol.sol_num);
	if (error)
		return error;

	error = rc_init(&data->d_rc_modules, sol.sol_num);
	if (error)
		goto UnlistObject;

	error = read_all_refcnts(&sol, &rc_ref, &maxlen);
	if (error)
		goto RcExit;

	for (i = 0; i < sol.sol_num; i++) {
		error = record_add(&data->d_rc_modules, "%s%*c%s", 
				   sol.sol_list[i],
				   maxlen + 10 - strlen(sol.sol_list[i]), 
				   ' ', rc_ref.rc_records[i].r_str);
		if (error)
			goto FreeRefcnts;
	}
	rc_add(a, &data->d_rc_modules);

 FreeRefcnts:
	rc_exit(&rc_ref);
 UnlistObject:
	sysfs_unlist_children(&sol);
 ObjectExit:
	sysfs_object_exit(&so);
	return error;
 RcExit:
	rc_exit(&data->d_rc_modules);
	goto FreeRefcnts;
}

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

	if (data->d_module)
		error = record_one_module(a);
	else
		error = record_all_modules(a);

	return error;
}

static void mod_exit(struct si_action * a)
{
	struct si_mod_data * data = a->a_data;

	a->a_data = NULL;
	free(data);
}

static const char * mod_help = "Display Module Information.";
static const char * mod_usage = 
	"[ <module> [ <option> ] | [ <param> [ <value> ]]";

decl_cmd(mod);

