/* 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.
 */

#include "mod_perl.h"

/*
 * modperl_svptr_table api is an add-on to the Perl ptr_table_ api.
 * we use a PTR_TBL_t to map config structures (e.g. from parsed
 * httpd.conf or .htaccess), where each interpreter needs to have its
 * own copy of the Perl SV object.  we do not use an HV* for this, because
 * the HV keys must be SVs with a string value, too much overhead.
 * we do not use an apr_hash_t because they only have the lifetime of
 * the pool used to create them. which may or may not be the same lifetime
 * of the objects we need to lookup.
 */

#ifdef USE_ITHREADS

#if MP_PERL_BRANCH(5, 6)
#   define my_sv_dup(s, p) sv_dup(s)

typedef struct {
    AV *stashes;
    UV flags;
    PerlInterpreter *proto_perl;
} CLONE_PARAMS;

#else
#   define my_sv_dup(s, p) sv_dup(s, p)
#endif

/*
 * copy a PTR_TBL_t whos PTR_TBL_ENT_t values are SVs.
 * the SVs are dup-ed so each interpreter has its own copy.
 */
PTR_TBL_t *modperl_svptr_table_clone(pTHX_ PerlInterpreter *proto_perl,
                                     PTR_TBL_t *source)
{
    UV i;
    PTR_TBL_t *tbl;
    PTR_TBL_ENT_t **src_ary, **dst_ary;
    CLONE_PARAMS parms;

    Newz(0, tbl, 1, PTR_TBL_t);
    tbl->tbl_max        = source->tbl_max;
    tbl->tbl_items        = source->tbl_items;
    Newz(0, tbl->tbl_ary, tbl->tbl_max + 1, PTR_TBL_ENT_t *);

    dst_ary = tbl->tbl_ary;
    src_ary = source->tbl_ary;

    Zero(&parms, 1, CLONE_PARAMS);
    parms.flags = 0;
    parms.stashes = newAV();

    for (i=0; i < source->tbl_max; i++, dst_ary++, src_ary++) {
        PTR_TBL_ENT_t *src_ent, *dst_ent=NULL;

        if (!*src_ary) {
            continue;
        }

        for (src_ent = *src_ary;
             src_ent;
             src_ent = src_ent->next)
        {
            if (dst_ent == NULL) {
                Newz(0, dst_ent, 1, PTR_TBL_ENT_t);
                *dst_ary = dst_ent;
            }
            else {
                Newz(0, dst_ent->next, 1, PTR_TBL_ENT_t);
                dst_ent = dst_ent->next;
            }

            /* key is just a pointer we do not modify, no need to copy */
            dst_ent->oldval = src_ent->oldval;

            dst_ent->newval =
                SvREFCNT_inc(my_sv_dup((SV*)src_ent->newval, &parms));
        }
    }

    SvREFCNT_dec(parms.stashes);

    return tbl;
}

#endif

/*
 * need to free the SV values in addition to ptr_table_free
 */
void modperl_svptr_table_destroy(pTHX_ PTR_TBL_t *tbl)
{
    UV i;
    PTR_TBL_ENT_t **ary = tbl->tbl_ary;

    for (i=0; i < tbl->tbl_max; i++, ary++) {
        PTR_TBL_ENT_t *ent;

        if (!*ary) {
            continue;
        }

        for (ent = *ary; ent; ent = ent->next) {
            if (!ent->newval) {
                continue;
            }

            SvREFCNT_dec((SV*)ent->newval);
            ent->newval = NULL;
        }
    }

    modperl_svptr_table_free(aTHX_ tbl);
}

/*
 * the Perl ptr_table_ api does not provide a function to remove
 * an entry from the table.  we need to SvREFCNT_dec the SV value
 * anyhow.
 */
void modperl_svptr_table_delete(pTHX_ PTR_TBL_t *tbl, void *key)
{
    PTR_TBL_ENT_t *entry, **oentry;
    UV hash = PTR2UV(key);

    oentry = &tbl->tbl_ary[hash & tbl->tbl_max];
    entry = *oentry;

    for (; entry; oentry = &entry->next, entry = *oentry) {
        if (entry->oldval == key) {
            *oentry = entry->next;
            SvREFCNT_dec((SV*)entry->newval);
            Safefree(entry);
            tbl->tbl_items--;
            return;
        }
    }
}

/*
 * XXX: the following are a copy of the Perl 5.8.0 Perl_ptr_table api
 * renamed s/Perl_ptr/modperl_svptr/g;
 * two reasons:
 *   these functions do not exist without -DUSE_ITHREADS
 *   the clear/free functions do not exist in 5.6.x
 */

/* create a new pointer-mapping table */

PTR_TBL_t *
modperl_svptr_table_new(pTHX)
{
    PTR_TBL_t *tbl;
    Newz(0, tbl, 1, PTR_TBL_t);
    tbl->tbl_max        = 511;
    tbl->tbl_items        = 0;
    Newz(0, tbl->tbl_ary, tbl->tbl_max + 1, PTR_TBL_ENT_t*);
    return tbl;
}

/* map an existing pointer using a table */

void *
modperl_svptr_table_fetch(pTHX_ PTR_TBL_t *tbl, void *sv)
{
    PTR_TBL_ENT_t *tblent;
    UV hash = PTR2UV(sv);
    assert(tbl);
    tblent = tbl->tbl_ary[hash & tbl->tbl_max];
    for (; tblent; tblent = tblent->next) {
        if (tblent->oldval == sv)
            return tblent->newval;
    }
    return (void*)NULL;
}

/* add a new entry to a pointer-mapping table */

void
modperl_svptr_table_store(pTHX_ PTR_TBL_t *tbl, void *oldv, void *newv)
{
    PTR_TBL_ENT_t *tblent, **otblent;
    /* XXX this may be pessimal on platforms where pointers aren't good
     * hash values e.g. if they grow faster in the most significant
     * bits */
    UV hash = PTR2UV(oldv);
    bool i = 1;

    assert(tbl);
    otblent = &tbl->tbl_ary[hash & tbl->tbl_max];
    for (tblent = *otblent; tblent; i=0, tblent = tblent->next) {
        if (tblent->oldval == oldv) {
            tblent->newval = newv;
            return;
        }
    }
    Newz(0, tblent, 1, PTR_TBL_ENT_t);
    tblent->oldval = oldv;
    tblent->newval = newv;
    tblent->next = *otblent;
    *otblent = tblent;
    tbl->tbl_items++;
    if (i && tbl->tbl_items > tbl->tbl_max)
        modperl_svptr_table_split(aTHX_ tbl);
}

/* double the hash bucket size of an existing ptr table */

void
modperl_svptr_table_split(pTHX_ PTR_TBL_t *tbl)
{
    PTR_TBL_ENT_t **ary = tbl->tbl_ary;
    UV oldsize = tbl->tbl_max + 1;
    UV newsize = oldsize * 2;
    UV i;

    Renew(ary, newsize, PTR_TBL_ENT_t*);
    Zero(&ary[oldsize], newsize-oldsize, PTR_TBL_ENT_t*);
    tbl->tbl_max = --newsize;
    tbl->tbl_ary = ary;
    for (i=0; i < oldsize; i++, ary++) {
        PTR_TBL_ENT_t **curentp, **entp, *ent;
        if (!*ary)
            continue;
        curentp = ary + oldsize;
        for (entp = ary, ent = *ary; ent; ent = *entp) {
            if ((newsize & PTR2UV(ent->oldval)) != i) {
                *entp = ent->next;
                ent->next = *curentp;
                *curentp = ent;
                continue;
            }
            else
                entp = &ent->next;
        }
    }
}

/* remove all the entries from a ptr table */

void
modperl_svptr_table_clear(pTHX_ PTR_TBL_t *tbl)
{
    register PTR_TBL_ENT_t **array;
    register PTR_TBL_ENT_t *entry;
    register PTR_TBL_ENT_t *oentry = Null(PTR_TBL_ENT_t*);
    UV riter = 0;
    UV max;

    if (!tbl || !tbl->tbl_items) {
        return;
    }

    array = tbl->tbl_ary;
    entry = array[0];
    max = tbl->tbl_max;

    for (;;) {
        if (entry) {
            oentry = entry;
            entry = entry->next;
            Safefree(oentry);
        }
        if (!entry) {
            if (++riter > max) {
                break;
            }
            entry = array[riter];
        }
    }

    tbl->tbl_items = 0;
}

/* clear and free a ptr table */

void
modperl_svptr_table_free(pTHX_ PTR_TBL_t *tbl)
{
    if (!tbl) {
        return;
    }
    modperl_svptr_table_clear(aTHX_ tbl);
    Safefree(tbl->tbl_ary);
    Safefree(tbl);
}
