#ifndef RT_VOYAGE_H
#define RT_VOYAGE_H

/* basic types and macros */

#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

#if defined(_MSC_VER) && !defined(RT_STATIC_LIB)
#define RT_DLLEXPORT __declspec(dllexport)
#define RT_DLLIMPORT __declspec(dllimport)
#else
#define RT_DLLEXPORT
#define RT_DLLIMPORT
#endif

#if defined(_MSC_VER)
#define RT_INLINE __forceinline
#elif defined(__GNUC__) || defined(__clang__)
#define RT_INLINE inline __attribute__((always_inline))
#endif

#define RT_UNUSED(x)      ((void)sizeof((x)))
#define RT_ARRAY_COUNT(x) (sizeof((x)) / sizeof((x)[0]))

#define RT_RESTRICT_VALUE_TO_BOUNDS(v, lower, upper)                                               \
    (((v) < (lower)) ? (lower) : (((v) > (upper)) ? (upper) : (v)))

#define RT_MIN(a, b) (((a) < (b))?(a):(b))
#define RT_MAX(a, b) (((a) > (b))?(a):(b))

#define RT_KB(n) ((n)*1024U)
#define RT_MB(n) ((n)*1024U * 1024U)
#define RT_GB(n) ((n)*1024U * 1024U * 1024U)

#ifndef __cplusplus
#if defined(_MSC_VER)
/* For some reason _Thread_local does not work with vs2022,
 * despite MS documentation claiming it should. */
#define RT_THREAD_LOCAL __declspec(thread)
#elif defined(__GNUC__) || defined(__clang__)
/* _Thread_local in C11-C17, thread_local in C23 */
#if __STDC_VERSION__ > 201710L
#define RT_THREAD_LOCAL thread_local
#elif __STDC_VERSION__ >= 201112L
#define RT_THREAD_LOCAL _Thread_local
#else
#error Versions older than C11 not supported.
#endif
#endif
#else
// C++ 11 supports thread_local
#if __cplusplus >= 201103L
#define RT_THREAD_LOCAL thread_local
#elif defined(_MSC_VER)
#define RT_THREAD_LOCAL __declspec(thread)
#else
#error Versions older than C++11 not supported.
#endif
#endif

/* Matches windows MAX_PATH */
#define RT_PATH_MAX 260

typedef unsigned int rt_result;

/* Default result codes */
enum {
    RT_SUCCESS       = 0,
    RT_OUT_OF_MEMORY = 1,
    RT_INVALID_VALUE = 2,

    RT_CUSTOM_ERROR_START,

    RT_UNKNOWN_ERROR = (rt_result)INT_MAX,
};

typedef struct {
    const char *start;
    unsigned int length;
} rt_text_span;

/* snprintf replacement.
 * Always returns a zero terminated string.
 */
RT_DLLEXPORT int rtSPrint(char *dest, size_t n, const char *fmt, ...);

RT_DLLEXPORT int rtVSPrint(char *dest, size_t n, const char *fmt, va_list ap);

/* Returns results like strcmp():
 * - If the first differing character is smaller in span than in cmp: < 0
 * - If span and cmp are equal: 0
 * - If the first differing character is greater in span than in cmp: > 0
 * - If span.length is smaller than strlen(cmp): -1
 * - If span.length is greater than strlen(cmp): 1
 */
RT_DLLEXPORT int rtCompareSpanToString(rt_text_span span, const char *cmp);

RT_DLLEXPORT void rtReportError(const char *subsystem, const char *fmt, ...);

RT_DLLEXPORT void rtLog(const char *subsystem, const char *fmt, ...);

#ifndef NDEBUG
#ifdef _MSC_VER
#define RT_DEBUGBREAK __debugbreak()
#elif defined(__clang__) && __has_builtin(__bultin_debugtrap)
#define RT_DEBUGBREAK __builtin_debugtrap()
#elif defined(__GNUC__)
#define RT_DEBUGBREAK __builtin_trap()
#endif

RT_DLLEXPORT int rtAssertHandler(const char *expr, const char *msg, const char *file, int line);
#define RT_ASSERT(x, msg)                                                                          \
    do {                                                                                           \
        if (!(x)) {                                                                                \
            if (rtAssertHandler(#x, (msg), __FILE__, __LINE__) == 0) {                             \
                RT_DEBUGBREAK;                                                                     \
            }                                                                                      \
        }                                                                                          \
    } while (0)

#define RT_ASSERT_ALWAYS_EVAL(x, msg) RT_ASSERT(x, msg)

// Asserts if p is "false", evaluates to p
// NOTE that this will evaluate p multiple times!
#define RT_VERIFY(p)                                                                               \
    ((!(p)) ? (rtAssertHandler(#p, "Verify failed", __FILE__, __LINE__), p) : p)

#else
#define RT_ASSERT(x, msg)             RT_UNUSED(x)
#define RT_ASSERT_ALWAYS_EVAL(x, msg) (x)
#define RT_VERIFY(p)                  (p)
#endif

/* Makes it easier to search for unimplemented functions */
#define RT_NOT_IMPLEMENTED RT_ASSERT_ALWAYS_EVAL(0, "Not implemented.")

enum {
    RT_INVALID_UNICODE = RT_CUSTOM_ERROR_START,
    RT_INSUFFICIENT_BUFFER,
};

/* Returns RT_SUCCESS if the string was successfully converted,
 * RT_INVALID_UNICODE if invalid unicode characters were encountered or
 * RT_INSUFFICIENT_BUFFER if the provided output buffer is too small */
RT_DLLEXPORT rt_result rtUTF8ToWStr(const char *utf8, wchar_t *wstr, size_t len);

/* Returns RT_SUCCESS if the string was successfully converted,
 * RT_INVALID_UNICODE if invalid unicode characters were encountered or
 * RT_INSUFFICIENT_BUFFER if the provided output buffer is too small */
RT_DLLEXPORT rt_result rtWStrToUTF8(const wchar_t *wstr, char *utf8, size_t len);

/* Relative pointer */
typedef struct {
    int32_t off;
} rt_relptr;

static RT_INLINE void *rtResolveRelptr(rt_relptr *ptr) {
    if (ptr->off != 0) {
        char *p = (char *)ptr;
        return (void *)(p + (ptrdiff_t)ptr->off);
    } else {
        return NULL;
    }
}

static RT_INLINE const void *rtResolveConstRelptr(const rt_relptr *ptr) {
    if (ptr->off != 0) {
        const char *p = (const char *)ptr;
        return (const void *)(p + (ptrdiff_t)ptr->off);
    } else {
        return NULL;
    }
}

static RT_INLINE void rtSetRelptr(rt_relptr *ptr, void *target) {
    if (target) {
        char *p = (char *)ptr, *t = (char *)target;
        ptrdiff_t d = t - p;
        ptr->off    = (int32_t)d;
    } else {
        ptr->off = 0;
    }
}

/* Bitfiddling functions */

/* Portable solution, On x64 using clzl is probably faster. */
static RT_INLINE uint32_t rtNextPowerOfTwo32(uint32_t v) {
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v++;
    return v;
}

/* Checks that exactly one bit is set */
#define RT_IS_POWER_OF_TWO(_n) ((_n) && !((_n) & ((_n)-1)))

/* Runtime init. Initializes basic systems.
 * You need to call this, even if you build a CLI only app. */
RT_DLLEXPORT rt_result rtInitRuntime(void);

RT_DLLEXPORT void rtShutdownRuntime(void);

#ifdef __cplusplus
}
#endif

#endif
