/* list.c: Linked list handling code.
 *
 * $Id: list.c,v 1.6 2003/02/07 08:21:20 andy Exp $
 *
 * Copyright (C) 2002 Andy Goth <unununium@openverse.com>
 * For more information visit http://ioioio.net/devel/install-log/
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA  02111-1307, USA. */

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "install-log.h"

static list_t* make_node(void* data);
static void free_node(list_t* node);
static void fprintf_node(list_t* node, va_list ap);

/* Creates a node containing 'data' and advance *'node' to point to it. */
void add_node(list_t** node, void* data)
{
	list_t* next = make_node(data);
	(*node)->next = next;
	*node = next;
}

/* Creates a node containing 'string' and advance *'node' to point to it. */
void add_string_node(list_t** node, const char* string)
{
	add_node(node, strdup(string));
}

/* Adds a new node containing 'string' to 'list', using an insertion sort to
 * determine its position. */
void insert_string_node(list_t* list, const char* string)
{
	list_t* node = list;
	list_t* new_node = make_node(strdup(string));
	
	/* Scan for proper position */
	for (; node->next != NULL; node = node->next)
		if (strcmp((char*)node->next->data, string) > 0) break;

	/* Insert! */
	new_node->next = node->next;
	node->next = new_node;
}

/* Overwrites 'list' with a linked list of strings created from 'string', a
 * NULL-terminated array of char*'s. */
void make_string_list(list_t* list, const char** string)
{
	list_t* node;
	clear_list(list);
	node = list;
	for (; *string != NULL; string++)
		add_string_node(&node, *string);
}

/* Calls 'func' on each node in 'list'. */
void proc_list(list_t* list, void (*func)(list_t*))
{
	list = list->next;
	while (list != NULL) {
		list_t* next = list->next;
		func(list);
		list = next;
	}
}

/* Calls 'func' on each node in 'list', passing a va_list to 'func'. */
void proc_list_va(list_t* list, void (*func)(list_t*, va_list), ...)
{
	va_list ap;
	list = list->next;
	while (list != NULL) {
		list_t* next = list->next;
		va_start(ap, func);
		func(list, ap);
		va_end(ap);
		list = next;
	}
}

/* fprintfs to 'file' every element of 'list' using a format string.  Use %!
 * instead of plain % to refer to node contents.  Example: %!s to print the
 * node's data as a string. */
void fprintf_list(FILE* file, list_t* list, char* fmt, ...)
{
	va_list ap;
	va_start(ap, fmt);
	proc_list_va(list, fprintf_node, file, fmt, ap);
	va_end(ap);
}

/* Removes all but the head node in 'list'. */
void clear_list(list_t* list)
{
	proc_list(list, free_node);
	list->next = NULL;
}

/* Checks to see if 'list' contains 'string'. */
bool list_has_string(const list_t* list, const char* string)
{
	while ((list = list->next) != NULL)
		if (strcmp((char*)list->data, string) == 0) return 1;
	return 0;
}

/* Creates and returns a new, isolated node containing 'data'. */
static list_t* make_node(void* data)
{
	list_t* node = xmalloc(sizeof(list_t));
	node->data = data;
	node->next = NULL;
	return node;
}

/* Frees 'node', deallocating the data it contains. */
static void free_node(list_t* node)
{
	free(node->data);
	free(node);
}

/* Prints 'node', using 'ap' as a formatting guide. */
static void fprintf_node(list_t* node, va_list ap)
{
	FILE* file = va_arg(ap, FILE*);
	char* fmt = va_arg(ap, char*);
	va_list ap2 = va_arg(ap, va_list);
	
	/* An individual %! token */
	int token_cap = 8;
	int token_len = 0;
	char* token = xmalloc(token_cap);
	/* The expanded version of one %! token */
	int exp_token_cap = 8;
	int exp_token_len = 0;
	char* exp_token = xmalloc(exp_token_cap);
	/* The format string with all %!'s expanded */
	char* fmt2 = strdup(fmt);
	int fmt2_len = strlen(fmt2);
	int fmt2_cap = strlen(fmt2);
	char* fmt2_p = fmt2;

	/* Expand all %! tokens */
	while ((fmt2_p = strstr(fmt2_p, "%!")) != NULL) {
		/* Extract %! token */
		token_len = strcspn(fmt2_p + 2, "diouxXeEfFgGaAcsCSpn%") + 3;
		if (token_len > token_cap) {
			token_cap = token_len + 8;
			token = xrealloc(token, token_cap);
		}
		strcpy(token, "%");
		strncpy(token + 1, fmt2_p + 2, token_len - 2);
		token[token_len - 1] = 0;

		/* Expand token and escape all % characters */
		safe_sprintf(&exp_token, &exp_token_cap, token, node->data);
		replace(&exp_token, &exp_token_cap, "%", "%%");
		exp_token_len = strlen(exp_token);

		/* Replace %! token with exp_token */
		fmt2_len += exp_token_len - token_len;
		if (fmt2_len > fmt2_cap) {
			int fmt2_idx = fmt2_p - fmt2;
			fmt2_cap = fmt2_len + 8;
			fmt2 = xrealloc(fmt2, fmt2_cap);
			fmt2_p = fmt2 + fmt2_idx;
		}
		memmove(fmt2_p + exp_token_len, fmt2_p + token_len,
				strlen(fmt2_p + token_len) + 1);
		memcpy(fmt2_p, exp_token, exp_token_len);
		fmt2_p += exp_token_len;
	}

	/* Finally, print */
	vfprintf(file, fmt2, ap2);

	free(token);
	free(exp_token);
	free(fmt2);
}

/* EOF */

