// SPDX-License-Identifier: MIT

#ifndef RGBDS_HELPERS_HPP
#define RGBDS_HELPERS_HPP

// Ideally we'd use `std::unreachable`, but it has insufficient compiler support
#ifdef __GNUC__ // GCC or compatible
	#ifdef NDEBUG
		#define unreachable_ __builtin_unreachable
	#else
		// In release builds, define "unreachable" as such, but trap in debug builds
		#define unreachable_ __builtin_trap
	#endif
#else
// This seems to generate similar code to __builtin_unreachable, despite different semantics
// Note that executing this is undefined behavior (declared [[noreturn]], but does return)
[[noreturn]]
static inline void unreachable_() {
}
#endif

// Ideally we'd use `[[assume()]]`, but it has insufficient compiler support
#ifdef NDEBUG
	#ifdef _MSC_VER
		#define assume(x) __assume(x)
	#else
		//  `[[gnu::assume()]]` for GCC or compatible also has insufficient support (GCC 13+ only)
		#define assume(x) \
			do { \
				if (!(x)) { \
					unreachable_(); \
				} \
			} while (0)
	#endif
#else
	// In release builds, define "assume" as such, but `assert` in debug builds
	#include <assert.h>
	#define assume assert
#endif

// Ideally we'd use `std::bit_width`, but it has insufficient compiler support
#ifdef __GNUC__ // GCC or compatible
	#define ctz __builtin_ctz
	#define clz __builtin_clz

#elif defined(_MSC_VER)
	#include <intrin.h>
	#pragma intrinsic(_BitScanReverse, _BitScanForward)

static inline int ctz(unsigned int x) {
	unsigned long cnt;

	assume(x != 0);
	_BitScanForward(&cnt, x);
	return cnt;
}

static inline int clz(unsigned int x) {
	unsigned long cnt;

	assume(x != 0);
	_BitScanReverse(&cnt, x);
	return 31 - cnt;
}

#else
	#include <limits.h>

static inline int ctz(unsigned int x) {
	int cnt = 0;

	while (!(x & 1)) {
		x >>= 1;
		cnt++;
	}
	return cnt;
}

static inline int clz(unsigned int x) {
	int cnt = 0;

	while (x <= UINT_MAX / 2) {
		x <<= 1;
		cnt++;
	}
	return cnt;
}
#endif

// Macros for stringification
#define STR(x)            #x
#define EXPAND_AND_STR(x) STR(x)

// Macros for concatenation
#define CAT(x, y)            x##y
#define EXPAND_AND_CAT(x, y) CAT(x, y)

// For lack of <ranges>, this adds some more brevity
#define RANGE(s) std::begin(s), std::end(s)

// MSVC does not inline `strlen()` or `.length()` of a constant string
template<int N>
static constexpr int literal_strlen(char const (&)[N]) {
	return N - 1;
}

// For ad-hoc RAII in place of a `defer` statement or cross-platform `__attribute__((cleanup))`
template<typename T>
struct Defer {
	T deferred;
	Defer(T func) : deferred(func) {}
	~Defer() { deferred(); }
};

#endif // RGBDS_HELPERS_HPP
