#include <config.h>
#ifndef _plan9_
#include <string.h>
#ifndef NO_MALLOC_H
#include <malloc.h>
#endif
#include <string.h>
#else
#include <u.h>
#include <libc.h>
#endif
#include <catalog.h>
#include <misc-f.h>
/* Well, just simple implementation of unbalanced trees in combination
 * of small hash table. I am lazy :) but should be OK for my purposes
 *
 * This function is used for both-lookups and adds into table (since most of
 * code is the same, depends whether newvalue is NULL. If newvalue is nonNULL,
 * new variable is added into table, if name is not present here or value is
 * changed othewise.
 */
static char *find_variable(catalog_t * context, CONST char *name,
			   CONST char *newvalue)
{
    int r = 0;
    int hash = (int) strlen(name);
    struct varnames *current, *last, *newp;
    hash =
	((unsigned char) (name[0]) + (unsigned char) (name[hash - 1]) +
	 hash) % (unsigned int) CHASHMAX;
    current = last = context->root[hash];
    while (current != NULL) {
	last = current;
	r = strcmp(current->name, name);
	if (!r) {
	    if (newvalue != NULL) {	/*overwrite value */
		free(current->value);
		current->value = mystrdup(newvalue);
	    }
	    return (current->value);
	}
	if (r > 0)
	    current = current->left;
	else
	    current = current->right;
    }
    /*Entry is new */
    if (newvalue == NULL)
	return (NULL);
    newp = (struct varnames *) calloc(1, sizeof(struct varnames));
    newp->name = mystrdup(name);
    newp->value = mystrdup(newvalue);
    /*FIXME. Should take a care to full memory */
    newp->left = NULL;
    newp->right = NULL;
    if (last == NULL) {
	context->root[hash] = newp;
    } else {
	if (r > 0)
	    last->left = newp;
	else
	    last->right = newp;
    }
    return (newp->value);
}

/*
 * free memory used by node and its sons
 */
static void free_node(struct varnames *node)
{
    while (node != NULL) {
	struct varnames *nextnode;
	free_node(node->left);
	nextnode = node->right;
	free(node->name);
	free(node->value);
	free(node);
	node = nextnode;
    }
}

/*
 * free catalog
 */
void free_catalog(catalog_t * context)
{
    int i;
    for (i = 0; i < CHASHMAX; i++) {
	free_node(context->root[i]);
	context->root[i] = NULL;
    }
    free(context);
}

static catalog_t *alloc_catalog(void)
{
    int i;
    catalog_t *c;
    c = (catalog_t *) calloc(1, sizeof(catalog_t));
    if (c == NULL)
	return NULL;
    for (i = 0; i < CHASHMAX; i++)
	c->root[i] = NULL;
    return c;
}

/*
 * Parse an catalog file and save values into memory
 */
// FIXME: this macro gives a segfault if \" is used in text. kovzol, 2009-06-29
#define seterror(text) sprintf(errort,"line %i:%s",line,text),*error=errort
catalog_t *load_catalog(xio_file f, CONST char **error)
{
    int i;
    int line = 1;
    int size;
    int c;
    catalog_t *catalog = alloc_catalog();
    static char errort[40];
    char name[1024];
    char value[1024];
    if (catalog == NULL) {
	*error = "Out of memory";
    }
    if (f == NULL) {
	*error = "File could not be opened";
	free_catalog(catalog);
	return NULL;
    }
    /* Just very simple parsing loop of format 
     * [blanks]name[blanks]"value"[blanks]
     * Blanks should be comments using # or space, newline, \r and tabulator
     * Value shoud contain and \ seqences where \\ means \ and
     * \[something] means something. Should be used for character "
     */
    while (!xio_feof(f)) {

	do {
	    c = xio_getc(f);
	    if (c == '\n')
		line++;
	    if (c == '#') {
		while ((c = xio_getc(f)) != '\n' && c != XIO_EOF);
		line++;
	    }
	}
	while (c == ' ' || c == '\n' || c == '\r' || c == '\t');

	/*Skip blanks */
	if (c == XIO_EOF) {
	    if (xio_feof(f))
		break;
	    free_catalog(catalog);
	    seterror("read error");
	    xio_close(f);
	    return NULL;
	}
	i = 0;

	/*read name */
	do {
	    name[i] = c;
	    i++;
	    c = xio_getc(f);
	    if (c == '\n')
		line++;
	    if (i == 1024) {
		seterror("Name is too long (>=1024 characters)");
		free_catalog(catalog);
		xio_close(f);
		return NULL;
	    }
	}
	
	while (c != '\n' && c != ' ' && c != '\t' && c != XIO_EOF);

	/*Skip blanks */
	while (c == ' ' || c == '\n' || c == '\r' || c == '\t') {
	    c = xio_getc(f);
	    if (c == '\n')
		line++;
	    if (c == '#') {
		while ((c = xio_getc(f)) != '\n' && c != XIO_EOF);
		line++;
	    }
	}

	/*Skip blanks */
	if (c == XIO_EOF) {
	    if (xio_feof(f))
		seterror("Unexpected end of file after name field");
	    else
		seterror("read error");
	    free_catalog(catalog);
	    xio_close(f);
	    return NULL;
	}
	
	name[i] = 0;
	if (c != '"') {
	    seterror("Begin of value field expected"); // Text modified due to segfault problem, see above (kovzol)
	    free_catalog(catalog);
	    xio_close(f);
	    return NULL;
	}
	c = xio_getc(f);
	if (c == '\n')
	    line++;
	i = 0;

	size = 0;
	do {
	    if (c == '\\')
		value[i] = xio_getc(f);
	    else
		value[i] = c;
	    i++;
	    c = xio_getc(f);
	    if (c == '\n')
		line++, size = 0;
	    if (size == 40 && c != '"') {
		fprintf(stderr, "Warning - too long text at line %i\n",
			line);
	    }
	    size++;
	    if (i == 1024) {
		seterror("Value is too long (>=1024 characters)");
		free_catalog(catalog);
		xio_close(f);
		return NULL;
	    }
	}
	while (c != '"' && c != XIO_EOF);

	if (c == XIO_EOF) {
	    seterror("Unexpected EOF in value field");
	    free_catalog(catalog);
	    xio_close(f);
	    return NULL;
	}
	
	value[i] = 0;
	find_variable(catalog, name, value);
    }				/*while */
    xio_close(f);
    return (catalog);
}				/*load_catalog */

char *find_text(catalog_t * catalog, CONST char *name)
{
    return (find_variable(catalog, name, NULL));
}
