// SPDX-License-Identifier: Unlicense

#ifndef TYPEDEFS_H
#define TYPEDEFS_H

#include "configuration.h"

#include <assert.h>
#include <float.h>
#include <math.h>
#include <stdint.h>
#include <type_traits>

// basic datatypes
using u8 = uint8_t;
using u16 = uint16_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i8 = int8_t;
using i16 = int16_t;
using i32 = int32_t;
using i64 = int64_t;

using f32 = float;
using f64 = double;

using TimeNS = i64; // nanoseconds
using Color = u32; // RGBA values

// openGL datatypes
using GLenum = unsigned int;
using GLboolean = unsigned char;
using GLbitfield = unsigned int;
using GLbyte = signed char;
using GLshort = short;
using GLint = int;
using GLsizei = int;
using GLubyte = unsigned char;
using GLushort = unsigned short;
using GLuint = unsigned int;
using GLfloat = float;
using GLclampf = float;
using GLdouble = double;
using GLclampd = double;
using GLvoid = void;

// advanced types
using SongIndex = i32; // When a song file is read, the song can be accessed with this index. Starts at 0.
using SfxBankIndex = i32;
using SfxToneIndex = i32;
using ArrangementIndex = i32; // A song can contain multiple arrangements like Lead, Rhythm and Bass guitar. Starts at 0.
using SfxIndex = i16; // Identifier for sound effect. SfxCore, SfxPlugin. Starts at 0.

// min+max values for basic types
namespace U8
{
  inline constexpr u8 max = UINT8_MAX;
}

namespace I8
{
  inline constexpr i8 min = INT8_MIN;
  inline constexpr i8 max = INT8_MAX;
}

namespace U16
{
  inline constexpr u16 max = UINT16_MAX;
}

namespace I16
{
  inline constexpr i16 min = INT16_MIN;
  inline constexpr i16 max = INT16_MAX;
}

namespace U32
{
  inline constexpr u32 max = UINT32_MAX;
}

namespace I32
{
  inline constexpr i32 min = INT32_MIN;
  inline constexpr i32 max = INT32_MAX;
}

namespace I64
{
  inline constexpr i64 min = INT64_MIN;
  inline constexpr i64 max = INT64_MAX;
}

namespace F32
{
  inline constexpr f32 min = FLT_MIN;
  inline constexpr f32 max = FLT_MAX;
  inline constexpr f32 inf = HUGE_VALF;
  inline constexpr f32 nan = NAN;
}

namespace F64
{
  inline constexpr f64 min = DBL_MIN;
  inline constexpr f64 max = DBL_MAX;
  inline constexpr f64 inf = HUGE_VAL;
  inline constexpr f64 nan = NAN;
}

// suffix literal for basic types
inline consteval u8 operator""_u8(const unsigned long long value)
{
  assert(value <= U8::max);
  return static_cast<u8>(value);
}

inline consteval u8 operator""_i8(const unsigned long long value)
{
  assert(value <= I8::max);
  return static_cast<i8>((value & 0xFF));
}

inline consteval u16 operator""_u16(const unsigned long long value)
{
  assert(value <= U16::max);
  return static_cast<u16>(value);
}

inline consteval u16 operator""_i16(const unsigned long long value)
{
  assert(value <= I16::max);
  return static_cast<i16>(value);
}

inline consteval u32 operator""_u32(const unsigned long long value)
{
  assert(value <= U32::max);
  return static_cast<u32>(value);
}

inline consteval u32 operator""_i32(const unsigned long long value)
{
  assert(value <= I32::max);
  return static_cast<i32>(value);
}

inline consteval u64 operator""_u64(const unsigned long long value)
{
  return static_cast<u64>(value);
}

inline consteval u64 operator""_i64(const unsigned long long value)
{
  return static_cast<i64>(value);
}

inline consteval f32 operator""_f32(const long double value)
{
  return static_cast<f32>(value);
}

inline consteval f64 operator""_f64(const long double value)
{
  return static_cast<f64>(value);
}

inline consteval TimeNS operator""_ns(const unsigned long long value)
{
  return static_cast<TimeNS>(value);
}

//inline constexpr TimeNS operator""_mus(const u64 value) // microseconds
//{
//  return static_cast<TimeNS>(value);
//}

inline constexpr TimeNS operator""_ms(const unsigned long long value)
{
  return static_cast<TimeNS>(value * 1'000'000);
}

inline constexpr TimeNS operator""_s(const unsigned long long value)
{
  return static_cast<TimeNS>(value * 1'000'000'000);
}

// global constants
inline constexpr f32 PI_ = 3.14159265358979323846_f32;       // pi
inline constexpr f32 PI_2 = 1.57079632679489661923_f32;     // pi/2
inline constexpr f32 PI_4 = 0.785398163397448309616_f32;    // pi/4
inline constexpr f32 SQRT2 = 1.41421356237309504880_f32;    // sqrt(2)
inline constexpr f32 SQRT1_2 = 0.707106781186547524401_f32; // 1/sqrt(2)
// ~global constants

// enum as flags
template <typename T>
constexpr std::underlying_type_t<T> to_underlying_(T value) noexcept
{
  return static_cast<std::underlying_type_t <T>>(value);
}

#ifdef SHR3D_WINDOW_SDL
typedef struct SDL_Window* Shr3DWindow;
#endif // SHR3D_WINDOW_SDL
#ifdef SHR3D_WINDOW_WIN32
typedef struct HWND__* Shr3DWindow;
#endif // SHR3D_WINDOW_WIN32

#define BIT_FLAGS(T);                                                                                                                                                    \
  inline constexpr T operator~(T a) { return static_cast<T>(~static_cast<std::underlying_type<T>::type>(a) ); }                                                          \
  inline constexpr T operator|(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b)); }       \
  inline constexpr T operator&(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b)); }       \
  inline constexpr T operator^(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b)); }       \
  inline T& operator|=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b)); } \
  inline T& operator&=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b)); } \
  inline T& operator^=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b)); }

#define BIT_FLAGS_FRIEND(T);                                                                                                                                                    \
  friend inline constexpr T operator~(T a) { return static_cast<T>(~static_cast<std::underlying_type<T>::type>(a) ); }                                                          \
  friend inline constexpr T operator|(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b)); }       \
  friend inline constexpr T operator&(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b)); }       \
  friend inline constexpr T operator^(T a, T b) { return static_cast<T>(static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b)); }       \
  friend inline T& operator|=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b)); } \
  friend inline T& operator&=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b)); } \
  friend inline T& operator^=(T& a, T b) { return reinterpret_cast<T&>(reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b)); }
// ~enum as flags

#define ASSERT assert

#ifdef NDEBUG
#define DEBUG_PRINT(...) ((void)0)
#else // NDEBUG
#if not defined PLATFORM_EMSCRIPTEN && not defined PLATFORM_ANDROID_SDL
#define DEBUG_PRINT(format, ...) //printf("%s:%d:%s(): " format, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#else // __EMSCRIPTEN__ && PLATFORM_ANDROID_SDL
#define DEBUG_PRINT printf
#endif // __EMSCRIPTEN__
#endif // NDEBUG

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a))

#define RUN_ONCE static i8 firstTime; !firstTime++

#ifdef __GNUC__
[[noreturn]] inline __attribute__((always_inline)) void unreachable() { ASSERT(false); __builtin_unreachable(); }
#elif defined(_MSC_VER) // MSVC
[[noreturn]] __forceinline void unreachable() { ASSERT(false); __assume(0); }
#else // ???
[[noreturn]] inline void unreachable() { ASSERT(false); }
#endif

#define UNUSED(x) (void)x;

#define ARRAY_SET2(X)(X),(X)
#define ARRAY_SET4(X)ARRAY_SET2(X),ARRAY_SET2(X)
#define ARRAY_SET8(X)ARRAY_SET4(X),ARRAY_SET4(X)
#define ARRAY_SET16(X)ARRAY_SET8(X),ARRAY_SET8(X)
#define ARRAY_SET32(X)ARRAY_SET16(X),ARRAY_SET16(X)
#define ARRAY_SET64(X)ARRAY_SET32(X),ARRAY_SET32(X)
#define ARRAY_SET128(X)ARRAY_SET64(X),ARRAY_SET64(X)
#define ARRAY_SET256(X)ARRAY_SET128(X),ARRAY_SET128(X)
#define ARRAY_SET512(X)ARRAY_SET256(X),ARRAY_SET256(X)

enum struct ProcessBlockResult : bool
{
  ProcessedInInBlock = false,
  ProcessedInOutBlock = true
};

#endif // TYPEDEFS_H
