/*
 * Copyright (C) 2000, 2001, 2013 Gregory Trubetskoy
 * Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007 Apache Software Foundation
 *
 * Licensed 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.
 *
 * Originally developed by Gregory Trubetskoy.
 *
 *
 * tableobject.c
 *
 *
 */

#include "mod_python.h"

/** XXX this is a hack. because apr_table_t
    is not available in a header file */
#define TABLE_HASH_SIZE 32
struct apr_table_t {
    apr_array_header_t a;
#ifdef MAKE_TABLE_PROFILE
    void *creator;
#endif
    apr_uint32_t index_initialized;
    int index_first[TABLE_HASH_SIZE];
    int index_last[TABLE_HASH_SIZE];
};

/**
 **     MpTable_FromTable
 **
 *      This routine creates a Python tableobject given an Apache
 *      table pointer.
 *
 */

PyObject * MpTable_FromTable(apr_table_t *t)
{
    tableobject *result;

    TABLE_DEBUG("MpTable_FromTable");

    result = PyObject_New(tableobject, &MpTable_Type);
    if (! result)
        return PyErr_NoMemory();

    result->table = t;
    result->pool = NULL;

    return (PyObject *)result;
}

/**
 ** MpTable_New
 **
 *  This returns a new object of built-in type table.
 *
 *  NOTE: The ap_table gets greated in its own pool, which lives
 *  throught the live of the tableobject. This is because this
 *  object may persist from hit to hit.
 *
 *  ALSO NOTE: table_new() also creates tables, independent of this
 *  (it gets called when table is being subclassed)
 *
 */

PyObject * MpTable_New()
{
    tableobject *t;
    apr_pool_t *p;

    TABLE_DEBUG("MpTable_New");

    /* XXX need second arg abort function to report mem error */
    apr_pool_create_ex(&p, NULL, NULL, NULL);

    /* two is a wild guess */
    t = (tableobject *)MpTable_FromTable(apr_table_make(p, 2));

    /* remember the pointer to our own pool */
    t->pool = p;

    return (PyObject *)t;

}

/**
 ** table_dealloc
 **
 *      Frees table's memory
 */

static void table_dealloc(register void *o)
{
    tableobject *self = (tableobject *)o;

    TABLE_DEBUG("table_dealloc");

    if (MpTable_Check(self)) {
        if (self->pool)
            apr_pool_destroy(self->pool);
        PyObject_Del(self);
    }
    else
        Py_TYPE(self)->tp_free((PyObject *)self);

}

/**
 ** table_print
 **
 *      prints table like a dictionary
 *      (Useful when debugging)
 */

static int table_print(register tableobject *self, register FILE *fp, register int flags)
{
    const apr_array_header_t *ah = NULL;
    apr_table_entry_t *elts;
    register int i;

    TABLE_DEBUG("table_print");

    fprintf(fp, "{");

    ah = apr_table_elts(self->table);
    elts = (apr_table_entry_t *) ah->elts;

    i = ah->nelts;
    if (i == 0) {
        fprintf(fp, "}");
        return 0;
    }

    while (i--)
        if (elts[i].key)
        {
            fprintf(fp, "'%s': '%s'", elts[i].key, elts[i].val);

            if (i > 0)
                fprintf(fp, ", ");
            else
                fprintf(fp, "}");
        }

    return 0;
}

/**
 ** table_repr
 **
 *      repr table like a dictionary
 */

static PyObject * table_repr(tableobject *self)
{
    PyObject *s;
    PyObject *t = NULL;
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    int i;

    TABLE_DEBUG("table_repr");

    s = PyBytes_FromString("{");

    ah = apr_table_elts (self->table);
    elts = (apr_table_entry_t *) ah->elts;

    i = ah->nelts;
    if (i == 0)
        PyBytes_ConcatAndDel(&s, PyBytes_FromString("}"));

    while (i--)
        if (elts[i].key)
        {
            t = PyBytes_FromString(elts[i].key);
            PyBytes_ConcatAndDel(&s, MpObject_ReprAsBytes(t));;
            Py_XDECREF(t);

            PyBytes_ConcatAndDel(&s, PyBytes_FromString(": "));

            if (elts[i].val) {
              t = PyBytes_FromString(elts[i].val);
            } else {
              t = Py_None;
              Py_INCREF(t);
            }

            PyBytes_ConcatAndDel(&s, MpObject_ReprAsBytes(t));
            Py_XDECREF(t);

            if (i > 0)
                PyBytes_ConcatAndDel(&s, PyBytes_FromString(", "));
            else
                PyBytes_ConcatAndDel(&s, PyBytes_FromString("}"));
        }

#if PY_MAJOR_VERSION < 3
    return s;
#else
    {
        PyObject *str = PyUnicode_FromString(PyBytes_AS_STRING(s));
        Py_DECREF(s);
        return str;
    }
#endif
}

/**
 ** tablelength
 **
 *      Number of elements in a table. Called
 *      when you do len(table) in Python.
 */

static Py_ssize_t tablelength(PyObject *self)
{
    TABLE_DEBUG("tablelength");

    return apr_table_elts(((tableobject *)self)->table)->nelts;
}

/**
 ** table_subscript
 **
 *      Gets a dictionary item
 */

static PyObject * table_subscript(PyObject *self, register PyObject *key)
{
    char *k;
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    register int i;
    PyObject *list;

    TABLE_DEBUG("table_subscript");

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_DECREF(key);
        return NULL;
    }

    /* it's possible that we have duplicate keys, so
       we can't simply use apr_table_get since that just
       returns the first match.
    */

    list = PyList_New(0);
    if (!list)
        return NULL;

    ah = apr_table_elts (((tableobject *)self)->table);
    elts = (apr_table_entry_t *) ah->elts;

    i = ah->nelts;

    while (i--)
        if (elts[i].key) {
            if (apr_strnatcasecmp(elts[i].key, k) == 0) {
                PyObject *v = NULL;
                if (elts[i].val != NULL)
                    v = MpBytesOrUnicode_FromString(elts[i].val);
                else {
                    v = Py_None;
                    Py_INCREF(v);
                }
                PyList_Insert(list, 0, v);
                Py_DECREF(v);
            }
        }

    Py_DECREF(key); /* becasue of MP_ANYSTR_AS_STR */

    /* if no match */
    if (PyList_Size(list) == 0) {
        Py_DECREF(list);
        PyErr_SetObject(PyExc_KeyError, key);
        return NULL;
    }

    /* if we got one match */
    if (PyList_Size(list) == 1) {
        PyObject *v = PyList_GetItem(list, 0);
        Py_INCREF(v);
        Py_DECREF(list);
        return v;
    }

    /* else we return a list */
    return list;
}

/**
 ** table_ass_subscript
 **
 *      insert into table dictionary-style
 *      *** NOTE ***
 *      Since the underlying ap_table_set makes a *copy* of the string,
 *      there is no need to increment the reference to the Python
 *      string passed in.
 */

static int table_ass_subscript(PyObject *self, PyObject *key,
                               PyObject *val)
{
    char *k, *v;

    TABLE_DEBUG("table_ass_subscript");

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_XDECREF(key); /* MP_ANYSTR_AS_STR */
        return -1;
    }

    if (val == NULL)
        apr_table_unset(((tableobject *)self)->table, k);
    else {
        MP_ANYSTR_AS_STR(v, val, 1);
        if (!v) {
            Py_XDECREF(key); /* MP_ANYSTR_AS_STR */
            Py_XDECREF(val); /* MP_ANYSTR_AS_STR */
            return -1;
        }
        apr_table_set(((tableobject *)self)->table, k, v);
    }
    Py_XDECREF(key); /* MP_ANYSTR_AS_STR */
    Py_XDECREF(val); /* MP_ANYSTR_AS_STR */
    return 0;
}

/* table as mapping */

static PyMappingMethods table_as_mapping = {
    tablelength,           /*mp_length*/
    table_subscript,       /*mp_subscript*/
    table_ass_subscript,   /*mp_ass_subscript*/
};

/**
 ** table_keys
 **
 *
 *  Implements dictionary's keys() method.
 */

static PyObject * table_keys(register tableobject *self)
{
    PyObject *v;
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    int i, j;

    TABLE_DEBUG("table_keys");

    ah = apr_table_elts(self->table);
    elts = (apr_table_entry_t *) ah->elts;

    v = PyList_New(ah->nelts);

    for (i = 0, j = 0; i < ah->nelts; i++)
    {
        if (elts[i].key)
        {
            PyObject *key = MpBytesOrUnicode_FromString(elts[i].key);
            PyList_SetItem(v, j, key);
            j++;
        }
    }
    return v;
}

/**
 ** table_values
 **
 *
 *  Implements dictionary's values() method.
 */

static PyObject * table_values(register tableobject *self)
{

    PyObject *v;
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    int i, j;

    TABLE_DEBUG("table_values");

    ah = apr_table_elts(self->table);
    elts = (apr_table_entry_t *) ah->elts;
    v = PyList_New(ah->nelts);

    for (i = 0, j = 0; i < ah->nelts; i++)
    {
        if (elts[i].key)
        {
            PyObject *val = NULL;
            if (elts[i].val != NULL)
                val = MpBytesOrUnicode_FromString(elts[i].val);
            else {
                val = Py_None;
                Py_INCREF(val);
            }
            PyList_SetItem(v, j, val);
            j++;
        }
    }
    return v;
}

/**
 ** table_items
 **
 *
 *  Implements dictionary's items() method.
 */

static PyObject * table_items(register tableobject *self)
{

    PyObject *v;
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    int i, j;

    TABLE_DEBUG("table_items");

    ah = apr_table_elts(self->table);
    elts = (apr_table_entry_t *) ah->elts;
    v = PyList_New(ah->nelts);

    for (i = 0, j = 0; i < ah->nelts; i++)
    {
        if (elts[i].key)
        {
            PyObject *keyval = Py_BuildValue("(s,s)", elts[i].key, elts[i].val);
            PyList_SetItem(v, j, keyval);
            j++;
        }
    }
    return v;
}

/**
 ** table_merge
 **
 *  Since tables can only store strings, key/vals from
 *  mapping object b will be str()ingized.
 */

static int table_merge(tableobject *a, PyObject *b, int override)
{
    /* Do it the generic, slower way */
    PyObject *keys = PyMapping_Keys(b);
    PyObject *iter;
    PyObject *key, *value, *skey, *svalue;
    int status;

    TABLE_DEBUG("table_merge");

    if (keys == NULL) {
        TABLE_DEBUG("  table_merge: keys NULL");
        return -1;
    }

    iter = PyObject_GetIter(keys);
    Py_DECREF(keys);
    if (iter == NULL) {
        TABLE_DEBUG("  table_merge: iter NULL");
        return -1;
    }

    for (key = PyIter_Next(iter); key; key = PyIter_Next(iter)) {
        char *c_skey;

        skey = PyObject_Str(key);
        if (skey == NULL) {
            Py_DECREF(iter);
            Py_DECREF(key);
            TABLE_DEBUG("  table_merge: skey NULL");
            return -1;
        }
        MP_ANYSTR_AS_STR(c_skey, skey, 0);
        if (!c_skey) {
            Py_DECREF(key);
            Py_DECREF(skey);
            TABLE_DEBUG("  table_merge: c_skey NULL");
            return -1;
        }
        if (!override && apr_table_get(a->table, c_skey) != NULL) {
            Py_DECREF(key);
            Py_DECREF(skey);
            continue;
        }

        value = PyObject_GetItem(b, key);
        if (value == NULL) {
            Py_DECREF(iter);
            Py_DECREF(key);
            Py_DECREF(skey);
            TABLE_DEBUG("  table_merge: value NULL");
            return -1;
        }
        svalue = PyObject_Str(value);
        if (svalue == NULL) {
            Py_DECREF(iter);
            Py_DECREF(key);
            Py_DECREF(skey);
            Py_DECREF(value);
            TABLE_DEBUG("  table_merge: svalue NULL");
            return -1;
        }
        status = table_ass_subscript((PyObject *)a, skey, svalue);
        Py_DECREF(key);
        Py_DECREF(value);
        Py_DECREF(skey);
        Py_DECREF(svalue);
        if (status < 0) {
            Py_DECREF(iter);
            TABLE_DEBUG("  table_merge: status < 0");
            return -1;
        }
    }
    Py_DECREF(iter);
    if (PyErr_Occurred()) {
        /* Iterator completed, via error */
        TABLE_DEBUG("  table_merge: PyErr_Occurred()");
        return -1;
    }

    return 0;
}

/**
 ** table_update
 **
 */

static PyObject *table_update(tableobject *self, PyObject *other)
{
    TABLE_DEBUG("table_update");

    if (table_merge(self, other, 1) < 0)
        return NULL;

    Py_INCREF(Py_None);
    return Py_None;
}

/**
 ** table_mergefromseq2
 **
 *  Similar to PyDict_MergeFromSeq2 (code borrowed from there).
 */

static int table_mergefromseq2(tableobject *self, PyObject *seq2, int override)
{
    PyObject *it;       /* iter(seq2) */
    int i;              /* index into seq2 of current element */
    PyObject *item;     /* seq2[i] */
    PyObject *fast;     /* item as a 2-tuple or 2-list */

    TABLE_DEBUG("table_mergefromseq2");

    it = PyObject_GetIter(seq2);
    if (it == NULL)
        return -1;

    for (i = 0; ; ++i) {
        PyObject *key, *value, *skey, *svalue;
        char *c_skey;
        int n;

        fast = NULL;
        item = PyIter_Next(it);
        if (item == NULL) {
            if (PyErr_Occurred())
                goto Fail;
            break;
        }

        /* Convert item to sequence, and verify length 2. */
        fast = PySequence_Fast(item, "");
        if (fast == NULL) {
            if (PyErr_ExceptionMatches(PyExc_TypeError))
                PyErr_Format(PyExc_TypeError,
                             "cannot convert table update "
                             "sequence element #%d to a sequence",
                             i);
            goto Fail;
        }
        n = PySequence_Fast_GET_SIZE(fast);
        if (n != 2) {
            PyErr_Format(PyExc_ValueError,
                         "table update sequence element #%d "
                         "has length %d; 2 is required",
                         i, n);
            goto Fail;
        }

        /* Update/merge with this (key, value) pair. */
        key = PySequence_Fast_GET_ITEM(fast, 0);
        value = PySequence_Fast_GET_ITEM(fast, 1);
        skey = PyObject_Str(key);
        if (skey == NULL)
            goto Fail;
        svalue = PyObject_Str(value);
        if (svalue == NULL) {
            Py_DECREF(svalue);
            goto Fail;
        }

        MP_ANYSTR_AS_STR(c_skey, skey, 0);
        if (!c_skey) {
            Py_DECREF(skey);
            Py_DECREF(svalue);
            goto Fail;
        }

        if (override || apr_table_get(self->table, c_skey) == NULL) {
            int status = table_ass_subscript((PyObject *)self, skey, svalue);
            if (status < 0) {
                Py_DECREF(skey);
                Py_DECREF(svalue);
                goto Fail;
            }
        }

        Py_DECREF(skey);
        Py_DECREF(svalue);
        Py_DECREF(fast);
        Py_DECREF(item);
    }

    i = 0;
    goto Return;
 Fail:
    Py_XDECREF(item);
    Py_XDECREF(fast);
    i = -1;
 Return:
    Py_DECREF(it);
    return i;
}

/**
 ** table_copy
 **
 */

static PyObject *table_copy(register tableobject *from)
{
    tableobject *to = (tableobject *)MpTable_New();

    TABLE_DEBUG("table_copy");

    if (to != NULL)
        apr_table_overlap(to->table, from->table, 0);

    return (PyObject*)to;
}

#if PY_MAJOR_VERSION < 3
/**
 ** table_compare
 **
 */

static int table_compare(tableobject *a, tableobject *b)
{
    /*
       we're so lazy that we just copy tables to dicts
       and rely on dict's compare ability. this is not
       the best way to do this to say the least
    */

    PyObject *ad, *bd;
    int result;

    TABLE_DEBUG("table_compare");

    ad = PyDict_New();
    bd = PyDict_New();

    PyDict_Merge(ad, (PyObject*)a, 0);
    PyDict_Merge(bd, (PyObject*)b, 0);

    result = PyObject_Compare(ad, bd);

    Py_DECREF(ad);
    Py_DECREF(bd);

    return result;
}
#endif

/**
 ** table_richcompare
 **
 */

static PyObject *table_richcompare(PyObject *a, PyObject *b, int op)
{

    PyObject *ad, *bd, *result;

    TABLE_DEBUG("table_richcompare");

    ad = PyDict_New();
    bd = PyDict_New();

    PyDict_Merge(ad, (PyObject*)a, 0);
    PyDict_Merge(bd, (PyObject*)b, 0);

    result = PyObject_RichCompare(ad, bd, op);

    Py_DECREF(ad);
    Py_DECREF(bd);

    return result;

}

/**
 ** table_has_key
 **
 */

static PyObject * table_has_key(tableobject *self, PyObject *key)
{

    const char *k;

    TABLE_DEBUG("table_has_key");

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_DECREF(key); /* MP_ANYSTR_AS_STR */
        return NULL;
    }

    if (apr_table_get(self->table, k))
        return PyLong_FromLong(1);
    else
        return PyLong_FromLong(0);
}

/**
 ** table_get
 **
 *    implements get([failobj]) method
 *    (only returns the first match)
 */

static PyObject *table_get(register tableobject *self, PyObject *args)
{
    PyObject *key;
    PyObject *failobj = Py_None;
    PyObject *val = NULL;
    const char *k, *v;

    TABLE_DEBUG("table_get");

    if (!PyArg_ParseTuple(args, "O|O:get", &key, &failobj))
        return NULL;

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_DECREF(key); /* MP_ANYSTR_AS_STR */
        return NULL;
    }

    v = apr_table_get(self->table, k);
    if (!v) {
        val = failobj;
        Py_INCREF(val);
    }
    else
        val = MpBytesOrUnicode_FromString(v);

    Py_DECREF(key); /* MP_ANYSTR_AS_STR */
    return val;
}

/**
 ** table_setdefault
 **
 *    implements setdefault(key, [val]) method
 */

static PyObject *table_setdefault(register tableobject *self, PyObject *args)
{
    int len;
    PyObject *failobj = NULL;
    PyObject *key, *val = NULL;
    char *k = NULL, *f = NULL;
    const char *v = NULL;

    TABLE_DEBUG("table_setdefault");

    if (!PyArg_ParseTuple(args, "O|O:setdefault", &key, &failobj))
        return NULL;

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_DECREF(key); /* MP_ANYSTR_AS_STR */
        return NULL;
    }

    if (failobj) {
        MP_ANYSTR_AS_STR(f, failobj, 1);
        if (!f) {
            Py_DECREF(failobj); /* MP_ANYSTR_AS_ATR */
            return NULL;
        }
    }

    v = apr_table_get(self->table, k);
    if (!v) {
        if (f) {
            apr_table_set(self->table, k, f);
            val = failobj;
            Py_INCREF(val);
        }
        else {
            apr_table_set(self->table, k, "");
            v = "";
        }
    }

    val = MpBytesOrUnicode_FromString(v);

    Py_XDECREF(failobj); /* MP_ANYSTR_AS_ATR */
    return val;
}

/**
 ** table_clear
 **
 */

static PyObject *table_clear(register tableobject *self)
{
    TABLE_DEBUG("table_clear");

    apr_table_clear(self->table);

    Py_INCREF(Py_None);
    return Py_None;
}

/**
 ** table_popitem
 **
 */

static PyObject *table_popitem(tableobject *self)
{
    apr_array_header_t *ah;
    apr_table_entry_t *elts;
    PyObject *res;

    TABLE_DEBUG("table_popitem");

    ah = (apr_array_header_t *) apr_table_elts(self->table);
    elts = (apr_table_entry_t *) ah->elts;

    if (ah->nelts == 0) {
        PyErr_SetString(PyExc_KeyError,
                        "popitem(): table is empty");
        return NULL;
    }

    res = Py_BuildValue("(s,s)", elts[0].key, elts[0].val);
    ah->nelts--;
    elts++;

    return res;
}

/**
 ** table_traverse
 **
 */

static int table_traverse(tableobject *self, visitproc visit, void *arg)
{
    const apr_array_header_t *ah;
    apr_table_entry_t *elts;
    register int i;

    TABLE_DEBUG("table_traverse");

    ah = apr_table_elts (self->table);
    elts = (apr_table_entry_t *) ah->elts;

    i = ah->nelts;

    while (i--)
        if (elts[i].key) {
            int err;

            PyObject *v = NULL;
            if (elts[i].val != NULL)
                v = MpBytesOrUnicode_FromString(elts[i].val);
            else {
                v = Py_None;
                Py_INCREF(v);
            }

            err = visit(v, arg);
            Py_XDECREF(v);
            if (err)
                return err;
        }

    return 0;
}

/**
 ** table_tp_clear
 **
 */

static int table_tp_clear(tableobject *self)
{
    TABLE_DEBUG("table_tp_clear");

    table_clear(self);
    return 0;
}

/**
 ** mp_table_add
 **
 *     this function is equivalent of ap_table_add -
 *     it can create duplicate entries.
 */

static PyObject * mp_table_add(tableobject *self, PyObject *args)
{
    PyObject *key, *val;
    const char *k, *v;

    TABLE_DEBUG("mp_table_add");

    if (! PyArg_ParseTuple(args, "OO", &key, &val))
        return NULL;

    MP_ANYSTR_AS_STR(k, key, 1);
    MP_ANYSTR_AS_STR(v, val, 1);
    if ((!k) || (!v)) {
        Py_DECREF(key); /* MP_ANYSTR_AS_STR */
        Py_DECREF(val); /* MP_ANYSTR_AS_STR */
        return NULL;
    }

    apr_table_add(self->table, k, v);

    Py_DECREF(key); /* MP_ANYSTR_AS_STR */
    Py_DECREF(val); /* MP_ANYSTR_AS_STR */

    Py_INCREF(Py_None);
    return Py_None;
}

typedef PyObject * (*tableselectfunc)(apr_table_entry_t *);

static PyObject *tableiter_new(tableobject *, tableselectfunc);

static PyObject *select_key(apr_table_entry_t *elts)
{
    return MpBytesOrUnicode_FromString(elts->key);
}

static PyObject *select_value(apr_table_entry_t *elts)
{
    PyObject *val = NULL;

    TABLE_DEBUG("select_value");

    if (elts->val != NULL)
        val = MpBytesOrUnicode_FromString(elts->val);
    else {
        val = Py_None;
        Py_INCREF(val);
    }

    return val;
}

static PyObject *select_item(apr_table_entry_t *elts)
{
    TABLE_DEBUG("select_item");

    return Py_BuildValue("(s,s)", elts->key, elts->val);
}

static PyObject *table_iterkeys(tableobject *self)
{
    TABLE_DEBUG("table_iterkeys");

    return tableiter_new(self, select_key);
}

static PyObject *table_itervalues(tableobject *self)
{
    TABLE_DEBUG("table_itervalues");

    return tableiter_new(self, select_value);
}

static PyObject *table_iteritems(tableobject *self)
{
    TABLE_DEBUG("table_iteritems");

    return tableiter_new(self, select_item);
}

static char has_key__doc__[] =
"T.has_key(k) -> 1 if T has a key k, else 0";

static char get__doc__[] =
"T.get(k[,d]) -> T[k] if T.has_key(k), else d.  d defaults to None.";

static char setdefault_doc__[] =
"T.setdefault(k[,d]) -> T.get(k,d), also set T[k]=d if not T.has_key(k)";

static char popitem__doc__[] =
"T.popitem() -> (k, v), remove and return some (key, value) pair as a\n\
2-tuple; but raise KeyError if T is empty";

static char keys__doc__[] =
"T.keys() -> list of T's keys";

static char items__doc__[] =
"T.items() -> list of T's (key, value) pairs, as 2-tuples";

static char values__doc__[] =
"T.values() -> list of T's values";

static char update__doc__[] =
"T.update(E) -> None.  Update T from E: for k in E.keys(): T[k] = E[k]";

static char clear__doc__[] =
"T.clear() -> None.  Remove all items from T.";

static char copy__doc__[] =
"T.copy() -> a shallow copy of T";

static char iterkeys__doc__[] =
"T.iterkeys() -> an iterator over the keys of T";

static char itervalues__doc__[] =
"T.itervalues() -> an iterator over the values of T";

static char iteritems__doc__[] =
"T.iteritems() -> an iterator over the (key, value) items of T";

static char add__doc__[] =
"T.add(k, v) -> add (as oppsed to replace) a key k and value v";

/* table method definitions */
static PyMethodDef mp_table_methods[] = {
    {"has_key",     (PyCFunction)table_has_key,      METH_O,       has_key__doc__},
    {"get",         (PyCFunction)table_get,          METH_VARARGS, get__doc__},
    {"setdefault",  (PyCFunction)table_setdefault,   METH_VARARGS, setdefault_doc__},
    {"popitem",     (PyCFunction)table_popitem,      METH_NOARGS,  popitem__doc__},
    {"keys",        (PyCFunction)table_keys,         METH_NOARGS,  keys__doc__},
    {"items",       (PyCFunction)table_items,        METH_NOARGS,  items__doc__},
    {"values",      (PyCFunction)table_values,       METH_NOARGS,  values__doc__},
    {"update",      (PyCFunction)table_update,       METH_O,       update__doc__},
    {"clear",       (PyCFunction)table_clear,        METH_NOARGS,  clear__doc__},
    {"copy",        (PyCFunction)table_copy,         METH_NOARGS,  copy__doc__},
    {"iterkeys",    (PyCFunction)table_iterkeys,     METH_NOARGS,  iterkeys__doc__},
    {"itervalues",  (PyCFunction)table_itervalues,   METH_NOARGS,  itervalues__doc__},
    {"iteritems",   (PyCFunction)table_iteritems,    METH_NOARGS,  iteritems__doc__},
    {"add",         (PyCFunction)mp_table_add,       METH_VARARGS, add__doc__},
    {NULL,          NULL}       /* sentinel */
};

static int table_contains(tableobject *self, PyObject *key)
{
    char *k;
    const char *v;
    int rc;

    TABLE_DEBUG("table_contains");

    MP_ANYSTR_AS_STR(k, key, 1);
    if (!k) {
        Py_DECREF(key);
        return -1;
    }
    v = apr_table_get(self->table, k);
    Py_DECREF(key);
    return (v != NULL);
}

static PySequenceMethods table_as_sequence = {
    0,                                  /* sq_length */
    0,                                  /* sq_concat */
    0,                                  /* sq_repeat */
    0,                                  /* sq_item */
    0,                                  /* sq_slice */
    0,                                  /* sq_ass_item */
    0,                                  /* sq_ass_slice */
    (objobjproc)table_contains,         /* sq_contains */
    0,                                  /* sq_inplace_concat */
    0,                                  /* sq_inplace_repeat */
};

/**
 ** table_new
 **
 */

static PyObject *table_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    TABLE_DEBUG("table_new");

    return MpTable_New();
}

static int table_init(tableobject *self, PyObject *args, PyObject *kwds)
{
    PyObject *arg = NULL;
    static char *kwlist[] = {"items", 0};
    int result = 0;

    TABLE_DEBUG("table_init");

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O:mp_table",
                                     kwlist, &arg))
        result = -1;

    else if (arg != NULL) {
        if (PyObject_HasAttrString(arg, "keys"))
            result = table_merge(self, arg, 1);
        else
            result = table_mergefromseq2(self, arg, 1);
    }
    return result;
}

static long table_nohash(PyObject *self)
{
    TABLE_DEBUG("table_nohash");

    PyErr_SetString(PyExc_TypeError, "mp_table objects are unhashable");
    return -1;
}

static PyObject *table_iter(tableobject *self)
{
    TABLE_DEBUG("table_iter");

    return tableiter_new(self, select_key);
}

static char mp_table_doc[] =
"table() -> new empty table.\n"
"table(mapping) -> new table initialized from a mapping object's\n"
"    (key, value) pairs.\n"
"table(seq) -> new table initialized as if via:\n"
"    d = {}\n"
"    for k, v in seq:\n"
"        d[k] = v";

PyTypeObject MpTable_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "mp_table",                         /* tp_name */
    sizeof(tableobject),                /* tp_basicsize */
    0,                                  /* tp_itemsize */
    (destructor)table_dealloc,          /* tp_dealloc */
    (printfunc)table_print,             /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
#if PY_MAJOR_VERSION < 3
    (cmpfunc)table_compare,             /* tp_compare */
#else
    0,                                  /* tp_reserved */
#endif
    (reprfunc)table_repr,               /* tp_repr */
    0,                                  /* tp_as_number */
    &table_as_sequence,                 /* tp_as_sequence */
    &table_as_mapping,                  /* tp_as_mapping */
    table_nohash,                       /* tp_hash */
    0,                                  /* tp_call */
    0,                                  /* tp_str */
    PyObject_GenericGetAttr,            /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT |
    Py_TPFLAGS_BASETYPE,                /* tp_flags */
    mp_table_doc,                       /* tp_doc */
    (traverseproc)table_traverse,       /* tp_traverse */
    /* PYTHON 2.5: 'inquiry' should be perhaps replaced with 'lenfunc' */
    (inquiry)table_tp_clear,            /* tp_clear */
    table_richcompare,                  /* tp_richcompare */
    0,                                  /* tp_weaklistoffset */
    (getiterfunc)table_iter,            /* tp_iter */
    0,                                  /* tp_iternext */
    mp_table_methods,                   /* tp_methods */
    0,                                  /* tp_members */
    0,                                  /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
    0,                                  /* tp_dictoffset */
    (initproc)table_init,               /* tp_init */
    0,                                  /* tp_alloc */
    table_new,                          /* tp_new */
    table_dealloc,                      /* tp_free */
};

/* Table iterator type */

extern PyTypeObject MpTableIter_Type; /* Forward */

typedef struct {
    PyObject_HEAD
    tableobject *table;
    int ti_nelts;
    int ti_pos;
    tableselectfunc ti_select;
} tableiterobject;

static PyObject *tableiter_new(tableobject *table, tableselectfunc select)
{
    tableiterobject *ti;

    TABLE_DEBUG("tableiter_new");

    ti = PyObject_NEW(tableiterobject, &MpTableIter_Type);
    if (ti == NULL)
        return NULL;
    Py_INCREF(table);
    ti->table = table;
    ti->ti_nelts = table->table->a.nelts;
    ti->ti_pos = 0;
    ti->ti_select = select;
    return (PyObject *)ti;
}

static void tableiter_dealloc(tableiterobject *ti)
{
    Py_DECREF(ti->table);
    PyObject_DEL(ti);
}

static PyObject *tableiter_next(tableiterobject *ti, PyObject *args)
{
    apr_table_entry_t *elts = (apr_table_entry_t *) ti->table->table->a.elts;

    TABLE_DEBUG("tableiter_next");

    /* make sure the table hasn't change while being iterated */

    if (ti->ti_nelts != ti->table->table->a.nelts) {
        PyErr_SetString(PyExc_RuntimeError,
                        "table changed size during iteration");
        return NULL;
    }

    /* return the next key/val */

    if (ti->ti_pos < ti->table->table->a.nelts) {
        return (*ti->ti_select)(&elts[ti->ti_pos++]);
    }

    /* the end has been reached */

    PyErr_SetObject(PyExc_StopIteration, Py_None);
    return NULL;
}

static PyObject *tableiter_getiter(PyObject *it)
{
    TABLE_DEBUG("tableiter_getiter");

    Py_INCREF(it);
    return it;
}

static PyMethodDef tableiter_methods[] = {
    {"next",    (PyCFunction)tableiter_next,    METH_VARARGS,
     "it.next() -- get the next value, or raise StopIteration"},
    {NULL,              NULL}           /* sentinel */
};

static PyObject *tableiter_iternext(tableiterobject *ti)
{
    apr_table_entry_t *elts = (apr_table_entry_t *) ti->table->table->a.elts;

    TABLE_DEBUG("tableiter_iternext");

    /* make sure the table hasn't change while being iterated */

    if (ti->ti_nelts != ti->table->table->a.nelts) {
        PyErr_SetString(PyExc_RuntimeError,
                        "table changed size during iteration");
        return NULL;
    }

    /* return the next key/val */

    if (ti->ti_pos < ti->table->table->a.nelts) {
        return (*ti->ti_select)(&elts[ti->ti_pos++]);
    }

    /* the end has been reached */

    PyErr_SetObject(PyExc_StopIteration, Py_None);
    return NULL;

}

PyTypeObject MpTableIter_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "dictionary-iterator",              /* tp_name */
    sizeof(tableiterobject),            /* tp_basicsize */
    0,                                  /* tp_itemsize */
    /* methods */
    (destructor)tableiter_dealloc,      /* tp_dealloc */
    0,                                  /* tp_print */
    0,                                  /* tp_getattr */
    0,                                  /* tp_setattr */
    0,                                  /* tp_compare */
    0,                                  /* tp_repr */
    0,                                  /* tp_as_number */
    0,                                  /* tp_as_sequence */
    0,                                  /* tp_as_mapping */
    0,                                  /* tp_hash */
    0,                                  /* tp_call */
    0,                                  /* tp_str */
    PyObject_GenericGetAttr,            /* tp_getattro */
    0,                                  /* tp_setattro */
    0,                                  /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
    0,                                  /* tp_doc */
    0,                                  /* tp_traverse */
    0,                                  /* tp_clear */
    0,                                  /* tp_richcompare */
    0,                                  /* tp_weaklistoffset */
    (getiterfunc)tableiter_getiter,     /* tp_iter */
    (iternextfunc)tableiter_iternext,   /* tp_iternext */
    tableiter_methods,                  /* tp_methods */
    0,                                  /* tp_members */
    0,                                  /* tp_getset */
    0,                                  /* tp_base */
    0,                                  /* tp_dict */
    0,                                  /* tp_descr_get */
    0,                                  /* tp_descr_set */
};


