/*
 * atheme-services: A collection of minimalist IRC services
 * table.c: Table rendering class.
 *
 * NOTE: This is a work in progress and will probably change considerably
 * later on.
 *
 * Copyright (c) 2005-2007 Atheme Project (http://www.atheme.org)
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "atheme.h"

static void table_destroy(void *obj)
{
	table_t *table = (table_t *) obj;
	mowgli_node_t *n, *tn;

	return_if_fail(table != NULL);

	MOWGLI_ITER_FOREACH_SAFE(n, tn, table->rows.head)
	{
		table_row_t *r = (table_row_t *) n->data;
		mowgli_node_t *n2, *tn2;

		return_if_fail(r != NULL);

		MOWGLI_ITER_FOREACH_SAFE(n2, tn2, r->cells.head)
		{
			table_cell_t *c = (table_cell_t *) n2->data;

			free(c->name);
			free(c->value);
			free(c);
			mowgli_node_delete(n2, &r->cells);
			mowgli_node_free(n2);
		}

		free(r);

		mowgli_node_delete(n, &table->rows);
		mowgli_node_free(n);
	}

	metadata_delete_all(table);

	free(table);
}

/*
 * table_new(const char *fmt, ...)
 *
 * Table constructor.
 *
 * Inputs:
 *     - printf-style string to name the table with.
 *
 * Outputs:
 *     - a table object.
 *
 * Side Effects:
 *     - none
 */
table_t *table_new(const char *fmt, ...)
{
	va_list vl;
	char buf[BUFSIZE];
	table_t *out;

	return_val_if_fail(fmt != NULL, NULL);

	va_start(vl, fmt);
	if (vsnprintf(buf, sizeof buf, fmt, vl) < 0)
	{
		va_end(vl);
		return NULL;
	}
	va_end(vl);

	out = scalloc(sizeof(table_t), 1);
	object_init(&out->parent, buf, table_destroy);

	return out;
}

/*
 * table_row_new
 *
 * Creates a table row. This isn't an object.
 *
 * Inputs:
 *     - table to associate to.
 *
 * Outputs:
 *     - a table row
 *
 * Side Effects:
 *     - none
 */
table_row_t *table_row_new(table_t *t)
{
	table_row_t *out;

	return_val_if_fail(t != NULL, NULL);

	out = scalloc(sizeof(table_row_t), 1);

	mowgli_node_add(out, mowgli_node_create(), &t->rows);

	return out;
}

/*
 * table_cell_associate
 *
 * Associates a cell with a row.
 *
 * Inputs:
 *     - row to add cell to.
 *
 * Outputs:
 *     - nothing
 *
 * Side Effects:
 *     - none
 */
void table_cell_associate(table_row_t *r, const char *name, const char *value)
{
	table_cell_t *c;

	return_if_fail(r != NULL);
	return_if_fail(name != NULL);
	return_if_fail(value != NULL);

	c = scalloc(sizeof(table_cell_t), 1);

	c->name = sstrdup(name);
	c->value = sstrdup(value);

	mowgli_node_add(c, mowgli_node_create(), &r->cells);
}

/*
 * table_render
 *
 * Renders a table. This is a two-pass operation, the first one to find out
 * the width of each cell, and then the second to render the data.
 *
 * Inputs:
 *     - a table
 *     - a callback function
 *     - opaque data
 *
 * Outputs:
 *     - none
 *
 * Side Effects:
 *     - a callback function is called for each row of the table.
 */
void table_render(table_t *t, void (*callback)(const char *line, void *data), void *data)
{
	mowgli_node_t *n;
	table_row_t *f;
	size_t bufsz = 0;
	char *buf = NULL;
	char *p;
	int i;

	return_if_fail(t != NULL);
	return_if_fail(callback != NULL);

	f = (table_row_t *) t->rows.head->data;

	MOWGLI_ITER_FOREACH(n, t->rows.head)
	{
		table_row_t *r = (table_row_t *) n->data;
		mowgli_node_t *n2, *rn;

		/* we, uhh... we don't provide a macro for dealing with two lists at once ;) */
		for (n2 = r->cells.head, rn = f->cells.head;
		     n2 != NULL && rn != NULL; n2 = n2->next, rn = rn->next)
		{
			table_cell_t *c, *rc;
			size_t sz;

			c  = (table_cell_t *) n2->data;
			rc = (table_cell_t *) rn->data;

			if ((sz = strlen(c->name)) > (size_t)rc->width)
				rc->width = sz;

			if ((sz = strlen(c->value)) > (size_t)rc->width)
				rc->width = sz;		
		}
	}

	/* now total up the result. */
	MOWGLI_ITER_FOREACH(n, f->cells.head)
	{
		table_cell_t *c = (table_cell_t *) n->data;
		bufsz += c->width + 1;
	}

	buf = smalloc(bufsz);
	*buf = '\0';

	/* start outputting the header. */
	callback("", data);
	MOWGLI_ITER_FOREACH(n, f->cells.head)
	{
		table_cell_t *c = (table_cell_t *) n->data;
		char buf2[1024];

		snprintf(buf2, 1024, "%-*s", n->next != NULL ? c->width + 1 : 0, c->name);
		mowgli_strlcat(buf, buf2, bufsz);
	}
	callback(buf, data);
	*buf = '\0';

	/* separator line */
	p = buf;
	MOWGLI_ITER_FOREACH(n, f->cells.head)
	{
		table_cell_t *c = (table_cell_t *) n->data;

		if (n->next != NULL)
		{
			for (i = 0; i < c->width; i++)
				*p++ = '-';
			*p++ = ' ';
		}
		else
			for (i = 0; i < (int)strlen(c->name); i++)
				*p++ = '-';
	}
	*p = '\0';
	callback(buf, data);
	*buf = '\0';

	MOWGLI_ITER_FOREACH(n, t->rows.head)
	{
		table_row_t *r = (table_row_t *) n->data;
		mowgli_node_t *n2, *rn;

		for (n2 = r->cells.head, rn = f->cells.head;
		     n2 != NULL && rn != NULL; n2 = n2->next, rn = rn->next)
		{
			table_cell_t *c, *rc;
			char buf2[1024];

			c  = (table_cell_t *) n2->data;
			rc = (table_cell_t *) rn->data;

			snprintf(buf2, 1024, "%-*s", n2->next != NULL ? rc->width + 1 : 0, c->value);
			mowgli_strlcat(buf, buf2, bufsz);
		}
		callback(buf, data);
		*buf = '\0';
	}

	/* separator line */
	p = buf;
	MOWGLI_ITER_FOREACH(n, f->cells.head)
	{
		table_cell_t *c = (table_cell_t *) n->data;

		if (n->next != NULL)
		{
			for (i = 0; i < c->width; i++)
				*p++ = '-';
			*p++ = ' ';
		}
		else
			for (i = 0; i < (int)strlen(c->name); i++)
				*p++ = '-';
	}
	*p = '\0';
	callback(buf, data);
	*buf = '\0';
}

/* vim:cinoptions=>s,e0,n0,f0,{0,}0,^0,=s,ps,t0,c3,+s,(2s,us,)20,*30,gs,hs
 * vim:ts=8
 * vim:sw=8
 * vim:noexpandtab
 */

