/*
 * ns_set support for mod_aolserver.
 * Copyright 2000, Robert S. Thau.
 */

#include <httpd.h>
#include <stdlib.h>
#include <tcl.h>
#include "nsd.h"

/*
 * This implementation makes an Ns_Set a wrapper around an Apache table.
 *
 * The table APIs are case-insensitive so we reimplement the lookup
 * functions here.
 *
 * Some of the sets we want to create are wrappers around existing
 * Apache tables (e.g. [ns_conn headers] and [ns_conn outputheaders]).
 * Others are standalone sets. We treat these differently because in the
 * case of standalone sets we want to try to be efficient about memory
 * usage, and you can't free storage piecemeal in the Apache memory
 * management model.
 *
 * The reason we want to be efficient is that every database row
 * returned via one handle is stored in the same set, overlaying the
 * previous contents of the set.  If we're not careful, we'll end up
 * allocating enough storage to store every database row ever selected
 * on each handle.
 *
 * So in the case of a standalone set, we allocate two subpools for
 * it. One stores the table struct and the keys; the other stores the
 * values. Ns_SetTrunc will clear both pools and reallocate the table
 * struct. Ns_SetClearValues (a new API for mod_aolserver) will just
 * clear the value pool and set all the table values to the empty
 * string.
 */

/* Amount of space for elements in a newly created set */

#define NEW_SET_ELTS 20

/* Possible sentinel values */

#define LIVE_SENTINEL 0x5c4e3a01 /* valid ns_set */
#define DEAD_SENTINEL 0xdeadbeef /* freed ns_set */

/* Ns_Set flag values */

/* Did we allocate our own table, or are we wrapping an existing one? */
#define FLAG_PRIVATE_TABLE 1

/* Should we clear all the set values on the next Ns_SetPutValue? */
#define FLAG_CLEAR_VALUES_ON_PUT_VALUE 2

/*--------------------------------------------------------------------*/

/* When dealing with sets in tcl, we need to encode them in strings
 * somehow.  Aolserver itself has a grubby scheme involving hash
 * tables.  We take the riskier approach of just using the address,
 * with a prefix that makes valid looking handles unlikely to arise by
 * accident.  Which of course raises the problem of doing integrity
 * checks --- we require that all valid sets have the LIVE_SENTINEL
 * above in their headers, and arrange (via Apache cleanups) that that
 * value is always overwritten when the set is invalidated by any
 * means.
 *
 * Handles are 'set.' followed by the ns_set's address in mock hex.
 * So we need to know how long the address is...
 */

#define HANDLE_LEN (4+sizeof(Ns_Set*)*2)

void ns_enter_set (Tcl_Interp *interp, Ns_Set *set_ptr)
{
    char handle[HANDLE_LEN+1];
    char *cp = handle;
    char *ptr_p = (char *)&set_ptr;
    int i;
    
    *cp++ = 's'; *cp++ = 'e'; *cp++ = 't'; *cp++ = '.';

    for (i = 0; i < sizeof(Ns_Set*); ++i) {
	char byte = *ptr_p++;
	*cp++ = '0' + ((byte >> 4) & 0xf);
	*cp++ = '0' + (byte & 0xf);
    }

    handle[HANDLE_LEN] = '\0';
    Tcl_AppendResult (interp, handle, NULL);
}

/* That's the code to create a handle.  Here's how you get a handle
 * out of them...
 */

Ns_Set *ns_unpack_handle (char *handle)
{
    Ns_Set *addr;
    char *bytep = handle + 4;
    char *addrp = (char *)&addr;
    int i;
    
    if (strlen (handle) != HANDLE_LEN
	|| handle[0] != 's' || handle[1] != 'e' || handle[2] != 't'
	|| handle[3] != '.')
    {
	return NULL;
    }

    for (i = 0; i < sizeof(Ns_Set*); ++i) {
	char hi_nybble = (*bytep++) - '0';
	char lo_nybble = (*bytep++) - '0';
	*addrp++ = ((hi_nybble << 4) | lo_nybble);
    }

    if (addr->sentinel != LIVE_SENTINEL) {
	return NULL;
    }

    return addr;
}

/*- support functions ------------------------------------------------*/

static void ns_set_cleanup (void *ptr) {

    /* This is really just a debugging aid. */
    ((Ns_Set *)ptr)->sentinel = DEAD_SENTINEL;
}

/* Pass a table to be wrapped in an Ns_Set, or NULL for a standalone Ns_Set. */
Ns_Set *ns_set_create_internal(char *name, table *t)
{
    Ns_Set *set;
    pool *outer_pool;
    pool *key_pool;
    pool *value_pool;
    int flags = 0;

    if (t != NULL) {
	outer_pool = ap_table_elts(t)->pool;
	key_pool = outer_pool;
	value_pool = key_pool;
    } 

    else {
	flags |= FLAG_PRIVATE_TABLE;
	outer_pool = TCL_POOL();
	key_pool = ap_make_sub_pool(outer_pool);
	value_pool = ap_make_sub_pool(outer_pool);
	t = ap_make_table(key_pool, NEW_SET_ELTS);
    }

    set = ap_pcalloc(outer_pool, sizeof(*set));
    set->name = ap_pstrdup(outer_pool, name);

    set->sentinel = LIVE_SENTINEL;
    set->flags = flags;
    set->key_pool = key_pool;
    set->value_pool = value_pool;
    set->real_table = t;

    ap_register_cleanup(outer_pool, (void*)set,
	ns_set_cleanup, ns_set_cleanup);

    return set;
}

/*- (most of the) AOLserver Ns_Set API -------------------------------*/

void Ns_SetUpdate (Ns_Set *set, char *key, char *value)
{
    Ns_SetDeleteKey(set, key);
    Ns_SetPut(set, key, value);
}

Ns_Set *Ns_SetCreate (char *name)
{
    return ns_set_create_internal(name, NULL);
}

void Ns_SetFree (Ns_Set *set)
{
    set->sentinel = DEAD_SENTINEL;
    if (set->flags & FLAG_PRIVATE_TABLE) {
	ap_destroy_pool(set->key_pool);
    }
}

int Ns_SetPut (Ns_Set *set, char *key, char *value)
{
    array_header *arr = ap_table_elts(set->real_table);
    table_entry *elts = (table_entry *) ap_push_array (arr);
    elts->key = ap_pstrdup (set->key_pool, key);
    elts->val = ap_pstrdup (set->value_pool, value);
    return arr->nelts - 1;
}

int Ns_SetUniqueCmp (Ns_Set *set, char *key,
		     int (*cmp) (const char*, const char*))
{
    int nelem = Ns_SetSize (set);
    int count = 0;
    int i;
    
    for (i = 0; i < nelem; ++i) {
	char *skey = Ns_SetKey (set, i);
	if ((!key && !skey) || (key && skey && !cmp (key, skey))) {
	    if (++count > 1)
		return 0;
	}
    }

    return 1;
}

int Ns_SetFindCmp (Ns_Set *set, char *key,
		   int (*cmp) (const char *, const char *))
{
    table *t = set->real_table;
    table_entry *elts = (table_entry *)ap_table_elts(t)->elts;
    int nelts = ap_table_elts(t)->nelts;
    int i;

    if (key == NULL)
	return -1;

    for (i = 0; i < nelts; ++i)
	if (!cmp(elts[i].key, key))
	    return i;

    return -1;
}

char *Ns_SetGetCmp (Ns_Set *set, char *key,
		  int (*cmp) (const char *, const char *))
{
    table *t = set->real_table;
    table_entry *elts = (table_entry *)ap_table_elts(t)->elts;
    int i = Ns_SetFindCmp(set, key, cmp);

    return (i < 0) ? NULL : elts[i].val;
}

int Ns_SetUnique (Ns_Set *s, char* k)
{
    return Ns_SetUniqueCmp (s, k, strcmp);
}

int Ns_SetIUnique (Ns_Set *s, char* k)
{
    return Ns_SetUniqueCmp (s, k, strcasecmp);
}

int Ns_SetFind (Ns_Set *set, char *key)
{
    return Ns_SetFindCmp (set, key, strcmp);
}

int Ns_SetIFind (Ns_Set *set, char *key)
{
    return Ns_SetFindCmp (set, key, strcasecmp);
}

char *Ns_SetGet (Ns_Set *set, char *key)
{
    return Ns_SetGetCmp(set, key, strcmp);
}

char *Ns_SetIGet (Ns_Set *set, char *key)
{
    return Ns_SetGetCmp(set, key, strcasecmp);
}

void Ns_SetTrunc (Ns_Set *set, int size)
{
    if (size >= Ns_SetSize(set)) {
	return;
    }

    else if (size == 0) {
	if (set->flags & FLAG_PRIVATE_TABLE) {
	    ap_clear_pool(set->key_pool);
	    ap_clear_pool(set->value_pool);
	    set->real_table = ap_make_table(set->key_pool, NEW_SET_ELTS);
	}

	else {
	    ap_clear_table(set->real_table);
	}
    }

    else {
	ap_table_elts(set->real_table)->nelts = size;
    }
}

/*
 * This function isn't part of the AOLserver API but we need it to
 * control memory consumption of database selects.
 */
void Ns_SetClearValues (Ns_Set *set)
{
    int i;

    for (i = 0; i < Ns_SetSize(set); i++) {
	ns_set_elts(set)[i].val = "";
    }

    if (set->flags & FLAG_PRIVATE_TABLE) {
	ap_clear_pool(set->value_pool);
    }
}

void Ns_SetClearValuesOnPutValue(Ns_Set *set)
{
    set->flags |= FLAG_CLEAR_VALUES_ON_PUT_VALUE;
}

void Ns_SetDelete (Ns_Set *set, int index)
{
    table *t = set->real_table;
    int nelts = ap_table_elts(t)->nelts;

    if (index < 0 || index >= nelts) {
	return;
    }

    else {
	table_entry *elts = (table_entry *) ap_table_elts(t)->elts;
	memmove (&elts[index], &elts[index + 1],
	    (nelts - index - 1)*sizeof(table_entry));
	--ap_table_elts(t)->nelts;
    }
}

void Ns_SetPutValue (Ns_Set *set, int index, char *value)
{
    if (set->flags & FLAG_CLEAR_VALUES_ON_PUT_VALUE) {
	Ns_SetClearValues(set);
	set->flags &= ~FLAG_CLEAR_VALUES_ON_PUT_VALUE;
    }

    if (index >= 0 && index < Ns_SetSize (set))
	ns_set_elts(set)[index].val =
	    ap_pstrdup (ns_set_table (set)->pool, value);
}

void Ns_SetDeleteKey (Ns_Set *set, char *key)
{
    Ns_SetDelete(set, Ns_SetFind(set, key));
}

void Ns_SetIDeleteKey (Ns_Set *set, char *key)
{
    Ns_SetDelete(set, Ns_SetIFind(set, key));
}

void Ns_SetMerge (Ns_Set *high, Ns_Set *low)
{
    int i;
    int nlow = Ns_SetSize (low);

    for (i = 0; i < nlow; ++i)
        if (Ns_SetFind(high, Ns_SetKey(low, i)) < 0)
            Ns_SetPut(high, Ns_SetKey(low, i), Ns_SetValue(low, i));
}

Ns_Set *Ns_SetCopy (Ns_Set *old)
{
    int i;
    Ns_Set *new;

    if (old == NULL)
	return NULL;

    new = Ns_SetCreate(old->name);
    for (i = 0; i < Ns_SetSize(old); i++) {
	Ns_SetPut(new, Ns_SetKey(old, i), Ns_SetValue(old, i));
    }

    return new;
}

void Ns_SetMove (Ns_Set *to, Ns_Set *from)
{
    int i;

    for (i = 0; i < Ns_SetSize(from); i++) {
	Ns_SetPut(to, Ns_SetKey(from, i), Ns_SetValue(from, i));
    }

    Ns_SetTrunc(from, 0);
}

void Ns_SetPrint (Ns_Set *set)
{
    int i;
    
    if (set->name) fprintf (stderr, "%s\n", set->name);
    else fputs ("<Unnamed set>\n", stderr);

    for (i = 0; i < Ns_SetSize(set); ++i) {
	char *key = Ns_SetKey(set, i)? Ns_SetKey(set, i) : "(null)";
	char *val = Ns_SetValue(set, i)? Ns_SetValue(set, i) : "(null)";
	fprintf (stderr, "\t%s = %s\n", key, val);
    }
}