/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
/*
 * Copyright (c) 2011  Laszlo Pandy <lpandy@src.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-signal-closure.h"

#include "pygi-argument.h"
#include "pygi-boxed.h"
#include "pygi-repository.h"
#include "pygi-value.h"

static GISignalInfo *
_pygi_lookup_signal_from_g_type (GType g_type, const gchar *signal_name)
{
    GIRepository *repository;
    GIBaseInfo *info;
    GISignalInfo *signal_info = NULL;

    repository = pygi_repository_get_default ();
    info = gi_repository_find_by_gtype (repository, g_type);
    if (info == NULL) return NULL;

    if (GI_IS_OBJECT_INFO (info))
        signal_info =
            gi_object_info_find_signal (GI_OBJECT_INFO (info), signal_name);
    else if (GI_IS_INTERFACE_INFO (info))
        signal_info = gi_interface_info_find_signal (GI_INTERFACE_INFO (info),
                                                     signal_name);

    gi_base_info_unref (info);

    return signal_info;
}

static void
pygi_signal_closure_invalidate (gpointer data, GClosure *closure)
{
    PyGClosure *pc = (PyGClosure *)closure;
    PyGILState_STATE state;

    state = PyGILState_Ensure ();
    Py_XDECREF (pc->callback);
    Py_XDECREF (pc->extra_args);
    Py_XDECREF (pc->swap_data);
    PyGILState_Release (state);

    pc->callback = NULL;
    pc->extra_args = NULL;
    pc->swap_data = NULL;

    gi_base_info_unref (((PyGISignalClosure *)pc)->signal_info);
    ((PyGISignalClosure *)pc)->signal_info = NULL;
}

static gboolean
array_length_from_parameter (GICallableInfo *callable_info,
                             GITypeInfo *type_info, GValue *values,
                             guint n_values, gsize *array_len)
{
    guint length_arg_index;
    GIArgInfo length_arg_info;
    GITypeInfo length_type_info;
    GIArgument length_arg;
    gboolean result;

    if (!gi_type_info_get_array_length_index (type_info, &length_arg_index)) {
        return FALSE;
    }

    g_assert (length_arg_index + 1 < n_values);

    gi_callable_info_load_arg (callable_info, length_arg_index,
                               &length_arg_info);
    gi_arg_info_load_type_info (&length_arg_info, &length_type_info);

    /* Take the next value: values[0] is "self". */
    length_arg = _pygi_argument_from_g_value (&(values[length_arg_index + 1]),
                                              &length_type_info);
    result = pygi_argument_to_gsize (
        length_arg, gi_type_info_get_tag (&length_type_info), array_len);

    gi_base_info_clear (&length_arg_info);
    gi_base_info_clear (&length_type_info);

    return result;
}

static void
pygi_signal_closure_marshal (GClosure *closure, GValue *return_value,
                             guint n_param_values, const GValue *param_values,
                             gpointer invocation_hint, gpointer marshal_data)
{
    PyGILState_STATE state;
    PyGClosure *pc = (PyGClosure *)closure;
    PyObject *params, *ret = NULL;
    guint i;
    GISignalInfo *signal_info;
    gint n_sig_info_args;
    gint sig_info_highest_arg;
    GSList *list_item = NULL;
    GSList *pass_by_ref_structs = NULL;

    state = PyGILState_Ensure ();

    signal_info = ((PyGISignalClosure *)closure)->signal_info;
    n_sig_info_args =
        gi_callable_info_get_n_args (GI_CALLABLE_INFO (signal_info));
    g_assert_cmpint (n_sig_info_args, >=, 0);
    /* the first argument to a signal callback is instance,
       but instance is not counted in the introspection data */
    sig_info_highest_arg = n_sig_info_args + 1;
    g_assert_cmpint (sig_info_highest_arg, ==, n_param_values);

    /* construct Python tuple for the parameter values */
    params = PyTuple_New (n_param_values);
    for (i = 0; i < n_param_values; i++) {
        /* swap in a different initial data for connect_object() */
        if (i == 0 && G_CCLOSURE_SWAP_DATA (closure)) {
            g_return_if_fail (pc->swap_data != NULL);
            Py_INCREF (pc->swap_data);
            PyTuple_SetItem (params, 0, pc->swap_data);

        } else if (i == 0) {
            PyObject *item = pyg_value_to_pyobject (&param_values[i], FALSE);

            if (!item) {
                goto out;
            }
            PyTuple_SetItem (params, i, item);

        } else if (i < (guint)sig_info_highest_arg) {
            GIArgInfo arg_info;
            GITypeInfo *type_info;
            GITypeTag type_tag;
            GIArgument arg;
            PyObject *item = NULL;
            gboolean pass_struct_by_ref = FALSE;

            gi_callable_info_load_arg (GI_CALLABLE_INFO (signal_info), i - 1,
                                       &arg_info);
            type_info = gi_arg_info_get_type_info (&arg_info);

            arg = _pygi_argument_from_g_value (&param_values[i], type_info);
            type_tag = gi_type_info_get_tag (type_info);

            /* Hack to ensure struct arguments are passed-by-reference allowing
             * callback implementors to modify the struct values. This is needed
             * for keeping backwards compatibility and should be removed in future
             * versions which support signal output arguments as return values.
             * See: https://bugzilla.gnome.org/show_bug.cgi?id=735486
             *
             * Note the logic here must match the logic path taken in pygi_argument_to_py.
             */
            if (type_tag == GI_TYPE_TAG_INTERFACE) {
                GIBaseInfo *info = gi_type_info_get_interface (type_info);

                if (GI_IS_STRUCT_INFO (info) || GI_IS_UNION_INFO (info)) {
                    GType gtype = gi_registered_type_info_get_g_type (
                        (GIRegisteredTypeInfo *)info);
                    gboolean is_foreign =
                        (GI_IS_STRUCT_INFO (info))
                        && (gi_struct_info_is_foreign ((GIStructInfo *)info));

                    if (!is_foreign && !g_type_is_a (gtype, G_TYPE_VALUE)
                        && g_type_is_a (gtype, G_TYPE_BOXED)) {
                        pass_struct_by_ref = TRUE;
                    }
                }

                gi_base_info_unref (info);
            }

            if (pass_struct_by_ref) {
                gsize array_length;

                if (array_length_from_parameter (
                        GI_CALLABLE_INFO (signal_info), type_info,
                        param_values, n_param_values, &array_length))
                    item = pygi_argument_to_py_with_array_length (
                        type_info, arg, GI_TRANSFER_EVERYTHING, array_length);
                else
                    item = pygi_argument_to_py (type_info, arg,
                                                GI_TRANSFER_EVERYTHING);

                if (item
                    && PyObject_IsInstance (item,
                                            (PyObject *)&PyGIBoxed_Type)) {
                    ((PyGBoxed *)item)->free_on_dealloc = FALSE;
                    pass_by_ref_structs =
                        g_slist_prepend (pass_by_ref_structs, item);
                }
            } else {
                gsize array_length;

                if (array_length_from_parameter (
                        GI_CALLABLE_INFO (signal_info), type_info,
                        param_values, n_param_values, &array_length))
                    item = pygi_argument_to_py_with_array_length (
                        type_info, arg, GI_TRANSFER_NOTHING, array_length);
                else
                    item = pygi_argument_to_py (type_info, arg,
                                                GI_TRANSFER_NOTHING);
            }

            gi_base_info_unref (type_info);
            gi_base_info_clear (&arg_info);

            if (item == NULL) {
                PyErr_Print ();
                goto out;
            }
            PyTuple_SetItem (params, i, item);
        }
    }
    /* params passed to function may have extra arguments */
    if (pc->extra_args) {
        PyObject *tuple = params;
        params = PySequence_Concat (tuple, pc->extra_args);
        Py_DECREF (tuple);
    }
    ret = PyObject_CallObject (pc->callback, params);
    if (ret == NULL) {
        if (pc->exception_handler)
            pc->exception_handler (return_value, n_param_values, param_values);
        else
            PyErr_Print ();
        goto out;
    }

    if (G_IS_VALUE (return_value)
        && pyg_value_from_pyobject (return_value, ret) != 0) {
        PyErr_SetString (PyExc_TypeError,
                         "can't convert return value to desired type");

        if (pc->exception_handler)
            pc->exception_handler (return_value, n_param_values, param_values);
        else
            PyErr_Print ();
    }
    Py_DECREF (ret);

    /* Run through the list of structs which have been passed by reference and
     * check if they are being held longer than the duration of the callback
     * execution. This is determined if the ref count is greater than 1.
     * A single ref is held by the argument list and any more would mean the callback
     * stored a ref somewhere else. In this case we make an internal copy of
     * the boxed struct so Python can own the memory to it.
     */
    list_item = pass_by_ref_structs;
    while (list_item) {
        PyObject *item = list_item->data;
        if (Py_REFCNT (item) > 1) {
            pygi_boxed_copy_in_place ((PyGIBoxed *)item);
        }
        list_item = g_slist_next (list_item);
    }

out:
    g_slist_free (pass_by_ref_structs);
    Py_DECREF (params);
    PyGILState_Release (state);
}

GClosure *
pygi_signal_closure_new (PyGObject *instance, GType g_type,
                         const gchar *signal_name, PyObject *callback,
                         PyObject *extra_args, PyObject *swap_data)
{
    GClosure *closure = NULL;
    PyGISignalClosure *pygi_closure = NULL;
    GISignalInfo *signal_info = NULL;

    g_return_val_if_fail (callback != NULL, NULL);

    signal_info = _pygi_lookup_signal_from_g_type (g_type, signal_name);
    if (signal_info == NULL) return NULL;

    closure = g_closure_new_simple (sizeof (PyGISignalClosure), NULL);
    g_closure_add_invalidate_notifier (closure, NULL,
                                       pygi_signal_closure_invalidate);
    g_closure_set_marshal (closure, pygi_signal_closure_marshal);

    pygi_closure = (PyGISignalClosure *)closure;

    pygi_closure->signal_info = signal_info;
    pygi_closure->pyg_closure.callback = Py_NewRef (callback);

    if (extra_args != NULL && !Py_IsNone (extra_args)) {
        Py_INCREF (extra_args);
        if (!PyTuple_Check (extra_args)) {
            PyObject *tmp = PyTuple_New (1);
            PyTuple_SetItem (tmp, 0, extra_args);
            extra_args = tmp;
        }
        pygi_closure->pyg_closure.extra_args = extra_args;
    }
    if (swap_data) {
        pygi_closure->pyg_closure.swap_data = Py_NewRef (swap_data);
        closure->derivative_flag = TRUE;
    }

    return closure;
}
