/* -*- Mode: C; c-basic-offset: 4 -*-
 * vim: tabstop=4 shiftwidth=4 expandtab
 *
 * Copyright (C) 2011 John (J5) Palmieri <johnp@redhat.com>
 * Copyright (C) 2014 Simon Feltman <sfeltman@gnome.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */

#include "pygi-argument.h"
#include "pygi-cache-private.h"
#include "pygi-util.h"

typedef struct _PyGIHashCache {
    PyGIArgCache arg_cache;
    PyGIArgCache *key_cache;
    PyGIArgCache *value_cache;
} PyGIHashCache;


static void
_hash_cache_free_func (PyGIHashCache *cache)
{
    if (cache != NULL) {
        pygi_arg_cache_free (cache->key_cache);
        pygi_arg_cache_free (cache->value_cache);
        g_slice_free (PyGIHashCache, cache);
    }
}

static void
free_hash_table_only (GHashTable *hash_table)
{
    g_hash_table_steal_all (hash_table);
    g_hash_table_unref (hash_table);
}

static gboolean
_pygi_marshal_from_py_ghash (PyGIInvokeState *state,
                             PyGICallableCache *callable_cache,
                             PyGIArgCache *arg_cache, PyObject *py_arg,
                             GIArgument *arg,
                             PyGIMarshalCleanupData *cleanup_data)
{
    PyGIMarshalFromPyFunc key_from_py_marshaller;
    PyGIMarshalFromPyFunc value_from_py_marshaller;

    int i;
    Py_ssize_t py_length;
    guint length;
    PyObject *py_keys, *py_values;

    GHashFunc hash_func = NULL;
    GEqualFunc equal_func = NULL;
    GDestroyNotify key_destroy_func = NULL;
    GDestroyNotify value_destroy_func = NULL;

    GHashTable *hash_ = NULL;
    PyGIHashCache *hash_cache = (PyGIHashCache *)arg_cache;
    GArray *item_cleanups;
    PyGIMarshalCleanupData hash_cleanup_data = { 0 };

    if (Py_IsNone (py_arg)) {
        arg->v_pointer = NULL;
        return TRUE;
    }

    py_keys = PyMapping_Keys (py_arg);
    if (py_keys == NULL) {
        PyErr_Format (PyExc_TypeError, "Must be mapping, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    py_length = PyMapping_Length (py_arg);
    if (py_length < 0) {
        Py_DECREF (py_keys);
        return FALSE;
    }
    if (!pygi_guint_from_pyssize (py_length, &length)) return FALSE;

    py_values = PyMapping_Values (py_arg);
    if (py_values == NULL) {
        Py_DECREF (py_keys);
        return FALSE;
    }

    item_cleanups = g_array_sized_new (
        FALSE, TRUE, sizeof (PyGIMarshalCleanupData), length * 2 + 1);
    pygi_marshal_cleanup_data_init_full (
        cleanup_data, item_cleanups,
        (GDestroyNotify)pygi_marshal_cleanup_data_destroy_array,
        (GDestroyNotify)pygi_marshal_cleanup_data_destroy_array_failed);

    key_from_py_marshaller = hash_cache->key_cache->from_py_marshaller;
    value_from_py_marshaller = hash_cache->value_cache->from_py_marshaller;

    if (hash_cache->key_cache->type_tag == GI_TYPE_TAG_UTF8
        || hash_cache->key_cache->type_tag == GI_TYPE_TAG_FILENAME) {
        hash_func = g_str_hash;
        equal_func = g_str_equal;
        /* Transfer rule has been updated in hash_table_item_cache_new(). */
        key_destroy_func = g_free;
    }
    if (hash_cache->value_cache->type_tag == GI_TYPE_TAG_UTF8
        || hash_cache->value_cache->type_tag == GI_TYPE_TAG_FILENAME) {
        value_destroy_func = g_free;
    }

    hash_ = g_hash_table_new_full (hash_func, equal_func, key_destroy_func,
                                   value_destroy_func);
    if (hash_ == NULL) {
        PyErr_NoMemory ();
        Py_DECREF (py_keys);
        Py_DECREF (py_values);
        return FALSE;
    }

    for (i = 0; i < py_length; i++) {
        GIArgument key, value;
        PyGIMarshalCleanupData key_cleanup_data = { 0 };
        PyGIMarshalCleanupData value_cleanup_data = { 0 };
        PyObject *py_key = PyList_GET_ITEM (py_keys, i);
        PyObject *py_value = PyList_GET_ITEM (py_values, i);
        if (py_key == NULL || py_value == NULL) goto err;

        // LEAK: key_cleanup_data and value_cleanup_data are never cleaned
        if (!key_from_py_marshaller (state, callable_cache,
                                     hash_cache->key_cache, py_key, &key,
                                     &key_cleanup_data))
            goto err;

        g_array_append_val (item_cleanups, key_cleanup_data);

        if (!value_from_py_marshaller (state, callable_cache,
                                       hash_cache->value_cache, py_value,
                                       &value, &value_cleanup_data))
            goto err;

        g_array_append_val (item_cleanups, value_cleanup_data);

        g_hash_table_insert (
            hash_,
            _pygi_arg_to_hash_pointer (key, hash_cache->key_cache->type_info),
            _pygi_arg_to_hash_pointer (value,
                                       hash_cache->value_cache->type_info));
        continue;
err:
        /* FIXME: cleanup hash keys and values */
        Py_DECREF (py_keys);
        Py_DECREF (py_values);
        g_hash_table_unref (hash_);
        _PyGI_ERROR_PREFIX ("Item %i: ", i);
        return FALSE;
    }

    arg->v_pointer = hash_;
    Py_DECREF (py_keys);
    Py_DECREF (py_values);

    /* Note that the keys and values will leak for transfer everything because
     * we do not use g_hash_table_new_full and set key/value_destroy_func. */
    pygi_marshal_cleanup_data_init_full (
        &hash_cleanup_data, arg->v_pointer,
        arg_cache->transfer == GI_TRANSFER_NOTHING
            ? (GDestroyNotify)g_hash_table_unref
            : NULL,
        (GDestroyNotify)free_hash_table_only);
    g_array_append_val (item_cleanups, hash_cleanup_data);

    return TRUE;
}

static PyObject *
_pygi_marshal_to_py_ghash (PyGIInvokeState *state,
                           PyGICallableCache *callable_cache,
                           PyGIArgCache *arg_cache, GIArgument *arg,
                           PyGIMarshalCleanupData *cleanup_data)
{
    GHashTable *hash_;
    GHashTableIter hash_table_iter;

    PyGIMarshalToPyFunc key_to_py_marshaller;
    PyGIMarshalToPyFunc value_to_py_marshaller;

    PyGIArgCache *key_arg_cache;
    PyGIArgCache *value_arg_cache;
    PyGIHashCache *hash_cache = (PyGIHashCache *)arg_cache;

    GIArgument key_arg;
    GIArgument value_arg;

    PyObject *py_obj = NULL;

    hash_ = arg->v_pointer;

    if (hash_ == NULL) {
        py_obj = Py_None;
        Py_INCREF (py_obj);
        return py_obj;
    }

    py_obj = PyDict_New ();
    if (py_obj == NULL) return NULL;

    key_arg_cache = hash_cache->key_cache;
    key_to_py_marshaller = key_arg_cache->to_py_marshaller;

    value_arg_cache = hash_cache->value_cache;
    value_to_py_marshaller = value_arg_cache->to_py_marshaller;

    g_hash_table_iter_init (&hash_table_iter, hash_);
    while (g_hash_table_iter_next (&hash_table_iter, &key_arg.v_pointer,
                                   &value_arg.v_pointer)) {
        PyGIMarshalCleanupData key_cleanup_data = { 0 };
        PyGIMarshalCleanupData value_cleanup_data = { 0 };
        PyObject *py_key;
        PyObject *py_value;
        int retval;


        _pygi_hash_pointer_to_arg_in_place (&key_arg,
                                            hash_cache->key_cache->type_info);
        py_key = key_to_py_marshaller (state, callable_cache, key_arg_cache,
                                       &key_arg, &key_cleanup_data);

        if (py_key == NULL) {
            Py_CLEAR (py_obj);
            return NULL;
        }

        _pygi_hash_pointer_to_arg_in_place (
            &value_arg, hash_cache->value_cache->type_info);
        py_value = value_to_py_marshaller (state, callable_cache,
                                           value_arg_cache, &value_arg,
                                           &value_cleanup_data);

        if (py_value == NULL) {
            Py_CLEAR (py_obj);
            Py_DECREF (py_key);
            return NULL;
        }

        retval = PyDict_SetItem (py_obj, py_key, py_value);

        Py_DECREF (py_key);
        Py_DECREF (py_value);

        if (retval < 0) {
            Py_CLEAR (py_obj);
            return NULL;
        }
    }

    pygi_marshal_cleanup_data_init_full (
        cleanup_data, arg->v_pointer,
        arg_cache->transfer != GI_TRANSFER_NOTHING
            ? (GDestroyNotify)g_hash_table_unref
            : NULL,
        arg_cache->transfer != GI_TRANSFER_NOTHING
            ? (GDestroyNotify)g_hash_table_unref
            : NULL);

    return py_obj;
}

static PyGIArgCache *
hash_table_item_cache_new (GITypeInfo *type_info, int index,
                           GITransfer transfer, PyGIDirection direction,
                           PyGICallableCache *callable_cache)
{
    PyGIArgCache *cache;
    GITypeInfo *item_type_info =
        gi_type_info_get_param_type (type_info, index);
    GITypeTag item_type_tag = gi_type_info_get_tag (item_type_info);
    GITransfer item_transfer;

    if (item_type_tag == GI_TYPE_TAG_UTF8
        || item_type_tag == GI_TYPE_TAG_FILENAME)
        /* We assign a destroy notifier to the hash table,
         * so we do not need to free strings ourselves. */
        item_transfer = GI_TRANSFER_EVERYTHING;
    else if (transfer == GI_TRANSFER_CONTAINER)
        item_transfer = GI_TRANSFER_NOTHING;
    else
        item_transfer = transfer;

    cache = pygi_arg_cache_new (item_type_info, NULL, item_transfer, direction,
                                callable_cache, 0, 0);

    gi_base_info_unref ((GIBaseInfo *)item_type_info);

    return cache;
}

PyGIArgCache *
pygi_arg_hash_table_new_from_info (GITypeInfo *type_info, GIArgInfo *arg_info,
                                   GITransfer transfer,
                                   PyGIDirection direction,
                                   PyGICallableCache *callable_cache)
{
    PyGIHashCache *hc = NULL;

    hc = g_slice_new0 (PyGIHashCache);
    if (hc == NULL) return NULL;

    pygi_arg_base_setup ((PyGIArgCache *)hc, type_info, arg_info, transfer,
                         direction);

    ((PyGIArgCache *)hc)->destroy_notify =
        (GDestroyNotify)_hash_cache_free_func;

    hc->key_cache = hash_table_item_cache_new (
        type_info, /*index=*/0, transfer, direction, callable_cache);

    if (hc->key_cache == NULL) {
        pygi_arg_cache_free ((PyGIArgCache *)hc);
        return NULL;
    }

    hc->value_cache = hash_table_item_cache_new (
        type_info, /*index=*/1, transfer, direction, callable_cache);

    if (hc->value_cache == NULL) {
        pygi_arg_cache_free ((PyGIArgCache *)hc);
        return NULL;
    }

    if (direction & PYGI_DIRECTION_FROM_PYTHON) {
        hc->arg_cache.from_py_marshaller = _pygi_marshal_from_py_ghash;
    }

    if (direction & PYGI_DIRECTION_TO_PYTHON) {
        hc->arg_cache.to_py_marshaller = _pygi_marshal_to_py_ghash;
    }

    return (PyGIArgCache *)hc;
}
