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

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

static gboolean pygi_gint8_from_py (PyObject *object, gint8 *result);
static gboolean pygi_guint8_from_py (PyObject *object, guint8 *result);

#if defined(G_OS_WIN32)
#include <float.h>
static gboolean
pygi_isfinite (gdouble value)
{
    return _finite (value);
}
#else
#include <math.h>
static gboolean
pygi_isfinite (gdouble value)
{
    return isfinite (value);
}
#endif

gboolean
pygi_gpointer_from_py (PyObject *py_arg, gpointer *result)
{
    void *temp;

    if (Py_IsNone (py_arg)) {
        *result = NULL;
        return TRUE;
    } else if (PyCapsule_CheckExact (py_arg)) {
        temp = PyCapsule_GetPointer (py_arg, NULL);
        if (temp == NULL) return FALSE;
        *result = temp;
        return TRUE;
    } else if (PyLong_Check (py_arg)) {
        temp = PyLong_AsVoidPtr (py_arg);
        if (PyErr_Occurred ()) return FALSE;
        *result = temp;
        return TRUE;
    } else {
        PyErr_SetString (
            PyExc_ValueError,
            "Pointer arguments are restricted to integers, capsules, and "
            "None. "
            "See: https://bugzilla.gnome.org/show_bug.cgi?id=683599");
        return FALSE;
    }
}

PyObject *
pygi_gsize_to_py (gsize value)
{
    return PyLong_FromSize_t (value);
}

PyObject *
pygi_gssize_to_py (gssize value)
{
    return PyLong_FromSsize_t (value);
}

static PyObject *
base_float_checks (PyObject *object)
{
    if (!PyNumber_Check (object)) {
        PyErr_Format (PyExc_TypeError, "Must be number, not %s",
                      Py_TYPE (object)->tp_name);
        return NULL;
    }

    return PyNumber_Float (object);
}

gboolean
pygi_gdouble_from_py (PyObject *py_arg, gdouble *result)
{
    PyObject *py_float;
    gdouble temp;

    py_float = base_float_checks (py_arg);
    if (py_float == NULL) return FALSE;

    temp = PyFloat_AsDouble (py_float);
    Py_DECREF (py_float);

    if (PyErr_Occurred ()) return FALSE;

    *result = temp;

    return TRUE;
}

PyObject *
pygi_gdouble_to_py (gdouble value)
{
    return PyFloat_FromDouble (value);
}

gboolean
pygi_gfloat_from_py (PyObject *py_arg, gfloat *result)
{
    gdouble double_;
    PyObject *py_float;

    py_float = base_float_checks (py_arg);
    if (py_float == NULL) return FALSE;

    double_ = PyFloat_AsDouble (py_float);
    if (PyErr_Occurred ()) {
        Py_DECREF (py_float);
        return FALSE;
    }

    if (pygi_isfinite (double_)
        && (double_ < -G_MAXFLOAT || double_ > G_MAXFLOAT)) {
        PyObject *min, *max;

        min = pygi_gfloat_to_py (-G_MAXFLOAT);
        max = pygi_gfloat_to_py (G_MAXFLOAT);
        PyErr_Format (PyExc_OverflowError, "%S not in range %S to %S",
                      py_float, min, max);
        Py_DECREF (min);
        Py_DECREF (max);
        Py_DECREF (py_float);
        return FALSE;
    }

    Py_DECREF (py_float);
    *result = (gfloat)double_;

    return TRUE;
}

PyObject *
pygi_gfloat_to_py (gfloat value)
{
    return PyFloat_FromDouble (value);
}

gboolean
pygi_gunichar_from_py (PyObject *py_arg, gunichar *result)
{
    Py_ssize_t size;
    gchar *string_;

    if (Py_IsNone (py_arg)) {
        *result = 0;
        return FALSE;
    }

    if (PyUnicode_Check (py_arg)) {
        PyObject *py_bytes;

        size = PyUnicode_GET_LENGTH (py_arg);
        py_bytes = PyUnicode_AsUTF8String (py_arg);
        if (!py_bytes) return FALSE;

        string_ = g_strdup (PyBytes_AsString (py_bytes));
        Py_DECREF (py_bytes);
    } else {
        PyErr_Format (PyExc_TypeError, "Must be string, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    if (size != 1) {
        PyErr_Format (PyExc_TypeError,
                      "Must be a one character string, not %lld characters",
                      (long long)size);
        g_free (string_);
        return FALSE;
    }

    *result = g_utf8_get_char (string_);
    g_free (string_);

    return TRUE;
}

PyObject *
pygi_gunichar_to_py (gunichar value)
{
    PyObject *py_obj = NULL;

    /* Preserve the bidirectional mapping between 0 and "" */
    if (value == 0) {
        py_obj = PyUnicode_FromString ("");
    } else if (g_unichar_validate (value)) {
        gchar utf8[6];
        gint bytes;

        bytes = g_unichar_to_utf8 (value, utf8);
        py_obj = PyUnicode_FromStringAndSize ((char *)utf8, bytes);
    } else {
        /* TODO: Convert the error to an exception. */
        PyErr_Format (PyExc_TypeError,
                      "Invalid unicode codepoint %" G_GUINT32_FORMAT, value);
    }

    return py_obj;
}

static gboolean
pygi_gtype_from_py (PyObject *py_arg, GType *type)
{
    GType temp = pyg_type_from_object (py_arg);

    if (temp == 0) {
        if (!PyErr_Occurred ()) {
            PyErr_SetString (PyExc_ValueError, "Invalid GType");
            return FALSE;
        }
        PyErr_Format (PyExc_TypeError, "Must be GObject.GType, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    *type = temp;

    return TRUE;
}

gboolean
pygi_utf8_from_py (PyObject *py_arg, gchar **result)
{
    gchar *string_;

    if (Py_IsNone (py_arg)) {
        *result = NULL;
        return TRUE;
    }

    if (PyUnicode_Check (py_arg)) {
        PyObject *pystr_obj = PyUnicode_AsUTF8String (py_arg);
        if (!pystr_obj) return FALSE;

        string_ = g_strdup (PyBytes_AsString (pystr_obj));
        Py_DECREF (pystr_obj);
    } else {
        PyErr_Format (PyExc_TypeError, "Must be string, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    *result = string_;
    return TRUE;
}

G_GNUC_UNUSED
static gboolean
filename_from_py_unix (PyObject *py_arg, gchar **result)
{
    gchar *filename;

    if (Py_IsNone (py_arg)) {
        *result = NULL;
        return TRUE;
    }

    if (PyBytes_Check (py_arg)) {
        char *buffer;

        if (PyBytes_AsStringAndSize (py_arg, &buffer, NULL) == -1)
            return FALSE;

        filename = g_strdup (buffer);
    } else if (PyUnicode_Check (py_arg)) {
        PyObject *bytes;
        char *buffer;

        bytes = PyUnicode_EncodeFSDefault (py_arg);

        if (!bytes) return FALSE;

        if (PyBytes_AsStringAndSize (bytes, &buffer, NULL) == -1) {
            Py_DECREF (bytes);
            return FALSE;
        }

        filename = g_strdup (buffer);
        Py_DECREF (bytes);
    } else {
        PyErr_Format (PyExc_TypeError, "Must be bytes, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    *result = filename;
    return TRUE;
}

G_GNUC_UNUSED
static gboolean
filename_from_py_win32 (PyObject *py_arg, gchar **result)
{
    gchar *filename;

    if (Py_IsNone (py_arg)) {
        *result = NULL;
        return TRUE;
    }

    if (PyBytes_Check (py_arg)) {
        PyObject *uni_arg;
        gboolean temp_result;
        char *buffer;

        if (PyBytes_AsStringAndSize (py_arg, &buffer, NULL) == -1)
            return FALSE;

        uni_arg = PyUnicode_DecodeFSDefault (buffer);
        if (!uni_arg) return FALSE;
        temp_result = filename_from_py_win32 (uni_arg, result);
        Py_DECREF (uni_arg);
        return temp_result;
    } else if (PyUnicode_Check (py_arg)) {
        PyObject *bytes, *temp_uni;
        char *buffer;

        /* The roundtrip merges lone surrogates, so we get the same output as
         * with Py 2. Requires 3.4+ because of https://bugs.python.org/issue27971
         * Separated lone surrogates can occur when concatenating two paths.
         */
        bytes =
            PyUnicode_AsEncodedString (py_arg, "utf-16-le", "surrogatepass");
        if (!bytes) return FALSE;
        temp_uni =
            PyUnicode_FromEncodedObject (bytes, "utf-16-le", "surrogatepass");
        Py_DECREF (bytes);
        if (!temp_uni) return FALSE;
        /* glib uses utf-8, so encode to that and allow surrogates so we can
         * represent all possible path values
         */
        bytes = PyUnicode_AsEncodedString (temp_uni, "utf-8", "surrogatepass");
        Py_DECREF (temp_uni);
        if (!bytes) return FALSE;

        if (PyBytes_AsStringAndSize (bytes, &buffer, NULL) == -1) {
            Py_DECREF (bytes);
            return FALSE;
        }

        filename = g_strdup (buffer);
        Py_DECREF (bytes);
    } else {
        PyErr_Format (PyExc_TypeError, "Must be str, not %s",
                      Py_TYPE (py_arg)->tp_name);
        return FALSE;
    }

    *result = filename;
    return TRUE;
}

static gboolean
pygi_filename_from_py (PyObject *py_arg, gchar **result)
{
#ifdef G_OS_WIN32
    return filename_from_py_win32 (py_arg, result);
#else
    return filename_from_py_unix (py_arg, result);
#endif
}

static PyObject *
base_number_checks (PyObject *object)
{
    PyObject *number;

    if (!PyNumber_Check (object)) {
        PyErr_Format (PyExc_TypeError, "Must be number, not %s",
                      Py_TYPE (object)->tp_name);
        return NULL;
    }

    number = PyNumber_Long (object);

    if (number == NULL) {
        PyErr_SetString (PyExc_TypeError, "expected int argument");
        return NULL;
    }

    return number;
}

gboolean
pygi_gboolean_from_py (PyObject *object, gboolean *result)
{
    int value = PyObject_IsTrue (object);
    if (value == -1) return FALSE;
    *result = (gboolean)value;
    return TRUE;
}

PyObject *
pygi_gboolean_to_py (gboolean value)
{
    return PyBool_FromLong (value);
}

/* A super set of pygi_gint8_from_py (also handles unicode) */
gboolean
pygi_gschar_from_py (PyObject *object, gint8 *result)
{
    if (PyUnicode_Check (object)) {
        gunichar uni;
        PyObject *temp;
        gboolean status;

        if (!pygi_gunichar_from_py (object, &uni)) return FALSE;

        temp = pygi_guint32_to_py (uni);
        status = pygi_gint8_from_py (temp, result);
        Py_DECREF (temp);
        return status;
    } else {
        /* pygi_gint8_from_py handles numbers and bytes */
        return pygi_gint8_from_py (object, result);
    }

    return FALSE;
}

/* A super set of pygi_guint8_from_py (also handles unicode) */
gboolean
pygi_guchar_from_py (PyObject *object, guchar *result)
{
    if (PyUnicode_Check (object)) {
        gunichar uni;
        PyObject *temp;
        gboolean status;
        gint8 codepoint;

        if (!pygi_gunichar_from_py (object, &uni)) return FALSE;

        temp = pygi_guint32_to_py (uni);
        status = pygi_gint8_from_py (temp, &codepoint);
        Py_DECREF (temp);
        if (status) *result = (guchar)codepoint;
        return status;
    } else {
        /* pygi_guint8_from_py handles numbers and bytes */
        return pygi_guint8_from_py (object, result);
    }
}

gboolean
pygi_gint_from_py (PyObject *object, gint *result)
{
    long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < G_MININT || long_value > G_MAXINT) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %d to %d", number,
                  (int)G_MININT, (int)G_MAXINT);
    Py_DECREF (number);
    return FALSE;
}

PyObject *
pygi_gint_to_py (gint value)
{
    return PyLong_FromLong (value);
}

gboolean
pygi_guint_from_py (PyObject *object, guint *result)
{
    unsigned long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsUnsignedLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value > G_MAXUINT) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %lu", number,
                  (long)0, (unsigned long)G_MAXUINT);
    Py_DECREF (number);
    return FALSE;
}

PyObject *
pygi_guint_to_py (guint value)
{
#if (G_MAXUINT <= LONG_MAX)
    return PyLong_FromLong ((long)value);
#else
    if (value <= LONG_MAX) return PyLong_FromLong ((long)value);
    return PyLong_FromUnsignedLong (value);
#endif
}

gboolean
pygi_glong_from_py (PyObject *object, glong *result)
{
    long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    }

    Py_DECREF (number);
    *result = (glong)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)G_MINLONG, (long)G_MAXLONG);
    Py_DECREF (number);
    return FALSE;
}

PyObject *
pygi_glong_to_py (glong value)
{
    return PyLong_FromLong (value);
}

gboolean
pygi_gulong_from_py (PyObject *object, gulong *result)
{
    unsigned long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsUnsignedLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    }

    Py_DECREF (number);
    *result = (gulong)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %lu", number,
                  (long)0, (unsigned long)G_MAXULONG);
    Py_DECREF (number);
    return FALSE;
}

PyObject *
pygi_gulong_to_py (gulong value)
{
    if (value <= LONG_MAX)
        return PyLong_FromLong ((long)value);
    else
        return PyLong_FromUnsignedLong (value);
}

static gboolean
pygi_gint8_from_py_converted (PyObject *object, gint8 *result)
{
    long long_value;
    PyObject *number;

    if (PyBytes_Check (object)) {
        if (PyBytes_Size (object) != 1) {
            PyErr_Format (PyExc_TypeError, "Must be a single character");
            return FALSE;
        }

        *result = (gint8)(PyBytes_AsString (object)[0]);
        return TRUE;
    }

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < G_MININT8 || long_value > G_MAXINT8) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint8)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)G_MININT8, (long)G_MAXINT8);
    Py_DECREF (number);
    return FALSE;
}

static gboolean
pygi_gint8_from_py (PyObject *object, gint8 *result)
{
    /* If we pass unicode, convert it to a bytes representation
     * and then continue. */
    if (PyUnicode_Check (object)) {
        PyObject *bytes_object = PyUnicode_AsUTF8String (object);

        if (!bytes_object) {
            PyErr_Format (PyExc_TypeError,
                          "Could not convert unicode string to bytes");
            return FALSE;
        }

        gboolean ret = pygi_gint8_from_py_converted (bytes_object, result);
        Py_DECREF (bytes_object);

        return ret;
    }

    return pygi_gint8_from_py_converted (object, result);
}

static PyObject *
pygi_gint8_to_py (gint8 value)
{
    return PyLong_FromLong (value);
}

static gboolean
pygi_guint8_from_py_converted (PyObject *object, guint8 *result)
{
    long long_value;
    PyObject *number;

    if (PyBytes_Check (object)) {
        if (PyBytes_Size (object) != 1) {
            PyErr_Format (PyExc_TypeError, "Must be a single character");
            return FALSE;
        }

        *result = (guint8)(PyBytes_AsString (object)[0]);
        return TRUE;
    }

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < 0 || long_value > G_MAXUINT8) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (guint8)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)0, (long)G_MAXUINT8);
    Py_DECREF (number);
    return FALSE;
}

static gboolean
pygi_guint8_from_py (PyObject *object, guint8 *result)
{
    /* If we pass unicode, convert it to a bytes representation
     * and then continue */
    if (PyUnicode_Check (object)) {
        PyObject *bytes_object = PyUnicode_AsUTF8String (object);

        if (!bytes_object) {
            PyErr_Format (PyExc_TypeError,
                          "Could not convert unicode string to bytes");
            return FALSE;
        }

        gboolean ret = pygi_guint8_from_py_converted (bytes_object, result);
        Py_DECREF (bytes_object);

        return ret;
    }

    return pygi_guint8_from_py_converted (object, result);
}

static PyObject *
pygi_guint8_to_py (guint8 value)
{
    return PyLong_FromLong (value);
}

static gboolean
pygi_gint16_from_py (PyObject *object, gint16 *result)
{
    long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < G_MININT16 || long_value > G_MAXINT16) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint16)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)G_MININT16, (long)G_MAXINT16);
    Py_DECREF (number);
    return FALSE;
}

static PyObject *
pygi_gint16_to_py (gint16 value)
{
    return PyLong_FromLong (value);
}

static gboolean
pygi_guint16_from_py (PyObject *object, guint16 *result)
{
    long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < 0 || long_value > G_MAXUINT16) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (guint16)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)0, (long)G_MAXUINT16);
    Py_DECREF (number);
    return FALSE;
}

static PyObject *
pygi_guint16_to_py (guint16 value)
{
    return PyLong_FromLong (value);
}

static gboolean
pygi_gint32_from_py (PyObject *object, gint32 *result)
{
    long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLong (number);
    if (long_value == -1 && PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < G_MININT32 || long_value > G_MAXINT32) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint32)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %ld", number,
                  (long)G_MININT32, (long)G_MAXINT32);
    Py_DECREF (number);
    return FALSE;
}

static PyObject *
pygi_gint32_to_py (gint32 value)
{
    return PyLong_FromLong (value);
}

static gboolean
pygi_guint32_from_py (PyObject *object, guint32 *result)
{
    long long long_value;
    PyObject *number;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLongLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < 0 || long_value > G_MAXUINT32) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (guint32)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %lu", number,
                  (long)0, (unsigned long)G_MAXUINT32);
    Py_DECREF (number);
    return FALSE;
}

PyObject *
pygi_guint32_to_py (guint32 value)
{
#if (G_MAXUINT <= LONG_MAX)
    return PyLong_FromLong (value);
#else
    if (value <= LONG_MAX)
        return PyLong_FromLong ((long)value);
    else
        return PyLong_FromLongLong (value);
#endif
}

gboolean
pygi_gint64_from_py (PyObject *object, gint64 *result)
{
    long long long_value;
    PyObject *number, *min, *max;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsLongLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value < G_MININT64 || long_value > G_MAXINT64) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (gint64)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    min = pygi_gint64_to_py (G_MININT64);
    max = pygi_gint64_to_py (G_MAXINT64);
    PyErr_Format (PyExc_OverflowError, "%S not in range %S to %S", number, min,
                  max);
    Py_DECREF (number);
    Py_DECREF (min);
    Py_DECREF (max);
    return FALSE;
}

PyObject *
pygi_gint64_to_py (gint64 value)
{
    if (LONG_MIN <= value && value <= LONG_MAX)
        return PyLong_FromLong ((long)value);
    else
        return PyLong_FromLongLong (value);
}

gboolean
pygi_guint64_from_py (PyObject *object, guint64 *result)
{
    unsigned long long long_value;
    PyObject *number, *max;

    number = base_number_checks (object);
    if (number == NULL) return FALSE;

    long_value = PyLong_AsUnsignedLongLong (number);
    if (PyErr_Occurred ()) {
        if (PyErr_ExceptionMatches (PyExc_OverflowError)) goto overflow;
        Py_DECREF (number);
        return FALSE;
    } else if (long_value > G_MAXUINT64) {
        goto overflow;
    }

    Py_DECREF (number);
    *result = (guint64)long_value;
    return TRUE;

overflow:
    PyErr_Clear ();
    max = pygi_guint64_to_py (G_MAXUINT64);
    PyErr_Format (PyExc_OverflowError, "%S not in range %ld to %S", number,
                  (long)0, max);
    Py_DECREF (number);
    Py_DECREF (max);
    return FALSE;
}

PyObject *
pygi_guint64_to_py (guint64 value)
{
    if (value <= LONG_MAX)
        return PyLong_FromLong ((long)value);
    else
        return PyLong_FromUnsignedLongLong (value);
}

GIArgument
pygi_marshal_from_py_basic_type (PyObject *object, /* in */
                                 GITypeTag type_tag, GITransfer transfer,
                                 gpointer *cleanup_data /* out */)
{
    GIArgument arg = PYGI_ARG_INIT;
    gboolean converted = FALSE;

    switch (type_tag) {
    case GI_TYPE_TAG_VOID:
        g_warn_if_fail (transfer == GI_TRANSFER_NOTHING);
        if (pygi_gpointer_from_py (object, &(arg.v_pointer))) {
            *cleanup_data = arg.v_pointer;
            converted = TRUE;
        }
        break;

    case GI_TYPE_TAG_INT8:
        converted = pygi_gint8_from_py (object, &(arg.v_int8));
        break;
    case GI_TYPE_TAG_UINT8:
        converted = pygi_guint8_from_py (object, &(arg.v_uint8));
        break;
    case GI_TYPE_TAG_INT16:
        converted = pygi_gint16_from_py (object, &(arg.v_int16));
        break;
    case GI_TYPE_TAG_UINT16:
        converted = pygi_guint16_from_py (object, &(arg.v_uint16));
        break;
    case GI_TYPE_TAG_INT32:
        converted = pygi_gint32_from_py (object, &(arg.v_int32));
        break;
    case GI_TYPE_TAG_UINT32:
        converted = pygi_guint32_from_py (object, &(arg.v_uint32));
        break;
    case GI_TYPE_TAG_INT64:
        converted = pygi_gint64_from_py (object, &(arg.v_int64));
        break;
    case GI_TYPE_TAG_UINT64:
        converted = pygi_guint64_from_py (object, &(arg.v_uint64));
        break;
    case GI_TYPE_TAG_BOOLEAN:
        converted = pygi_gboolean_from_py (object, &(arg.v_boolean));
        break;
    case GI_TYPE_TAG_FLOAT:
        converted = pygi_gfloat_from_py (object, &(arg.v_float));
        break;
    case GI_TYPE_TAG_DOUBLE:
        converted = pygi_gdouble_from_py (object, &(arg.v_double));
        break;
    case GI_TYPE_TAG_GTYPE:
        converted = pygi_gtype_from_py (object, &(arg.v_size));
        break;
    case GI_TYPE_TAG_UNICHAR:
        converted = pygi_gunichar_from_py (object, &(arg.v_uint32));
        break;
    case GI_TYPE_TAG_UTF8:
        if (pygi_utf8_from_py (object, &(arg.v_string))) {
            *cleanup_data = arg.v_string;
            converted = TRUE;
        }
        break;

    case GI_TYPE_TAG_FILENAME:
        if (pygi_filename_from_py (object, &(arg.v_string))) {
            *cleanup_data = arg.v_string;
            converted = TRUE;
        }
        break;

    case GI_TYPE_TAG_ARRAY:
    case GI_TYPE_TAG_INTERFACE:
    case GI_TYPE_TAG_GLIST:
    case GI_TYPE_TAG_GSLIST:
    case GI_TYPE_TAG_GHASH:
    case GI_TYPE_TAG_ERROR:
        PyErr_Format (PyExc_TypeError, "Type tag %s (%d) not supported",
                      gi_type_tag_to_string (type_tag), type_tag);
        break;
    default:
        g_assert_not_reached ();
    }

    if (!converted && !PyErr_Occurred ()) {
        PyErr_Format (PyExc_TypeError,
                      "Object %R Could not be converted type tag %s", object,
                      gi_type_tag_to_string (type_tag));
    }

    return arg;
}

PyObject *
pygi_utf8_to_py (gchar *value)
{
    if (value == NULL) {
        Py_RETURN_NONE;
    }

    return PyUnicode_FromString (value);
}

PyObject *
pygi_filename_to_py (gchar *value)
{
    PyObject *py_obj;

    if (value == NULL) {
        Py_RETURN_NONE;
    }

#ifdef G_OS_WIN32
    py_obj = PyUnicode_DecodeUTF8 (value, strlen (value), "surrogatepass");
#else
    py_obj = PyUnicode_DecodeFSDefault (value);
#endif

    return py_obj;
}

/**
 * pygi_marshal_to_py_basic_type:
 * @arg: The argument to convert to an object.
 * @type_tag: Type tag for @arg
 * @transfer: Transfer annotation
 *
 * Convert the given argument to a Python object. This function
 * is restricted to simple types that only require the GITypeTag
 * and GITransfer. For a more complete conversion routine, use:
 * pygi_argument_to_py.
 *
 * Returns: A PyObject representing @arg or NULL if it cannot convert
 *          the argument.
 */
PyObject *
pygi_marshal_to_py_basic_type (GIArgument arg, GITypeTag type_tag,
                               GITransfer transfer)
{
    switch (type_tag) {
    case GI_TYPE_TAG_BOOLEAN:
        return pygi_gboolean_to_py (arg.v_boolean);

    case GI_TYPE_TAG_INT8:
        return pygi_gint8_to_py (arg.v_int8);

    case GI_TYPE_TAG_UINT8:
        return pygi_guint8_to_py (arg.v_uint8);

    case GI_TYPE_TAG_INT16:
        return pygi_gint16_to_py (arg.v_int16);

    case GI_TYPE_TAG_UINT16:
        return pygi_guint16_to_py (arg.v_uint16);

    case GI_TYPE_TAG_INT32:
        return pygi_gint32_to_py (arg.v_int32);

    case GI_TYPE_TAG_UINT32:
        return pygi_guint32_to_py (arg.v_uint32);

    case GI_TYPE_TAG_INT64:
        return pygi_gint64_to_py (arg.v_int64);

    case GI_TYPE_TAG_UINT64:
        return pygi_guint64_to_py (arg.v_uint64);

    case GI_TYPE_TAG_FLOAT:
        return pygi_gfloat_to_py (arg.v_float);

    case GI_TYPE_TAG_DOUBLE:
        return pygi_gdouble_to_py (arg.v_double);

    case GI_TYPE_TAG_GTYPE:
        return pyg_type_wrapper_new ((GType)arg.v_size);

    case GI_TYPE_TAG_UNICHAR:
        return pygi_gunichar_to_py (arg.v_uint32);

    case GI_TYPE_TAG_UTF8:
        return pygi_utf8_to_py (arg.v_string);

    case GI_TYPE_TAG_FILENAME:
        return pygi_filename_to_py (arg.v_string);

    case GI_TYPE_TAG_VOID:
    case GI_TYPE_TAG_ARRAY:
    case GI_TYPE_TAG_INTERFACE:
    case GI_TYPE_TAG_GLIST:
    case GI_TYPE_TAG_GSLIST:
    case GI_TYPE_TAG_GHASH:
    case GI_TYPE_TAG_ERROR:
        PyErr_Format (PyExc_TypeError, "Type tag %d not supported", type_tag);
        return NULL;
    default:
        g_assert_not_reached ();
    }
}
