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

#include "pygi-list.h"

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

typedef PyGISequenceCache PyGIArgGList;

/*
 * GList and GSList from Python
 */
static gboolean
_pygi_marshal_from_py_glist (PyGIInvokeState   *state,
                             PyGICallableCache *callable_cache,
                             PyGIArgCache      *arg_cache,
                             PyObject          *py_arg,
                             GIArgument        *arg,
                             gpointer          *cleanup_data)
{
    PyGIMarshalFromPyFunc from_py_marshaller;
    int i;
    Py_ssize_t length;
    GList *list_ = NULL;
    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;


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

    if (!PySequence_Check (py_arg)) {
        PyErr_Format (PyExc_TypeError, "Must be sequence, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    length = PySequence_Length (py_arg);
    if (length < 0)
        return FALSE;

    from_py_marshaller = sequence_cache->item_cache->from_py_marshaller;
    for (i = 0; i < length; i++) {
        GIArgument item = {0};
        gpointer item_cleanup_data = NULL;
        PyObject *py_item = PySequence_GetItem (py_arg, i);
        if (py_item == NULL)
            goto err;

        if (!from_py_marshaller ( state,
                                  callable_cache,
                                  sequence_cache->item_cache,
                                  py_item,
                                 &item,
                                 &item_cleanup_data))
            goto err;

        Py_DECREF (py_item);
        list_ = g_list_prepend (list_, _pygi_arg_to_hash_pointer (&item, sequence_cache->item_cache->type_info));
        continue;
err:
        /* FIXME: clean up list
        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
            PyGIMarshalCleanupFunc cleanup = sequence_cache->item_cache->from_py_cleanup;
        }
        */
        Py_XDECREF (py_item);
        g_list_free (list_);
        _PyGI_ERROR_PREFIX ("Item %i: ", i);
        return FALSE;
    }

    arg->v_pointer = g_list_reverse (list_);

    if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
        /* Free everything in cleanup. */
        *cleanup_data = arg->v_pointer;
    } else if (arg_cache->transfer == GI_TRANSFER_CONTAINER) {
        /* Make a shallow copy so we can free the elements later in cleanup
         * because it is possible invoke will free the list before our cleanup. */
        *cleanup_data = g_list_copy (arg->v_pointer);
    } else { /* GI_TRANSFER_EVERYTHING */
        /* No cleanup, everything is given to the callee. */
        *cleanup_data = NULL;
    }
    return TRUE;
}


static gboolean
_pygi_marshal_from_py_gslist (PyGIInvokeState   *state,
                              PyGICallableCache *callable_cache,
                              PyGIArgCache      *arg_cache,
                              PyObject          *py_arg,
                              GIArgument        *arg,
                              gpointer          *cleanup_data)
{
    PyGIMarshalFromPyFunc from_py_marshaller;
    int i;
    Py_ssize_t length;
    GSList *list_ = NULL;
    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;

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

    if (!PySequence_Check (py_arg)) {
        PyErr_Format (PyExc_TypeError, "Must be sequence, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    length = PySequence_Length (py_arg);
    if (length < 0)
        return FALSE;

    from_py_marshaller = sequence_cache->item_cache->from_py_marshaller;
    for (i = 0; i < length; i++) {
        GIArgument item = {0};
        gpointer item_cleanup_data = NULL;
        PyObject *py_item = PySequence_GetItem (py_arg, i);
        if (py_item == NULL)
            goto err;

        if (!from_py_marshaller ( state,
                             callable_cache,
                             sequence_cache->item_cache,
                             py_item,
                            &item,
                            &item_cleanup_data))
            goto err;

        Py_DECREF (py_item);
        list_ = g_slist_prepend (list_, _pygi_arg_to_hash_pointer (&item, sequence_cache->item_cache->type_info));
        continue;
err:
        /* FIXME: Clean up list
        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
            PyGIMarshalCleanupFunc cleanup = sequence_cache->item_cache->from_py_cleanup;
        }
        */

        Py_XDECREF (py_item);
        g_slist_free (list_);
        _PyGI_ERROR_PREFIX ("Item %i: ", i);
        return FALSE;
    }

    arg->v_pointer = g_slist_reverse (list_);

    if (arg_cache->transfer == GI_TRANSFER_NOTHING) {
        /* Free everything in cleanup. */
        *cleanup_data = arg->v_pointer;
    } else if (arg_cache->transfer == GI_TRANSFER_CONTAINER) {
        /* Make a shallow copy so we can free the elements later in cleanup
         * because it is possible invoke will free the list before our cleanup. */
        *cleanup_data = g_slist_copy (arg->v_pointer);
    } else { /* GI_TRANSFER_EVERYTHING */
        /* No cleanup, everything is given to the callee. */
        *cleanup_data = NULL;
    }

    return TRUE;
}

static void
_pygi_marshal_cleanup_from_py_glist  (PyGIInvokeState *state,
                                      PyGIArgCache    *arg_cache,
                                      PyObject        *py_arg,
                                      gpointer         data,
                                      gboolean         was_processed)
{
    if (was_processed) {
        GSList *list_;
        PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;

        list_ = (GSList *)data;

        /* clean up items first */
        if (sequence_cache->item_cache->from_py_cleanup != NULL) {
            PyGIMarshalCleanupFunc cleanup_func =
                sequence_cache->item_cache->from_py_cleanup;
            GSList *node = list_;
            gsize i = 0;
            while (node != NULL) {
                PyObject *py_item = PySequence_GetItem (py_arg, i);
                cleanup_func (state,
                              sequence_cache->item_cache,
                              py_item,
                              node->data,
                              TRUE);
                Py_XDECREF (py_item);
                node = node->next;
                i++;
            }
        }

        if (arg_cache->type_tag == GI_TYPE_TAG_GLIST) {
            g_list_free ( (GList *)list_);
        } else if (arg_cache->type_tag == GI_TYPE_TAG_GSLIST) {
            g_slist_free (list_);
        } else {
            g_assert_not_reached();
        }
    }
}


/*
 * GList and GSList to Python
 */
static PyObject *
_pygi_marshal_to_py_glist (PyGIInvokeState   *state,
                           PyGICallableCache *callable_cache,
                           PyGIArgCache      *arg_cache,
                           GIArgument        *arg,
                           gpointer          *cleanup_data)
{
    GList *list_;
    guint length;
    guint i;
    GPtrArray *item_cleanups;

    PyGIMarshalToPyFunc item_to_py_marshaller;
    PyGIArgCache *item_arg_cache;
    PyGISequenceCache *seq_cache = (PyGISequenceCache *)arg_cache;

    PyObject *py_obj = NULL;

    list_ = arg->v_pointer;
    length = g_list_length (list_);

    py_obj = PyList_New (length);
    if (py_obj == NULL)
        return NULL;

    item_cleanups = g_ptr_array_sized_new (length);
    *cleanup_data = item_cleanups;

    item_arg_cache = seq_cache->item_cache;
    item_to_py_marshaller = item_arg_cache->to_py_marshaller;

    for (i = 0; list_ != NULL; list_ = g_list_next (list_), i++) {
        GIArgument item_arg;
        PyObject *py_item;
        gpointer item_cleanup_data = NULL;

        item_arg.v_pointer = list_->data;
        _pygi_hash_pointer_to_arg (&item_arg, item_arg_cache->type_info);
        py_item = item_to_py_marshaller (state,
                                         callable_cache,
                                         item_arg_cache,
                                         &item_arg,
                                         &item_cleanup_data);

        g_ptr_array_index (item_cleanups, i) = item_cleanup_data;

        if (py_item == NULL) {
            Py_CLEAR (py_obj);
            _PyGI_ERROR_PREFIX ("Item %u: ", i);
            g_ptr_array_unref (item_cleanups);
            return NULL;
        }

        PyList_SET_ITEM (py_obj, i, py_item);
    }

    return py_obj;
}

static PyObject *
_pygi_marshal_to_py_gslist (PyGIInvokeState   *state,
                            PyGICallableCache *callable_cache,
                            PyGIArgCache      *arg_cache,
                            GIArgument        *arg,
                            gpointer *cleanup_data)
{
    GSList *list_;
    guint length;
    guint i;
    GPtrArray *item_cleanups;

    PyGIMarshalToPyFunc item_to_py_marshaller;
    PyGIArgCache *item_arg_cache;
    PyGISequenceCache *seq_cache = (PyGISequenceCache *)arg_cache;

    PyObject *py_obj = NULL;

    list_ = arg->v_pointer;
    length = g_slist_length (list_);

    py_obj = PyList_New (length);
    if (py_obj == NULL)
        return NULL;

    item_cleanups = g_ptr_array_sized_new (length);
    *cleanup_data = item_cleanups;

    item_arg_cache = seq_cache->item_cache;
    item_to_py_marshaller = item_arg_cache->to_py_marshaller;

    for (i = 0; list_ != NULL; list_ = g_slist_next (list_), i++) {
        GIArgument item_arg;
        PyObject *py_item;
        gpointer item_cleanup_data = NULL;

        item_arg.v_pointer = list_->data;
        _pygi_hash_pointer_to_arg (&item_arg, item_arg_cache->type_info);
        py_item = item_to_py_marshaller (state,
                                        callable_cache,
                                        item_arg_cache,
                                        &item_arg,
                                        &item_cleanup_data);

        g_ptr_array_index (item_cleanups, i) = item_cleanup_data;
        if (py_item == NULL) {
            Py_CLEAR (py_obj);
            _PyGI_ERROR_PREFIX ("Item %u: ", i);
            g_ptr_array_unref (item_cleanups);
            return NULL;
        }

        PyList_SET_ITEM (py_obj, i, py_item);
    }

    return py_obj;
}

static void
_pygi_marshal_cleanup_to_py_glist (PyGIInvokeState *state,
                                   PyGIArgCache    *arg_cache,
                                   gpointer         cleanup_data,
                                   gpointer         data,
                                   gboolean         was_processed)
{
    GPtrArray *item_cleanups = (GPtrArray *) cleanup_data;
    PyGISequenceCache *sequence_cache = (PyGISequenceCache *)arg_cache;
    GSList *list_ = (GSList *)data;

    if (sequence_cache->item_cache->to_py_cleanup != NULL) {
        PyGIMarshalToPyCleanupFunc cleanup_func =
            sequence_cache->item_cache->to_py_cleanup;
        GSList *node = list_;
        guint i = 0;

        while (node != NULL) {
            cleanup_func (state,
                          sequence_cache->item_cache,
                          g_ptr_array_index(item_cleanups, i),
                          node->data,
                          was_processed);
            node = node->next;
            i++;
        }
    }

    if (arg_cache->transfer == GI_TRANSFER_EVERYTHING ||
            arg_cache->transfer == GI_TRANSFER_CONTAINER) {

        if (arg_cache->type_tag == GI_TYPE_TAG_GLIST) {
            g_list_free ( (GList *)list_);
        } else if (arg_cache->type_tag == GI_TYPE_TAG_GSLIST) {
            g_slist_free (list_);
        } else {
            g_assert_not_reached();
        }
    }

    g_ptr_array_unref (item_cleanups);
}

static void
_arg_cache_from_py_glist_setup (PyGIArgCache *arg_cache,
                                GITransfer transfer)
{
    arg_cache->from_py_marshaller = _pygi_marshal_from_py_glist;
    arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_glist;
}

static void
_arg_cache_to_py_glist_setup (PyGIArgCache *arg_cache,
                              GITransfer transfer)
{
    arg_cache->to_py_marshaller = _pygi_marshal_to_py_glist;
    arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_glist;
}

static void
_arg_cache_from_py_gslist_setup (PyGIArgCache *arg_cache,
                                 GITransfer transfer)
{
    arg_cache->from_py_marshaller = _pygi_marshal_from_py_gslist;
    arg_cache->from_py_cleanup = _pygi_marshal_cleanup_from_py_glist;
}

static void
_arg_cache_to_py_gslist_setup (PyGIArgCache *arg_cache,
                                 GITransfer transfer)
{
    arg_cache->to_py_marshaller = _pygi_marshal_to_py_gslist;
    arg_cache->to_py_cleanup = _pygi_marshal_cleanup_to_py_glist;
}


/*
 * GList/GSList Interface
 */

static gboolean
pygi_arg_glist_setup_from_info (PyGIArgCache      *arg_cache,
                                GITypeInfo        *type_info,
                                GIArgInfo         *arg_info,
                                GITransfer         transfer,
                                PyGIDirection      direction,
                                PyGICallableCache *callable_cache)
{
    GITypeTag type_tag = gi_type_info_get_tag (type_info);

    if (!pygi_arg_sequence_setup ((PyGISequenceCache *)arg_cache,
                                  type_info,
                                  arg_info,
                                  transfer,
                                  direction,
                                  callable_cache))
        return FALSE;

    switch (type_tag) {
        case GI_TYPE_TAG_GLIST:
            {
                if (direction & PYGI_DIRECTION_FROM_PYTHON)
                    _arg_cache_from_py_glist_setup (arg_cache, transfer);

                if (direction & PYGI_DIRECTION_TO_PYTHON)
                    _arg_cache_to_py_glist_setup (arg_cache, transfer);
                break;
            }
        case GI_TYPE_TAG_GSLIST:
            {
                if (direction & PYGI_DIRECTION_FROM_PYTHON)
                    _arg_cache_from_py_gslist_setup (arg_cache, transfer);

                if (direction & PYGI_DIRECTION_TO_PYTHON)
                    _arg_cache_to_py_gslist_setup (arg_cache, transfer);

                break;
             }
       default:
           g_assert_not_reached ();
    }

    return TRUE;
}

PyGIArgCache *
pygi_arg_glist_new_from_info (GITypeInfo        *type_info,
                              GIArgInfo         *arg_info,
                              GITransfer         transfer,
                              PyGIDirection      direction,
                              PyGICallableCache *callable_cache)
{
    gboolean res = FALSE;

    PyGIArgCache *arg_cache = (PyGIArgCache *) g_slice_new0 (PyGIArgGList);
    if (arg_cache == NULL)
        return NULL;

    res = pygi_arg_glist_setup_from_info (arg_cache,
                                          type_info,
                                          arg_info,
                                          transfer,
                                          direction,
                                          callable_cache);
    if (res) {
        return arg_cache;
    } else {
        pygi_arg_cache_free (arg_cache);
        return NULL;
    }
}
