/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#define mpxs_APR__Table_STORE   apr_table_set
#define mpxs_APR__Table_DELETE  apr_table_unset
#define mpxs_APR__Table_CLEAR   apr_table_clear

#define MPXS_DO_TABLE_N_MAGIC_RETURN(call)                              \
    apr_pool_t *p = mp_xs_sv2_APR__Pool(p_sv);                          \
    apr_table_t *t = call;                                              \
    SV *t_sv = modperl_hash_tie(aTHX_ "APR::Table", Nullsv, t);         \
    mpxs_add_pool_magic(t_sv, p_sv);                                    \
    return t_sv;

static MP_INLINE SV *mpxs_APR__Table_make(pTHX_ SV *p_sv, int nelts)
{
    MPXS_DO_TABLE_N_MAGIC_RETURN(apr_table_make(p, nelts));
}


static MP_INLINE SV *mpxs_APR__Table_copy(pTHX_ apr_table_t *base, SV *p_sv)
{
    MPXS_DO_TABLE_N_MAGIC_RETURN(apr_table_copy(p, base));
}

static MP_INLINE SV *mpxs_APR__Table_overlay(pTHX_ apr_table_t *base,
                                             apr_table_t *overlay, SV *p_sv)
{
    MPXS_DO_TABLE_N_MAGIC_RETURN(apr_table_overlay(p, overlay, base));
}


typedef struct {
    SV *cv;
    apr_hash_t *filter;
    PerlInterpreter *perl;
} mpxs_table_do_cb_data_t;

typedef int (*mpxs_apr_table_do_cb_t)(void *, const char *, const char *);

static int mpxs_apr_table_do_cb(void *data,
                                const char *key, const char *val)
{
    mpxs_table_do_cb_data_t *tdata = (mpxs_table_do_cb_data_t *)data;
    dTHXa(tdata->perl);
    dSP;
    int rv = 0;

    /* Skip completely if something is wrong */
    if (!(tdata && tdata->cv && key && val)) {
        return 0;
    }

    /* Skip entries if not in our filter list */
    if (tdata->filter) {
        if (!apr_hash_get(tdata->filter, key, APR_HASH_KEY_STRING)) {
            return 1;
        }
    }

    ENTER;
    SAVETMPS;

    PUSHMARK(sp);
    XPUSHs(sv_2mortal(newSVpv(key,0)));
    XPUSHs(sv_2mortal(newSVpv(val,0)));
    PUTBACK;

    rv = call_sv(tdata->cv, 0);
    SPAGAIN;
    rv = (1 == rv) ? POPi : 1;
    PUTBACK;

    FREETMPS;
    LEAVE;

    /* rv of 0 aborts the traversal */
    return rv;
}

static MP_INLINE
int mpxs_apr_table_do(pTHX_ I32 items, SV **MARK, SV **SP)
{
    apr_table_t *table;
    SV *sub;
    mpxs_table_do_cb_data_t tdata;

    mpxs_usage_va_2(table, sub, "$table->do(sub, [@filter])");

    tdata.cv = sub;
    tdata.filter = NULL;
#ifdef USE_ITHREADS
    tdata.perl = aTHX;
#endif

    if (items > 2) {
        char *filter_entry;
        STRLEN len;

        tdata.filter = apr_hash_make(apr_table_elts(table)->pool);

        while (MARK <= SP) {
            filter_entry = SvPV(*MARK, len);
            apr_hash_set(tdata.filter, filter_entry, len, "1");
            MARK++;
        }
    }

    /* XXX: would be nice to be able to call apr_table_vdo directly,
     * but I don't think it's possible to create/populate something
     * that smells like a va_list with our list of filters specs
     */

    apr_table_do(mpxs_apr_table_do_cb, (void *)&tdata, table, NULL);

    /* Free tdata.filter or wait for the pool to go away? */

    /* XXX: return return value of apr_table_do once we require newer httpd */
    return 1;
}

static MP_INLINE int mpxs_APR__Table_EXISTS(apr_table_t *t, const char *key)
{
    return (NULL == apr_table_get(t, key)) ? 0 : 1;
}

/* Note: SvCUR is used as the iterator state counter, why not ;-? */
#define mpxs_apr_table_iterix(sv) \
SvCUR(SvRV(sv))

#define mpxs_apr_table_nextkey(t, sv) \
   ((apr_table_entry_t *) \
     apr_table_elts(t)->elts)[mpxs_apr_table_iterix(sv)++].key

static MP_INLINE const char *mpxs_APR__Table_NEXTKEY(pTHX_ SV *tsv, SV *key)
{
    apr_table_t *t;
    SV *rv = modperl_hash_tied_object_rv(aTHX_ "APR::Table", tsv);
    if (!SvROK(rv)) {
        Perl_croak(aTHX_ "Usage: $table->NEXTKEY($key): "
                   "first argument not an APR::Table object");
    }

    t = INT2PTR(apr_table_t *, SvIVX(SvRV(rv)));

    if (apr_is_empty_table(t)) {
        return NULL;
    }

    if (key == NULL) {
        mpxs_apr_table_iterix(rv) = 0; /* reset iterator index */
    }

    if (mpxs_apr_table_iterix(rv) < apr_table_elts(t)->nelts) {
        return mpxs_apr_table_nextkey(t, rv);
    }

    mpxs_apr_table_iterix(rv) = 0;

    return NULL;
}

/* Try to shortcut apr_table_get by fetching the key using the current
 * iterator (unless it's inactive or points at different key).
 */
static MP_INLINE const char *mpxs_APR__Table_FETCH(pTHX_ SV *tsv,
                                                   const char *key)
{
    SV* rv = modperl_hash_tied_object_rv(aTHX_ "APR::Table", tsv);
    const int i = mpxs_apr_table_iterix(rv);
    apr_table_t *t = INT2PTR(apr_table_t *, SvIVX(SvRV(rv)));
    const apr_array_header_t *arr = apr_table_elts(t);
    apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;

    if (i > 0 && i <= arr->nelts && !strcasecmp(key, elts[i-1].key)) {
        return elts[i-1].val;
    }
    else {
        return apr_table_get(t, key);
    }
}


MP_STATIC XS(MPXS_apr_table_get)
{
    dXSARGS;

    if (items != 2) {
        Perl_croak(aTHX_ "Usage: $table->get($key)");
    }

    mpxs_PPCODE({
        APR__Table t = modperl_hash_tied_object(aTHX_ "APR::Table", ST(0));
        const char *key = (const char *)SvPV_nolen(ST(1));

        if (!t) {
            XSRETURN_UNDEF;
        }

        if (GIMME_V == G_SCALAR) {
            const char *val = apr_table_get(t, key);

            if (val) {
                XPUSHs(sv_2mortal(newSVpv((char*)val, 0)));
            }
        }
        else {
            const apr_array_header_t *arr = apr_table_elts(t);
            apr_table_entry_t *elts = (apr_table_entry_t *)arr->elts;
            int i;

            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)));
            }
        }
    });

}
