#define _GNU_SOURCE	/* for vasprintf() */
#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

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


void rc_add(struct si_action * a, struct record_col * rc)
{
	if (a->a_records) {
		rc->rc_prev = a->a_records->rc_prev;
		rc->rc_next = a->a_records;

		a->a_records->rc_prev->rc_next = rc;
		a->a_records->rc_prev = rc;
	} else {
		a->a_records = rc;
		rc->rc_next = rc->rc_prev = rc;
	}
}

void rc_del(struct record_col * rc)
{
	rc->rc_next->rc_prev = rc->rc_prev;
	rc->rc_prev->rc_next = rc->rc_next;

	rc->rc_next = rc->rc_prev = NULL;
}


int rc_init(struct record_col * rc, int num)
{
	memset(rc, 0, sizeof(struct record_col));

	if (num) {
		rc->rc_records = calloc(num, sizeof(struct record));
		if (!rc->rc_records)
			return -ENOMEM;
		rc->rc_num = num;
	}
	return 0;
}

void rc_exit(struct record_col * rc)
{
	int i;

	if (rc->rc_next)
		rc_del(rc);

	for (i = 0; i < rc->rc_num; i++)
		free(rc->rc_records[i].r_str);
	if (rc->rc_records)
		free(rc->rc_records);
	if (rc->rc_first)
		free(rc->rc_first);
	memset(rc, 0, sizeof(struct record_col));
}


int record_add(struct record_col * rc, char * fmt, ...)
{
	struct record * r = rc->rc_records + rc->rc_used;
	int error = 0;
	va_list ap;

	va_start(ap, fmt);
	r->r_len = vasprintf(&r->r_str, fmt, ap);
	if (r->r_len < 0)
		error = -ENOMEM;
	else
		rc->rc_used++;
	va_end(ap);
	return error;
}

void record_del(struct record_col * rc)
{
	struct record * r = &rc->rc_records[--rc->rc_used];
	free(r->r_str);
	r->r_str = NULL;
	r->r_len = 0;
}

int record_add_pos(struct record_col * rc, int pos, char * fmt, ...)
{
	struct record * r = rc->rc_records + pos;
	va_list ap;
	int error = 0;

	va_start(ap, fmt);
	r->r_len = vasprintf(&r->r_str, fmt, ap);
	if (r->r_len < 0)
		error= -ENOMEM;
	else
		rc->rc_used++;
	va_end(ap);
	return error;
}

void record_del_pos(struct record_col * rc, int pos)
{
	struct record * r = &rc->rc_records[pos];

	free(r->r_str);
	r->r_str = NULL;
	r->r_len = 0;
	rc->rc_used--;
}

int record_first(struct record_col * rc, char * fmt, ...)
{
	int error = 0;
	va_list ap;
	int len;

	va_start(ap, fmt);
	len = vasprintf(&rc->rc_first, fmt, ap);
	if (len < 0)
		error = -ENOMEM;
	return error;
}

int record_find(struct record_col * rc, char * rec)
{
	struct record * r;
	int i;

	for (i = 0; i < rc->rc_num; i++) {
		r = &rc->rc_records[i];
		/*
		 * Look for 'refcnt' at the beginning of the attribute record.
		 */
		if (r->r_str && (strstr(r->r_str, rec) == r->r_str))
			return i;
	}
	return -ENOENT;
}

void record_prune(struct record_col * rc, char * rec)
{
	int pos;

	pos = record_find(rc, rec);
	if (pos >= 0)
		record_del_pos(rc, pos);
}


int record_sol(struct sysfs_object_list * sol, struct record_col * rc)
{
	int error = 0;
	int i;

	for (i = 0; i < sol->sol_num; i++) {
		error = record_add(rc, sol->sol_list[i]);
		if (error)
			goto RecordFail;
	}
	return 0;

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

int record_sal(struct sysfs_attr_list * sal, struct record_col * rc)
{
	int error = 0;
	int i;

	for (i = 0; i < sal->sal_num; i++) {
		error = record_add(rc, sal->sal_list[i]);
		if (error)
			goto RecordFail;
	}
	return 0;

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

int record_sll(struct sysfs_link_list * sll, struct record_col * rc)
{
	int error = 0;
	int i;

	for (i = 0; i < sll->sll_num; i++) {
		error = record_add(rc, "%s  -->  %s", 
				   sll->sll_name[i], sll->sll_target[i]);
		if (error)
			goto RecordFail;
	}
	return 0;

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

int record_children(struct si_action * a, struct record_col * rc)
{
	struct sysfs_object_list sol;
	int error;

	error = sysfs_list_children(&sol);
	if (error)
		return error;

	error = rc_init(rc, sol.sol_num);
	if (error)
		goto Unlist;

	error = record_sol(&sol, rc);
	if (error)
		rc_exit(rc);

	rc_add(a, rc);
 Unlist:
	sysfs_unlist_children(&sol);
	return error;
}

int record_attributes(struct si_action * a, struct record_col * rc)
{
	struct sysfs_attr_list sal;
	int error;

	error = sysfs_list_attr(&sal);
	if (error)
		return error;

	error = rc_init(rc, sal.sal_num);
	if (error)
		goto Unlist;

	error = record_sal(&sal, rc);
	if (error)
		rc_exit(rc);

	rc_add(a, rc);
 Unlist:
	sysfs_unlist_attr(&sal);
	return error;
}

int record_links(struct si_action * a, struct record_col * rc)
{
	struct sysfs_link_list sll;
	int error;

	error = sysfs_list_link(&sll);
	if (error)
		return error;

	error = rc_init(rc, sll.sll_num);
	if (error)
		goto Unlist;

	error = record_sll(&sll, rc);
	if (error)
		rc_exit(rc);
	rc_add(a, rc);
 Unlist:
	sysfs_unlist_link(&sll);
	return error;
}


int format_attributes(struct si_action * a, struct record_col * rc,
		      struct sysfs_attr_list * sal)
{
	int maxlen = 0;
	int error = 0;
	int i;

	/*
	 * Find the column to align on
	 */
	for (i = 0; i < sal->sal_num; i++) {
		int len = sal->sal_attr[i].a_name_len;
		if (len > maxlen)
			maxlen = len;
	}

	for (i = 0; i < sal->sal_num; i++) {
		struct sysfs_attr * a = &sal->sal_attr[i];

		if (!a->a_flag) {
			sysfs_chop_attr(a);
			error = record_add(rc, "%s%*c%s", 
					   a->a_name, 
					   30 - a->a_name_len, ' ', 
					   a->a_data);
			if (error)
				goto RecordFail;
		}
	}
	return 0;

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

int read_attributes(struct si_action * a, struct record_col * rc)
{
	struct sysfs_attr_list sal;
	int error;

	error = sysfs_list_attr(&sal);
	if (error)
		return error;

	error = rc_init(rc, sal.sal_num);
	if (error)
		goto Unlist;

	error = sysfs_read_attr(&sal);
	if (error)
		goto RcExit;

	error = format_attributes(a, rc, &sal);
	if (error)
		goto RcExit;

	rc_add(a, rc);
 Unlist:
	sysfs_unlist_attr(&sal);
	return error;

 RcExit:
	rc_exit(rc);
	goto Unlist;
	
}
