#include "config.h"
#include <cstring>
#include <cstdlib>
#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 otherwise.
 */
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[80];
    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 should contain and \ sequences 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;
            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));
}
