/* strtab.c - simple unordered write-once string table package
**
** The internal data structure is a hashtable of unsorted lists.
**
** Adding the same string to a table twice with different data does work,
** in that the second data overrides the first, because the second version
** goes to the same hash bucket as the first and then comes before it in
** the list.  The first version is still there, but inaccessible.
**
** There is currently no provision for removing items from a table.
**
** Copyright © 1995,2000 by Jef Poskanzer <jef@mail.acme.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``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 OR CONTRIBUTORS 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 <stdlib.h>
#include <string.h>

#include "strtab.h"

#define HASH 137

struct _strtab {
    int num_elements[HASH], max_elements[HASH];
    struct _element* elements[HASH];
    };
typedef struct _strtab* real_strtab;

struct _element {
    char* name;
    int name_size;
    char* value;
    int value_size;
    };
typedef struct _element element;



static int
hash( const char* str )
    {
    int h;

    for ( h = 0; *str != '\0'; ++str )
	h = h * 17 + ( (int) *str & 0xff );
    h = h % HASH;
    if ( h < 0 )
	h += HASH;
    return h;
    }


strtab
strtab_new( void )
    {
    real_strtab rs;
    int h;

    rs = (real_strtab) malloc( sizeof(struct _strtab) );
    if ( rs != (real_strtab) 0 )
        {
	for ( h = 0; h < HASH; ++h )
	    {
	    rs->num_elements[h] = 0;
	    rs->max_elements[h] = 0;
	    rs->elements[h] = (element*) 0;
	    }
        }
    return rs;
    }


void
strtab_clear( strtab s )
    {
    real_strtab rs = (real_strtab) s;
    int h;

    for ( h = 0; h < HASH; ++h )
	rs->num_elements[h] = 0;
    }


void
strtab_delete( strtab s )
    {
    real_strtab rs = (real_strtab) s;
    int h, i;

    strtab_clear( rs );
    for ( h = 0; h < HASH; ++h )
	if ( rs->max_elements[h] > 0 )
	    {
	    for ( i = 0; i < rs->max_elements[h]; ++i )
		{
		if ( rs->elements[h][i].name != (char*) 0 )
		    free( (void*) rs->elements[h][i].name );
		if ( rs->elements[h][i].value != (char*) 0 )
		    free( (void*) rs->elements[h][i].value );
		}
	    free( (void*) rs->elements[h] );
	    }
    free( (void*) rs );
    }


int
strtab_add( strtab s, const char* name, const char* value )
    {
    real_strtab rs = (real_strtab) s;
    int h = hash( name );
    int len;

    if ( rs->num_elements[h] >= rs->max_elements[h] )
	{
	int new_max, i;
	if ( rs->max_elements[h] == 0 )
	    {
	    new_max = 20;
	    rs->elements[h] = (element*) malloc( new_max * sizeof(element) );
	    }
	else
	    {
	    new_max = rs->max_elements[h] * 2;
	    rs->elements[h] = (element*) realloc(
		(void*) rs->elements[h], new_max * sizeof(element) );
	    }
	if ( rs->elements[h] == (element*) 0 )
	    return 0;
	for ( i = rs->max_elements[h]; i < new_max; ++i )
	    {
	    rs->elements[h][i].name = (char*) 0;
	    rs->elements[h][i].name_size = 0;
	    rs->elements[h][i].value = (char*) 0;
	    rs->elements[h][i].value_size = 0;
	    }
	rs->max_elements[h] = new_max;
	}
    len = strlen( name );
    if ( len + 1 >= rs->elements[h][rs->num_elements[h]].name_size )
	{
	rs->elements[h][rs->num_elements[h]].name_size = ( len + 1 ) * 2;
	if ( rs->elements[h][rs->num_elements[h]].name == (char*) 0 )
	    rs->elements[h][rs->num_elements[h]].name = (char*) malloc(
		rs->elements[h][rs->num_elements[h]].name_size );
	else
	    rs->elements[h][rs->num_elements[h]].name = (char*) realloc(
		(void*) rs->elements[h][rs->num_elements[h]].name,
		rs->elements[h][rs->num_elements[h]].name_size );
	if ( rs->elements[h][rs->num_elements[h]].name == (char*) 0 )
	    return 0;
	}
    (void) strcpy( rs->elements[h][rs->num_elements[h]].name, name );
    len = strlen( value );
    if ( len + 1 >= rs->elements[h][rs->num_elements[h]].value_size )
	{
	rs->elements[h][rs->num_elements[h]].value_size = ( len + 1 ) * 2;
	if ( rs->elements[h][rs->num_elements[h]].value == (char*) 0 )
	    rs->elements[h][rs->num_elements[h]].value = (char*) malloc(
		rs->elements[h][rs->num_elements[h]].value_size );
	else
	    rs->elements[h][rs->num_elements[h]].value = (char*) realloc(
		(void*) rs->elements[h][rs->num_elements[h]].value,
		rs->elements[h][rs->num_elements[h]].value_size );
	if ( rs->elements[h][rs->num_elements[h]].value == (char*) 0 )
	    return 0;
	}
    (void) strcpy( rs->elements[h][rs->num_elements[h]].value, value );
    ++rs->num_elements[h];
    return 1;
    }


int
strtab_includes( strtab s, const char* name )
    {
    real_strtab rs = (real_strtab) s;
    int h = hash( name );
    int i;

    for ( i = 0; i < rs->num_elements[h]; ++i )
        if ( strcmp( name, rs->elements[h][i].name ) == 0 )
            return 1;
    return 0;
    }


const char*
strtab_find( strtab s, const char* name )
    {
    real_strtab rs = (real_strtab) s;
    int h = hash( name );
    int i;

    for ( i = 0; i < rs->num_elements[h]; ++i )
        if ( strcmp( name, rs->elements[h][i].name ) == 0 )
            return rs->elements[h][i].value;
    return (const char*) 0;
    }


int
strtab_num_elements( strtab s )
    {
    real_strtab rs = (real_strtab) s;
    int h, sum;

    sum = 0;
    for ( h = 0; h < HASH; ++h )
	sum += rs->num_elements[h];

    return sum;
    }
