from cpython.object cimport PyTypeObject


cdef extern from "Python.h":
    # Note: importing extern-style allows us to declare these as nogil
    # functions, whereas `from cpython cimport` does not.
    bint PyBool_Check(object obj) nogil
    bint PyFloat_Check(object obj) nogil
    bint PyComplex_Check(object obj) nogil
    bint PyObject_TypeCheck(object obj, PyTypeObject* type) nogil

    # TODO(cython3): cimport this, xref GH#49670
    # Note that following functions can potentially raise an exception,
    # thus they cannot be declared 'nogil'. Also PyUnicode_AsUTF8AndSize() can
    # potentially allocate memory inside in unlikely case of when underlying
    # unicode object was stored as non-utf8 and utf8 wasn't requested before.
    const char* PyUnicode_AsUTF8AndSize(object obj,
                                        Py_ssize_t* length) except NULL

    object PyUnicode_EncodeLocale(object obj, const char *errors) nogil
    object PyUnicode_DecodeLocale(const char *str, const char *errors) nogil


from numpy cimport (
    float64_t,
    int64_t,
)


cdef extern from "numpy/arrayobject.h":
    PyTypeObject PyFloatingArrType_Type

cdef extern from "numpy/ndarrayobject.h":
    PyTypeObject PyTimedeltaArrType_Type
    PyTypeObject PyDatetimeArrType_Type
    PyTypeObject PyComplexFloatingArrType_Type
    PyTypeObject PyBoolArrType_Type

    bint PyArray_IsIntegerScalar(obj) nogil
    bint PyArray_Check(obj) nogil

cdef extern from "numpy/npy_common.h":
    int64_t NPY_MIN_INT64


cdef inline int64_t get_nat() noexcept:
    return NPY_MIN_INT64


# --------------------------------------------------------------------
# Type Checking

cdef inline bint is_integer_object(object obj) noexcept nogil:
    """
    Cython equivalent of

    `isinstance(val, (int, long, np.integer)) and not isinstance(val, bool)`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_integer : bool

    Notes
    -----
    This counts np.timedelta64 objects as integers.
    """
    return (not PyBool_Check(obj) and PyArray_IsIntegerScalar(obj)
            and not is_timedelta64_object(obj))


cdef inline bint is_float_object(object obj) noexcept nogil:
    """
    Cython equivalent of `isinstance(val, (float, np.float64))`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_float : bool
    """
    return (PyFloat_Check(obj) or
            (PyObject_TypeCheck(obj, &PyFloatingArrType_Type)))


cdef inline bint is_complex_object(object obj) noexcept nogil:
    """
    Cython equivalent of `isinstance(val, (complex, np.complex128))`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_complex : bool
    """
    return (PyComplex_Check(obj) or
            PyObject_TypeCheck(obj, &PyComplexFloatingArrType_Type))


cdef inline bint is_bool_object(object obj) noexcept nogil:
    """
    Cython equivalent of `isinstance(val, (bool, np.bool_))`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_bool : bool
    """
    return (PyBool_Check(obj) or
            PyObject_TypeCheck(obj, &PyBoolArrType_Type))


cdef inline bint is_real_number_object(object obj) noexcept nogil:
    return is_bool_object(obj) or is_integer_object(obj) or is_float_object(obj)


cdef inline bint is_timedelta64_object(object obj) noexcept nogil:
    """
    Cython equivalent of `isinstance(val, np.timedelta64)`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_timedelta64 : bool
    """
    return PyObject_TypeCheck(obj, &PyTimedeltaArrType_Type)


cdef inline bint is_datetime64_object(object obj) noexcept nogil:
    """
    Cython equivalent of `isinstance(val, np.datetime64)`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_datetime64 : bool
    """
    return PyObject_TypeCheck(obj, &PyDatetimeArrType_Type)


cdef inline bint is_array(object val) noexcept:
    """
    Cython equivalent of `isinstance(val, np.ndarray)`

    Parameters
    ----------
    val : object

    Returns
    -------
    is_ndarray : bool
    """
    return PyArray_Check(val)


cdef inline bint is_nan(object val):
    """
    Check if val is a Not-A-Number float or complex, including
    float('NaN') and np.nan.

    Parameters
    ----------
    val : object

    Returns
    -------
    is_nan : bool
    """
    cdef float64_t fval
    if is_float_object(val):
        fval = val
        return fval != fval
    return is_complex_object(val) and val != val


cdef inline const char* get_c_string_buf_and_size(str py_string,
                                                  Py_ssize_t *length) except NULL:
    """
    Extract internal char* buffer of unicode or bytes object `py_string` with
    getting length of this internal buffer saved in `length`.

    Notes
    -----
    Python object owns memory, thus returned char* must not be freed.
    `length` can be NULL if getting buffer length is not needed.

    Parameters
    ----------
    py_string : str
    length : Py_ssize_t*

    Returns
    -------
    buf : const char*
    """
    return PyUnicode_AsUTF8AndSize(py_string, length)


cdef inline const char* get_c_string(str py_string) except NULL:
    return get_c_string_buf_and_size(py_string, NULL)


cdef inline bytes string_encode_locale(str py_string) noexcept:
    """As opposed to PyUnicode_Encode, use current system locale to encode."""
    return PyUnicode_EncodeLocale(py_string, NULL)


cdef inline object char_to_string_locale(const char* data) noexcept:
    """As opposed to PyUnicode_FromString, use current system locale to decode."""
    return PyUnicode_DecodeLocale(data, NULL)