/// Macros header
///
/// # Features
///  * `_EVAL_DEBUG_ONLY_STMTS` - Evaluate expressions passed to macros that are no-ops when not in debug mode.
#ifndef _MACROS_H
#define _MACROS_H

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdarg.h>
#include <assert.h>

// Attribute macros

#define _pure     __attribute__((const))
#define _readonly __attribute__((pure))

#ifndef __cplusplus
#define noreturn __attribute__((noreturn))
#define deprecated __attribute__((deprecated))
#define deprecated_message(str) __attribute__((deprecated(str))) 
#endif

#define noinline __attribute__((noinline))
#define always_inline __attribute__((always_inline))
#define force_inline __attribute__((gnu_inline))

#ifdef DEBUG
#define _mixin static inline force_inline
#else
#define _mixin extern inline force_inline
#endif

#define unrestrict __attribute__((may_alias))
#define _shared unrestrict

#ifdef __cplusplus
#define restrict __restrict__
#endif

// Argument attribute

// Used like: `int *pOUT output, const int *pIN input`
#define pOUT restrict
#define pIN const restrict
#define pINOUT restrict

// Used like: `int _OUT output, const int _IN input`
#define _IN *pIN
#define _OUT *pOUT
#define _INOUT *pINOUT

// Type macros

#define AS(x, t) ((t)(x))

#ifdef __cplusplus
#define let auto
#define var(x) decltype(x)
#else
#define let __auto_type
#define var(x) __typeof((x))
#endif

// Control flow macros

#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)

#define ifL(x) if(LIKELY((x)))
#define ifU(x) if(UNLIKELY((x)))

/// Equivalent to GNU C `x ?: y` operator. 
#define TERN_OR(x, y) ({ let _x = (x); _x ? _x : (y); })

// Statement macros

#define _no_op ((void)0)
#define IGNORE(x) ((void)(x))
_mixin void _drain_val(void* x, ...) { IGNORE(x); } // This compiles to no-op on -O0 and -O2+ due to `gnu_inline` (_mixin)
#define _drain(...) _drain_val(NULL, __VA_ARGS__)
#define IGNORE_ALL(...) _drain(__VA_ARGS__)

// Allocation macros

#define box(t) aligned_alloc(_Alignof(t), sizeof(t))
#define stackalloc(t) __builtin_alloca_with_align(sizeof(t), _Alignof(t))

// Function macros

//  debug-only statements
#ifdef DEBUG
#define DBG(x) x
#else
#define DBG(x) ((void)0)
#endif

//  Assertions

#define assert_msg(expr, ...) ( (expr) ? 1 : FATAL(__VA_ARGS__))

#ifdef DEBUG
#define debug_assert(x) assert((x))
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
#define debug_assert(x) IGNORE(x)
#else
#define debug_assert(x) _no_op
#endif

#define assert_eq(x,y) assert((x) == (y))
#define assert_ne(x,y) assert((x) != (y))

#define debug_assert_eq(x,y) debug_assert((x) == (y))
#define debug_assert_ne(x,y) debug_assert((x) != (y))

#ifdef DEBUG
#define debug_static_assert(x, msg) _Static_assert((x), msg)
#else
#define debug_static_assert(x, msg) _Static_assert(1, msg)
#endif

#define static_assert_eq(x,y,msg) _Static_assert((x) == (y), msg)
#define static_assert_ne(x,y,msg) _Static_assert((x) != (y), msg)

//  Bitmasking

#define _BSWAP(x) __builtin_bswa  x
#define BSWAP_16(x) _BSWAP(p16)  (AS((x), uint16_t))
#define BSWAP_32(x) _BSWAP(p32)  (AS((x), uint32_t))
#define BSWAP_64(x) _BSWAP(p64)  (AS((x), uint64_t))
#define BSWAP_128(x) _BSWAP(p128)(AS((x), uint128_t))

#undef _BSWAP
#define _BSWAP(n) __builtin_bswap ## n

#define bswap(x) _Generic((x), \
		uint64_t: _BSWAP(64), int64_t: _BSWAP(64), \
		uint32_t: _BSWAP(32), int32_t: _BSWAP(32), \
		uint16_t: _BSWAP(16), int16_t: _BSWAP(16), \
		__uint128_t: _BSWAP(128), __int128_t: _BSWAP(128)) \
	((x))


static_assert_eq(bswap(128lu), 9223372036854775808lu, "bswap128 (lu) failed (1)");
static_assert_eq(128lu, bswap(9223372036854775808lu), "bswap128 (lu) failed (2)");
static_assert_eq(bswap(bswap(128lu)), 128, "bswap128 (lu) failed (3)");

//  Trace message output

#define TRACEx(X, msg, ...) (fprintf(stderr, "[" X "] " __FILE__ ":%d->%s(): " msg "\n", __LINE__, __func__, ## __VA_ARGS__)) 

#ifdef DEBUG
#define TRACE(msg, ...) TRACEx("trace",   msg, ## __VA_ARGS__)
#define dprintf(msg, ...) TRACEx("debug",   msg, ## __VA_ARGS__)
#elif defined(_EVAL_DEBUG_ONLY_STMTS)
#define TRACE(msg, ...) _drain(msg, ## __VA_ARGS__)
#define dprintf(msg, ...) _drain(msg, ## __VA_ARGS__)
#else
#define TRACE(msg, ...) _no_op
#endif
#define INFO(msg, ...) TRACEx("info",  msg,  ## __VA_ARGS__)
#define WARN(msg, ...) TRACEx("warning", msg, ## __VA_ARGS__)
#define ERROR(msg, ...) TRACEx("error",  msg, ## __VA_ARGS__)

#define FATAL(msg, ...) (TRACEx("FATAL",   msg, ## __VA_ARGS__), abort())

#define TODO(x) FATAL("function %s() is unimplemented: " x, __func__)

//  Debug testing

#define TEST_OK		1
#define TEST_FAILED	0

#define TESTFUN(name) _test__ ## name
#define CALLTEST(name) (assert_msg(TESTFUN(name)() == TEST_OK, "Test `" #name "` failed"), TRACE("Test `" #name "` passed!"))
#define DEFTEST(name) int _test__ ## name(void)

#define TEST_ASSERT(x) do { if (!(x)) {ERROR("Test `%s' assertion failure: `" #x "`", __func__ + 7); return TEST_FAILED;} } while(0)
#define TEST_ASSERT_EQ(x,y) TEST_ASSERT( (x) == (y) )
#define TEST_ASSERT_NE(x,y) TEST_ASSERT( (x) != (y) )

// Version macros

#define _AS_u32(x) ((uint32_t)(x))

#define VER_COMP_MAJ 24u
#define VER_COMP_MIN 16u
#define VER_COMP_BF  8u
#define VER_COMP_REV 0u

/// Create a `uint32_t` from these version componants
#define VERSION(maj,min,bf,rev) _AS_u32( _AS_u32(maj) << 24u | _AS_u32(min) << 16u | _AS_u32(bf) << 8u | _AS_u32(rev) )

/// Mask `ver` to retain only the version component specified by `mask`
///
/// # Example
/// `_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u)`
#define VERSION_COMP(ver, mask) _AS_u32( _AS_u32(ver) & (0xffu << _AS_u32(mask)) /*>> _AS_u32(mask)*/ )

#define VERSION_MAJ(ver) _AS_u32(VERSION_COMP((ver), 24u) >> 24u)
#define VERSION_MIN(ver) _AS_u32(VERSION_COMP((ver), 16u) >> 16u)
#define VERSION_BF(ver)  _AS_u32(VERSION_COMP((ver), 8u) >> 8u)
#define VERSION_REV(ver) _AS_u32(_AS_u32(ver) & 0xffu)
_Static_assert( (VERSION_COMP(VERSION(1,2,3,4), VER_COMP_MIN) >> VER_COMP_MIN) == 2u, "invalid native version spec");

#endif /* _MACROS_H */
