#include "mod_perl.h"

typedef struct {
    SV *cv;
    table *only;
} TableDo;

#define table_pool(t) ((array_header *)(t))->pool

static int Apache_table_do(TableDo *td, const char *key, const char *val)
{
    int count=0, rv=1;
    dSP;

    if(td->only && !table_get(td->only, key))
       return 1;

    ENTER;SAVETMPS;
    PUSHMARK(sp);
    XPUSHs(sv_2mortal(newSVpv((char *)key,0)));
    XPUSHs(sv_2mortal(newSVpv((char *)val,0)));
    PUTBACK;
    count = perl_call_sv(td->cv, G_SCALAR);
    SPAGAIN;
    if(count == 1)
	rv = POPi;
    PUTBACK;
    FREETMPS;LEAVE;
    return rv;
}

typedef void (
#ifdef WIN32
      _stdcall 
#endif
      *TABFUNC) (table *, const char *, const char *);

static void table_modify(TiedTable *self, const char *key, SV *sv, 
			 TABFUNC tabfunc)
{
    dTHR;
    const char *val;

    if(!self->utable) return;

    if(SvROK(sv) && (SvTYPE(SvRV(sv)) == SVt_PVAV)) {
	I32 i;
	AV *av = (AV*)SvRV(sv);
	for(i=0; i<=AvFILL(av); i++) {
	    val = (const char *)SvPV(*av_fetch(av, i, FALSE),na);
            (*tabfunc)(self->utable, key, val);
	}
    }
    else {
        val = (const char *)SvPV(sv,na);
	(*tabfunc)(self->utable, key, val);
    }

}

static void
#ifdef WIN32
_stdcall 
#endif
table_delete(table *tab, const char *key, const char *val)
{
    table_unset(tab, val);
}

static Apache__Table ApacheTable_new(table *utable)
{
    Apache__Table RETVAL = (Apache__Table)safemalloc(sizeof(TiedTable));
    RETVAL->utable = utable;
    RETVAL->ix = 0;
    RETVAL->elts = NULL;
    RETVAL->arr = NULL;
    return RETVAL;
}

MODULE = Apache::Table		PACKAGE = Apache::Table

PROTOTYPES: DISABLE

BOOT:
    items = items; /*avoid warning*/ 

Apache::Table
TIEHASH(pclass, table)
    SV *pclass
    Apache::table table

    CODE:
    if(!pclass) XSRETURN_UNDEF;
    RETVAL = ApacheTable_new(table);

    OUTPUT:
    RETVAL

void
new(pclass, r, nalloc=10)
    SV *pclass
    Apache r
    int nalloc

    CODE:
    if(!pclass) XSRETURN_UNDEF;
    ST(0) = mod_perl_tie_table(make_table(r->pool, nalloc));

void
DESTROY(self)
    SV *self

    PREINIT:
    Apache__Table tab;

    CODE:
    tab = (Apache__Table)hvrv2table(self);
    if(SvROK(self) && SvTYPE(SvRV(self)) == SVt_PVHV) 
        safefree(tab);

void
FETCH(self, key)
    Apache::Table self
    const char *key

    ALIAS:
    get = 1

    PPCODE:
    ix = ix; /*avoid warning*/
    if(!self->utable) XSRETURN_UNDEF;
    if(GIMME == G_SCALAR) {
	const char *val = table_get(self->utable, key);
	if (val) XPUSHs(sv_2mortal(newSVpv((char*)val,0)));
	else XSRETURN_UNDEF;
    }
    else {
	int i;
	array_header *arr  = table_elts(self->utable);
	table_entry *elts = (table_entry *)arr->elts;
	for (i = 0; i < arr->nelts; ++i) {
	    if (!elts[i].key || strcasecmp(elts[i].key, key)) continue;
	    XPUSHs(sv_2mortal(newSVpv(elts[i].val,0)));
	}
    }

bool
EXISTS(self, key)
    Apache::Table self
    const char *key

    CODE:
    if(!self->utable) XSRETURN_UNDEF;
    RETVAL = table_get(self->utable, key) ? TRUE : FALSE;

    OUTPUT:
    RETVAL

const char*
DELETE(self, sv)
    Apache::Table self
    SV *sv

    ALIAS:
    unset = 1

    PREINIT:
    I32 gimme = GIMME_V;

    CODE:
    ix = ix;
    if(!self->utable) XSRETURN_UNDEF;
    RETVAL = NULL;
    if((ix == 0) && (gimme != G_VOID)) {
        STRLEN n_a;
        RETVAL = table_get(self->utable, SvPV(sv,n_a));
    }

    table_modify(self, NULL, sv, (TABFUNC)table_delete);
    if(!RETVAL) XSRETURN_UNDEF;

    OUTPUT:
    RETVAL

void
STORE(self, key, val)
    Apache::Table self
    const char *key
    const char *val

    ALIAS:
    set = 1

    CODE:
    ix = ix; /*avoid warning*/
    if(!self->utable) XSRETURN_UNDEF;
    table_set(self->utable, key, val);

void
CLEAR(self)
    Apache::Table self

    ALIAS:
    clear = 1

    CODE:
    ix = ix; /*avoid warning*/
    if(!self->utable) XSRETURN_UNDEF;
    clear_table(self->utable);

const char *
NEXTKEY(self, lastkey=Nullsv)
    Apache::Table self
    SV *lastkey

    CODE:
    if(self->ix >= self->arr->nelts) XSRETURN_UNDEF;
    RETVAL = self->elts[self->ix++].key;

    OUTPUT:
    RETVAL

const char *
FIRSTKEY(self)
    Apache::Table self

    CODE:
    if(!self->utable) XSRETURN_UNDEF;
    self->arr = table_elts(self->utable);
    if(!self->arr->nelts) XSRETURN_UNDEF;
    self->elts = (table_entry *)self->arr->elts;
    self->ix = 0;
    RETVAL = self->elts[self->ix++].key;

    OUTPUT:
    RETVAL

void
add(self, key, sv)
    Apache::Table self
    const char *key
    SV *sv;

    CODE:
    table_modify(self, key, sv, (TABFUNC)table_add);

void
merge(self, key, sv)
    Apache::Table self
    const char *key
    SV *sv

    CODE:
    table_modify(self, key, sv, (TABFUNC)table_merge);

void
do(self, cv, ...)
    Apache::Table self
    SV *cv

    PREINIT:
    TableDo td;
    td.only = (table *)NULL;

    CODE:
    if(items > 2) {
	int i;
	STRLEN len;
        td.only = make_table(table_pool(self->utable), items-2);
	for(i=2; ; i++) {
	    char *key = SvPV(ST(i),len);
	    table_set(td.only, key, "1");
	    if(i == (items - 1)) break; 
	}
    }
    td.cv = cv;

    table_do((int (*) (void *, const char *, const char *)) Apache_table_do,
	    (void *) &td, self->utable, NULL);