/* SPDX-License-Identifier: BSD-2-Clause */

/*
 * The implementation of the Python object to C/C++ integer convertors.
 *
 * Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>
 */


/*
 * NOTES
 *
 * The legacy integer conversions (ie. without support for overflow checking)
 * are flawed and inconsistent.  Large Python signed values were converted to
 * -1 whereas small values were truncated.  When converting function arguments
 * all overlows were ignored, however when converting the results returned by
 * Python re-implementations then large Python values raised an exception
 * whereas small values were truncated.
 *
 * With the new integer conversions large Python signed values will always
 * raise an overflow exception (even if overflow checking is disabled).  This
 * is because a truncated value is not available - it would have to be
 * computed.  This may cause new exceptions to be raised but is justified in
 * that the value that was being used bore no relation to the original value.
 */


/* Remove when Python v3.12 is no longer supported. */
#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include <limits.h>

#include "sipint.h"


/* Wrappers to deal with lack of long long support. */
#if defined(HAVE_LONG_LONG)
#define SIPLong_AsLongLong              PyLong_AsLongLong
#define SIP_LONG_LONG                   PY_LONG_LONG
#define SIP_LONG_LONG_FORMAT            "%lld"
#define SIP_UNSIGNED_LONG_LONG_FORMAT   "%llu"
#else
#define SIPLong_AsLongLong              PyLong_AsLong
#define SIP_LONG_LONG                   long
#define SIP_LONG_LONG_FORMAT            "%ld"
#define SIP_UNSIGNED_LONG_LONG_FORMAT   "%lu"
#endif


static int overflow_checking = FALSE;   /* Check for overflows. */

static SIP_LONG_LONG long_as_long_long(PyObject *o, SIP_LONG_LONG min,
        SIP_LONG_LONG max);
static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max);
static void raise_signed_overflow(SIP_LONG_LONG min, SIP_LONG_LONG max);
static void raise_unsigned_overflow(unsigned SIP_LONG_LONG max);


/*
 * Enable or disable overflow checking (Python API).
 */
PyObject *sipEnableOverflowChecking(PyObject *self, PyObject *args)
{
    int enable;

    (void)self;

    if (PyArg_ParseTuple(args, "i:enableoverflowchecking", &enable))
    {
        PyObject *res;

        res = (sip_api_enable_overflow_checking(enable) ? Py_True : Py_False);

        Py_INCREF(res);
        return res;
    }

    return NULL;
}


/*
 * Enable or disable overflow checking (C API).
 */
int sip_api_enable_overflow_checking(int enable)
{
    int was_enabled = overflow_checking;

    overflow_checking = enable;

    return was_enabled;
}


/*
 * Convert a Python object to a C++ bool (returned as an int).
 */
int sip_api_convert_to_bool(PyObject *o)
{
    int was_enabled, v;

    /* Convert the object to an int while checking for overflow. */
    was_enabled = sip_api_enable_overflow_checking(TRUE);
    v = sip_api_long_as_int(o);
    sip_api_enable_overflow_checking(was_enabled);

    if (PyErr_Occurred())
    {
        if (PyErr_ExceptionMatches(PyExc_OverflowError))
        {
            PyErr_Clear();

            /* The value must have been non-zero. */
            v = 1;
        }
        else
        {
            PyErr_Format(PyExc_TypeError, "a 'bool' is expected not '%s'",
                    Py_TYPE(o)->tp_name);

            v = -1;
        }
    }
    else if (v != 0)
    {
        v = 1;
    }

    return v;
}


/*
 * Convert a Python object to a C char.
 */
char sip_api_long_as_char(PyObject *o)
{
    return (char)long_as_long_long(o, CHAR_MIN, CHAR_MAX);
}


/*
 * Convert a Python object to a C signed char.
 */
signed char sip_api_long_as_signed_char(PyObject *o)
{
    return (signed char)long_as_long_long(o, SCHAR_MIN, SCHAR_MAX);
}


/*
 * Convert a Python object to a C unsigned char.
 */
unsigned char sip_api_long_as_unsigned_char(PyObject *o)
{
    return (unsigned char)long_as_unsigned_long(o, UCHAR_MAX);
}


/*
 * Convert a Python object to a C short.
 */
short sip_api_long_as_short(PyObject *o)
{
    return (short)long_as_long_long(o, SHRT_MIN, SHRT_MAX);
}


/*
 * Convert a Python object to a C unsigned short.
 */
unsigned short sip_api_long_as_unsigned_short(PyObject *o)
{
    return (unsigned short)long_as_unsigned_long(o, USHRT_MAX);
}


/*
 * Convert a Python object to a C int.
 */
int sip_api_long_as_int(PyObject *o)
{
    return (int)long_as_long_long(o, INT_MIN, INT_MAX);
}


/*
 * Convert a Python object to a C unsigned int.
 */
unsigned sip_api_long_as_unsigned_int(PyObject *o)
{
    return (unsigned)long_as_unsigned_long(o, UINT_MAX);
}


/*
 * Convert a Python object to a C size_t.
 */
size_t sip_api_long_as_size_t(PyObject *o)
{
    return (size_t)long_as_unsigned_long(o, SIZE_MAX);
}


/*
 * Convert a Python object to a C long.
 */
long sip_api_long_as_long(PyObject *o)
{
    return (long)long_as_long_long(o, LONG_MIN, LONG_MAX);
}


/*
 * Convert a Python object to a C unsigned long.
 */
unsigned long sip_api_long_as_unsigned_long(PyObject *o)
{
    return long_as_unsigned_long(o, ULONG_MAX);
}


#if defined(HAVE_LONG_LONG)
/*
 * Convert a Python object to a C long long.
 */
PY_LONG_LONG sip_api_long_as_long_long(PyObject *o)
{
    return long_as_long_long(o, LLONG_MIN, LLONG_MAX);
}


/*
 * Convert a Python object to a C unsigned long long.
 */
unsigned PY_LONG_LONG sip_api_long_as_unsigned_long_long(PyObject *o)
{
    unsigned PY_LONG_LONG value;

    /*
     * Note that this doesn't handle Python v2 int objects, but the old
     * convertors didn't either.
     */

    PyErr_Clear();

    if (overflow_checking)
    {
        value = PyLong_AsUnsignedLongLong(o);

        if (PyErr_Occurred())
        {
            /* Provide a better exception message. */
            if (PyErr_ExceptionMatches(PyExc_OverflowError))
                raise_unsigned_overflow(ULLONG_MAX);
        }
    }
    else
    {
        value = PyLong_AsUnsignedLongLongMask(o);
    }

    return value;
}
#endif


/*
 * Convert a Python object to a long long checking that the value is within a
 * range if overflow checking is enabled.
 */
static SIP_LONG_LONG long_as_long_long(PyObject *o, SIP_LONG_LONG min,
        SIP_LONG_LONG max)
{
    SIP_LONG_LONG value;

    PyErr_Clear();

    value = SIPLong_AsLongLong(o);

    if (PyErr_Occurred())
    {
        /* Provide a better exception message. */
        if (PyErr_ExceptionMatches(PyExc_OverflowError))
            raise_signed_overflow(min, max);
    }
    else if (overflow_checking && (value < min || value > max))
    {
        raise_signed_overflow(min, max);
    }

    return value;
}


/*
 * Convert a Python object to an unsigned long checking that the value is
 * within a range if overflow checking is enabled.
 */
static unsigned long long_as_unsigned_long(PyObject *o, unsigned long max)
{
    unsigned long value;

    PyErr_Clear();

    if (overflow_checking)
    {
        value = PyLong_AsUnsignedLong(o);

        if (PyErr_Occurred())
        {
            /* Provide a better exception message. */
            if (PyErr_ExceptionMatches(PyExc_OverflowError))
                raise_unsigned_overflow(max);
        }
        else if (value > max)
        {
            raise_unsigned_overflow(max);
        }
    }
    else
    {
        value = PyLong_AsUnsignedLongMask(o);
    }

    return value;
}


/*
 * Raise an overflow exception if a signed value is out of range.
 */
static void raise_signed_overflow(SIP_LONG_LONG min, SIP_LONG_LONG max)
{
    PyErr_Format(PyExc_OverflowError,
            "value must be in the range " SIP_LONG_LONG_FORMAT  " to " SIP_LONG_LONG_FORMAT,
            min, max);
}


/*
 * Raise an overflow exception if an unsigned value is out of range.
 */
static void raise_unsigned_overflow(unsigned SIP_LONG_LONG max)
{
    PyErr_Format(PyExc_OverflowError,
            "value must be in the range 0 to " SIP_UNSIGNED_LONG_LONG_FORMAT,
            max);
}
