#ifndef __G_ARITHMETIC_H__
#define __G_ARITHMETIC_H__

#include <glib.h>

/* addition */

static inline gboolean g_int_add        (gint *res, gint a, gint b);
static inline gboolean g_long_add       (glong *res, glong a, glong b);
static inline gboolean g_int32_add      (gint32 *res, gint32 a, gint32 b);
static inline gboolean g_int64_add      (gint64 *res, gint64 a, gint64 b);

static inline gboolean g_size_add       (gsize *res, gsize a, gsize b);
static inline gboolean g_uint_add       (guint *res, guint a, guint b);
static inline gboolean g_ulong_add      (gulong *res, gulong a, gulong b);
static inline gboolean g_uint32_add     (guint32 *res, guint32 a, guint32 b);
static inline gboolean g_uint64_add     (guint64 *res, guint64 a, guint64 b);

static inline gint     g_int_add_sat    (gint a, gint b);
static inline glong    g_long_add_sat   (glong a, glong b);
static inline gint32   g_int32_add_sat  (gint32 a, gint32 b);
static inline gint64   g_int64_add_sat  (gint64 a, gint64 b);

static inline gsize    g_size_add_sat   (gsize a, gsize b);
static inline guint    g_uint_add_sat   (guint a, guint b);
static inline gulong   g_ulong_add_sat  (gulong a, gulong b);
static inline guint32  g_uint32_add_sat (guint32 a, guint32 b);
static inline guint64  g_uint64_add_sat (guint64 a, guint64 b);

/*
 * Generic (GCC/Clang only):
 *  gboolean g_add (TYPE *res, TYPE a, TYPE b)
 *  void g_add_sat (TYPE *res, TYPE a, TYPE b)
 */

/* subtraction */

static inline gboolean g_int_sub        (gint *res, gint a, gint b);
static inline gboolean g_long_sub       (glong *res, glong a, glong b);
static inline gboolean g_int32_sub      (gint32 *res, gint32 a, gint32 b);
static inline gboolean g_int64_sub      (gint64 *res, gint64 a, gint64 b);

static inline gboolean g_size_sub       (gsize *res, gsize a, gsize b);
static inline gboolean g_uint_sub       (guint *res, guint a, guint b);
static inline gboolean g_ulong_sub      (gulong *res, gulong a, gulong b);
static inline gboolean g_uint32_sub     (guint32 *res, guint32 a, guint32 b);
static inline gboolean g_uint64_sub     (guint64 *res, guint64 a, guint64 b);

static inline gint     g_int_sub_sat    (gint a, gint b);
static inline glong    g_long_sub_sat   (glong a, glong b);
static inline gint32   g_int32_sub_sat  (gint32 a, gint32 b);
static inline gint64   g_int64_sub_sat  (gint64 a, gint64 b);

static inline gsize    g_size_sub_sat   (gsize a, gsize b);
static inline guint    g_uint_sub_sat   (guint a, guint b);
static inline gulong   g_ulong_sub_sat  (gulong a, gulong b);
static inline guint32  g_uint32_sub_sat (guint32 a, guint32 b);
static inline guint64  g_uint64_sub_sat (guint64 a, guint64 b);

/*
 * Generic (GCC/Clang only):
 *  gboolean g_sub (TYPE *res, TYPE a, TYPE b)
 *  void g_sub_sat (TYPE *res, TYPE a, TYPE b)
 */

/* multiplication */

static inline gboolean g_int_mul        (gint *res, gint a, gint b);
static inline gboolean g_long_mul       (glong *res, glong a, glong b);
static inline gboolean g_int32_mul      (gint32 *res, gint32 a, gint32 b);
static inline gboolean g_int64_mul      (gint64 *res, gint64 a, gint64 b);

static inline gboolean g_size_mul       (gsize *res, gsize a, gsize b);
static inline gboolean g_uint_mul       (guint *res, guint a, guint b);
static inline gboolean g_ulong_mul      (gulong *res, gulong a, gulong b);
static inline gboolean g_uint32_mul     (guint32 *res, guint32 a, guint32 b);
static inline gboolean g_uint64_mul     (guint64 *res, guint64 a, guint64 b);

static inline gint     g_int_mul_sat    (gint a, gint b);
static inline glong    g_long_mul_sat   (glong a, glong b);
static inline gint32   g_int32_mul_sat  (gint32 a, gint32 b);
static inline gint64   g_int64_mul_sat  (gint64 a, gint64 b);

static inline gsize    g_size_mul_sat   (gsize a, gsize b);
static inline guint    g_uint_mul_sat   (guint a, guint b);
static inline gulong   g_ulong_mul_sat  (gulong a, gulong b);
static inline guint32  g_uint32_mul_sat (guint32 a, guint32 b);
static inline guint64  g_uint64_mul_sat (guint64 a, guint64 b);

/*
 * Generic (GCC/Clang only):
 *  gboolean g_mul (TYPE *res, TYPE a, TYPE b)
 *  void g_mul_sat (TYPE *res, TYPE a, TYPE b)
 */

#if defined (__GNUC__)

#define _G_ARITH_UTIL_TYPECOND(type, _if, _then, _else) \
    __builtin_choose_expr (__builtin_types_compatible_p (type, _if), \
                           _then, \
                            _else)

#define _G_ARITH_UTIL_GET_GENERIC_EXPR(res, SUBFIX) \
 _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), gint, g_int_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), glong, g_long_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), gint32, g_int32_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), gint64, g_int64_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), guint, g_uint_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), gulong, g_ulong_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), guint32, g_uint32_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), guint64, g_uint64_##SUBFIX, \
    _G_ARITH_UTIL_TYPECOND (__typeof__ (*(res)), gsize, g_size_##SUBFIX, \
    (void)0 \
    )))))))))

#define _G_ARITH_UTIL_GET_GENERIC_FUNC(OP, res, a, b) \
    G_GNUC_EXTENSION ({ \
    _G_ARITH_UTIL_GET_GENERIC_EXPR (res, OP)(res, a, b); \
    })

#define _G_ARITH_UTIL_GET_GENERIC_SAT_FUNC(OP, res, a, b) \
    G_STMT_START{ \
    *(res) = _G_ARITH_UTIL_GET_GENERIC_EXPR (res, OP##_sat)(a, b); \
    }G_STMT_END

#define _G_ARITH_UTIL_GET_BUILTIN(OP, res, a, b) \
    G_GNUC_EXTENSION ({ \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), int, \
                            __builtin_s##OP##_overflow, \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), long, \
                            __builtin_s##OP##l_overflow, \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), long long, \
                            __builtin_s##OP##ll_overflow, \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), unsigned int, \
                            __builtin_u##OP##_overflow, \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), unsigned long, \
                            __builtin_u##OP##l_overflow, \
    _G_ARITH_UTIL_TYPECOND (__typeof__(*(res)), unsigned long long, \
                            __builtin_u##OP##ll_overflow, \
    (void)0 \
    ))))))(a, b, res); \
    })

#define _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER(TYPE, OP) \
    static inline gboolean g_##TYPE##_##OP(g##TYPE *res, \
                                           g##TYPE a, \
                                           g##TYPE b) \
    { \
        return !_G_ARITH_UTIL_GET_BUILTIN (OP, res, a, b); \
    }

#define _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPERS(OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (int, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (long, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (int32, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (int64, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (uint, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (ulong, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (uint32, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (uint64, OP) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPER (size, OP) \

#endif /* defined (__GNUC__) */

#define _G_ARITH_UTIL_MAP_TYPES(SMACRO, UMACRO, PREFIX) \
    SMACRO (PREFIX, int, G_MININT, G_MAXINT) \
    SMACRO (PREFIX, long, G_MINLONG, G_MAXLONG) \
    SMACRO (PREFIX, int32, G_MININT32, G_MAXINT32) \
    SMACRO (PREFIX, int64, G_MININT64, G_MAXINT64) \
    UMACRO (PREFIX, uint, G_MAXUINT) \
    UMACRO (PREFIX, ulong, G_MAXULONG) \
    UMACRO (PREFIX, uint32, G_MAXUINT32) \
    UMACRO (PREFIX, uint64, G_MAXUINT64) \
    UMACRO (PREFIX, size, G_MAXSIZE)

#ifndef __has_builtin
  #define __has_builtin(x) 0
#endif

#if ((__GNUC__ >= 5) || (__has_builtin (__builtin_sadd_overflow)))
#define _G_ARITH_UTIL_DEFINE_ALL(OP, FALLBACK_SIGNED, FALLBACK_UNSIGNED, \
                                 SAT_SIGNED, SAT_UNSIGNED) \
    _G_ARITH_UTIL_DEFINE_BUILTIN_WRAPPERS (OP) \
    _G_ARITH_UTIL_MAP_TYPES (FALLBACK_SIGNED, FALLBACK_UNSIGNED, _) \
    _G_ARITH_UTIL_MAP_TYPES (SAT_SIGNED, SAT_UNSIGNED, )
#else
#define _G_ARITH_UTIL_DEFINE_ALL(OP, FALLBACK_SIGNED, FALLBACK_UNSIGNED, \
                                 SAT_SIGNED, SAT_UNSIGNED) \
    _G_ARITH_UTIL_MAP_TYPES (FALLBACK_SIGNED, FALLBACK_UNSIGNED, ) \
    _G_ARITH_UTIL_MAP_TYPES (FALLBACK_SIGNED, FALLBACK_UNSIGNED, _) \
    _G_ARITH_UTIL_MAP_TYPES (SAT_SIGNED, SAT_UNSIGNED, )
#endif

/* g_int_add () */

#define _G_ARITH_ADD_DEFINE_FALLBACK_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_add(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if (b > 0 && (MAX - b) < a) { \
            return FALSE; \
        } \
        if (b < 0 && (MIN - b) > a) { \
            return FALSE; \
        } \
        *res = a + b; \
        return TRUE; \
    }

#define _G_ARITH_ADD_DEFINE_FALLBACK_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_add(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if ((MAX - a) < b) { \
            return FALSE; \
        } \
        *res = a + b; \
        return TRUE; \
    }

#define _G_ARITH_ADD_DEFINE_SAT_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_add_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res; \
        if (!g_##TYPE##_add (&res, a, b)) { \
            res = (b > 0) ? MAX : MIN; \
        } \
        return res; \
    }

#define _G_ARITH_ADD_DEFINE_SAT_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_add_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res;\
        if (!g_##TYPE##_add (&res, a, b)) { \
            res = MAX; \
        } \
        return res; \
    }

_G_ARITH_UTIL_DEFINE_ALL (add,
                          _G_ARITH_ADD_DEFINE_FALLBACK_SIGNED,
                          _G_ARITH_ADD_DEFINE_FALLBACK_UNSIGNED,
                          _G_ARITH_ADD_DEFINE_SAT_SIGNED,
                          _G_ARITH_ADD_DEFINE_SAT_UNSIGNED)

#if defined (__GNUC__)
#define g_add(res, a, b) _G_ARITH_UTIL_GET_GENERIC_FUNC (add, res, a, b)
#define g_add_sat(res, a, b) _G_ARITH_UTIL_GET_GENERIC_SAT_FUNC (add, res, a, b)
#endif

/* g_int_sub */

#define _G_ARITH_SUB_DEFINE_FALLBACK_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_sub(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if (b > 0 && (MIN + b) > a) { \
            return FALSE; \
        } \
        if (b < 0 && (MAX - a) > b) { \
            return FALSE; \
        } \
        *res = a - b; \
        return TRUE; \
    }

#define _G_ARITH_SUB_DEFINE_FALLBACK_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_sub(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if (b > a) { \
            return FALSE; \
        } \
        *res = a - b; \
        return TRUE; \
    }

#define _G_ARITH_SUB_DEFINE_SAT_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_sub_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res; \
        if (!g_##TYPE##_sub (&res, a, b)) { \
            res = (b < 0) ? MAX : MIN; \
        } \
        return res; \
    }

#define _G_ARITH_SUB_DEFINE_SAT_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_sub_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res; \
        if (!g_##TYPE##_sub (&res, a, b)) { \
            res = 0; \
        } \
        return res; \
    }

_G_ARITH_UTIL_DEFINE_ALL (sub,
                          _G_ARITH_SUB_DEFINE_FALLBACK_SIGNED,
                          _G_ARITH_SUB_DEFINE_FALLBACK_UNSIGNED,
                          _G_ARITH_SUB_DEFINE_SAT_SIGNED,
                          _G_ARITH_SUB_DEFINE_SAT_UNSIGNED)

#if defined (__GNUC__)
#define g_sub(res, a, b) _G_ARITH_UTIL_GET_GENERIC_FUNC (sub, res, a, b)
#define g_sub_sat(res, a, b) _G_ARITH_UTIL_GET_GENERIC_SAT_FUNC (sub, res, a, b)
#endif

/* g_int_mul */

#define _G_ARITH_MUL_DEFINE_FALLBACK_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_mul(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if (a > 0) { \
            if (b > 0 && (MAX / a) < b) { \
                return FALSE; \
            } \
            if (b < 0 && (MIN / a) > b) { \
                return FALSE; \
            } \
        } else if (a < 0) { \
            if (b < 0 && (MAX / b) > a) { \
                return FALSE; \
            } \
            if (b > 0 && (MIN / b) > a) { \
                return FALSE; \
            } \
        } \
        *res = a * b; \
        return TRUE; \
    }

#define _G_ARITH_MUL_DEFINE_FALLBACK_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline gboolean PREFIX##g_##TYPE##_mul(g##TYPE *res, g##TYPE a, g##TYPE b) \
    { \
        if (b && (MAX / b) < a) { \
            return FALSE; \
        } \
        *res = a * b; \
        return TRUE; \
    }

#define _G_ARITH_MUL_DEFINE_SAT_SIGNED(PREFIX, TYPE, MIN, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_mul_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res; \
        if (!g_##TYPE##_mul (&res, a, b)) { \
            res = ((a > 0) == (b > 0)) ? MAX : MIN; \
        } \
        return res; \
    }

#define _G_ARITH_MUL_DEFINE_SAT_UNSIGNED(PREFIX, TYPE, MAX) \
    static inline g##TYPE PREFIX##g_##TYPE##_mul_sat(g##TYPE a, g##TYPE b) \
    { \
        g##TYPE res; \
        if (!g_##TYPE##_mul (&res, a, b)) { \
            res = MAX; \
        } \
        return res; \
    }

_G_ARITH_UTIL_DEFINE_ALL (mul,
                          _G_ARITH_MUL_DEFINE_FALLBACK_SIGNED,
                          _G_ARITH_MUL_DEFINE_FALLBACK_UNSIGNED,
                          _G_ARITH_MUL_DEFINE_SAT_SIGNED,
                          _G_ARITH_MUL_DEFINE_SAT_UNSIGNED)

#if defined (__GNUC__)
#define g_mul(res, a, b) _G_ARITH_UTIL_GET_GENERIC_FUNC (mul, res, a, b)
#define g_mul_sat(res, a, b) _G_ARITH_UTIL_GET_GENERIC_SAT_FUNC (mul, res, a, b)
#endif

#endif /* __G_ARITHMETIC_H__ */
