#pragma once

// Some of these macros are ""borrowed"" from nick aversano | source: https://github.com/nickav/na/blob/main/na.h

// #OS_Platform
#if defined(_WIN32)
    #define OS_WINDOWS 1
#elif defined(__APPLE__)
    #define OS_MACOS 1
#elif defined(__linux__)
    #define OS_LINUX 1
#endif

#if !defined(OS_WINDOWS)
    #define OS_WINDOWS 0
#endif
#if !defined(OS_LINUX)
    #define OS_LINUX 0
#endif
#if !defined(OS_MACOS)
    #define OS_MACOS 0
#endif

#if defined(__cplusplus)
    #define LANG_CPP 1
#else
    #define LANG_C 1
#endif

// #Compiler: Language
#if !defined(LANG_CPP)
    #define LANG_CPP 0
#endif
#if !defined(LANG_C)
    #define LANG_C 0
#endif

// #Compiler: Vendor
#if defined(__clang__)
    #define COMPILER_CLANG 1
#elif defined(_MSC_VER)
    #define COMPILER_MSVC 1
#elif defined(__GNUC__) || defined(__GNUG__)
    #define COMPILER_GCC 1
#endif

#if !defined(COMPILER_MSVC)
    #define COMPILER_MSVC 0
#endif
#if !defined(COMPILER_GCC)
    #define COMPILER_GCC 0
#endif
#if !defined(COMPILER_CLANG)
    #define COMPILER_CLANG 0
#endif

#if COMPILER_MSVC
    #if _MSC_VER >= 1930
        #define COMPILER_MSVC_YEAR 2022
    #elif _MSC_VER >= 1920
        #define COMPILER_MSVC_YEAR 2019
    #elif _MSC_VER >= 1910
        #define COMPILER_MSVC_YEAR 2017
    #elif _MSC_VER >= 1900
        #define COMPILER_MSVC_YEAR 2015
    #elif _MSC_VER >= 1800
        #define COMPILER_MSVC_YEAR 2013
    #elif _MSC_VER >= 1700
        #define COMPILER_MSVC_YEAR 2012
    #elif _MSC_VER >= 1600
        #define COMPILER_MSVC_YEAR 2010
    #elif _MSC_VER >= 1500
        #define COMPILER_MSVC_YEAR 2008
    #elif _MSC_VER >= 1400
        #define COMPILER_MSVC_YEAR 2005
    #else
        #define COMPILER_MSVC_YEAR 0
    #endif
#endif

// #Architecture: CPU Vendor
#if defined(_WIN32)
    #if defined(_M_AMD64)
        #define ARCH_CPU_X64 1
    #elif defined(_M_IX86)
        #define ARCH_CPU_X86 1
    #elif defined(_M_ARM64)
        #define ARCH_CPU_ARM64 1
    #elif defined(_M_ARM)
        #define ARCH_CPU_ARM32 1
    #endif

#else
    #if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64)
        #define ARCH_CPU_X64 1
    #elif defined(i386) || defined(__i386) || defined(__i386__)
        #define ARCH_CPU_X86 1
    #elif defined(__aarch64__)
        #define ARCH_CPU_ARM64 1
    #elif defined(__arm__)
        #define ARCH_CPU_ARM32 1
    #endif

#endif

#if !defined(ARCH_CPU_X64)
    #define ARCH_CPU_X64 0
#endif
#if !defined(ARCH_CPU_X86)
    #define ARCH_CPU_X86 0
#endif
#if !defined(ARCH_CPU_ARM64)
    #define ARCH_CPU_ARM64 0
#endif
#if !defined(ARCH_CPU_ARM32)
    #define ARCH_CPU_ARM32 0
#endif

// #Architecture: Register Width
#if defined(ARCH_CPU_X64) || defined(ARCH_CPU_ARM64)
    #define ARCH_64BIT 1
#elif defined(ARCH_CPU_X86) || defined(ARCH_CPU_ARM32)
    #define ARCH_32BIT 1
#endif

#if !defined(ARCH_64BIT)
    #define ARCH_64BIT 0
#endif
#if !defined(ARCH_32BIT)
    #define ARCH_32BIT 0
#endif

// #Architecture: Endianness
static const int __arch_endian_check_num = 1;

#define ARCH_LITTLE_ENDIAN (*(char *)&__arch_endian_check_num == 1)
#define ARCH_BIG_ENDIAN  (!ARCH_LITTLE_ENDIAN)

#if defined(_MSC_VER)
  #ifdef _DEBUG
    #define BUILD_DEBUG 1
  #else
    #define BUILD_DEBUG 0
  #endif
#elif defined(__GNUC__) || defined(__clang__)
  #ifndef NDEBUG
    #define BUILD_DEBUG 1
  #else
    #define BUILD_DEBUG 0
  #endif
#endif

// #ifndef NDEBUG
//   #define BUILD_DEBUG 1
// #else
//   #define BUILD_DEBUG 0
// #endif

#define BUILD_CONSOLE_INTERFACE BUILD_DEBUG

#include <stdio.h> // vsnprintf
#include <cstdarg> // va_list, ...

#if ARCH_CPU_X64
  #include "CPU_X64.cpp"
  #define PLATFORM_MEMORY_PAGE_SIZE 4096
  #define PLATFORM_MEMORY_LARGE_PAGE_SIZE 2097152
  #define CPU_REGISTER_WIDTH_BYTES 8
  #define CPU_CACHE_LINE_SIZE 64
#else
  #error "CPU not supported (yet)!"
#endif

#if OS_WINDOWS
  #define WIN32_LEAN_AND_MEAN
  #include <Windows.h>
  #undef ERROR // why...
  #undef NO_ERROR // ugh...
  #include <winioctl.h>
#else
  #error "This configuration is NOT supported. Only Windows with MSVC is currently supported."
#endif

#ifndef PROTOTYPING_API
#ifdef OS_WINDOWS
#define PROTOTYPING_API extern "C" __declspec(dllexport)
#else 
#define PROTOTYPING_API
#endif
#endif // #ifndef PROTOTYPING_API

#define C_API
#define TEMPORARY_API
#define DEPRECATED_API

#include <string.h>
#include <stdint.h>

// Primitive types. 
// typedef nullptr null
typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
typedef int8_t   s8;
typedef int16_t  s16;
typedef int32_t  s32;
typedef int64_t  s64;
// typedef bool     b8; // just use bool for b8s
typedef s16      b16;
typedef s32      b32;
typedef s64      b64;
typedef float    f32;
typedef double   f64;

// Units
#define KB(n)  (((s64)(n)) << 10)
#define MB(n)  (((s64)(n)) << 20)
#define GB(n)  (((s64)(n)) << 30)
#define TB(n)  (((s64)(n)) << 40)
#define Thousand(n)   ((n)*1000)
#define Million(n)    ((n)*1000000)
#define Billion(n)    ((n)*1000000000)

#define internal      static
#define global        static
#define local_persist static // I don't like these, so I generally won't use them!

#if COMPILER_MSVC
# define thread_static __declspec(thread)
#elif COMPILER_CLANG || COMPILER_GCC
# define thread_static __thread
#else
# error thread_static not defined for this compiler.
#endif

#if COMPILER_MSVC || (COMPILER_CLANG && OS_WINDOWS)
# pragma section(".rdata$", read)
# define read_only __declspec(allocate(".rdata$"))
#elif (COMPILER_CLANG && OS_LINUX)
# define read_only __attribute__((section(".rodata")))
#else
#endif

#if COMPILER_MSVC
# define force_inline __forceinline
#elif COMPILER_CLANG || COMPILER_GCC
# define force_inline __attribute__((always_inline))
#else
# error force_inline not defined for this compiler.
#endif

// Maybe move to a different file. 
force_inline s64 Align_To_Page_Size(s64 n) {
  return (n + PLATFORM_MEMORY_PAGE_SIZE - 1) & (~(PLATFORM_MEMORY_PAGE_SIZE-1));
}

template <typename T>
force_inline T Align (T value, s64 alignment) {
  s64 intermediate = (((s64)value) + alignment - 1) & (~(alignment - 1));
  return (T)intermediate;
}

/*
force_inline s64 Align_Forwards(s64 size, s64 alignment) {
  return (((size + alignment - 1) / alignment) * alignment);
}
*/

// Branchless nextpow2 implementation. Returns zero if v is negative.
// All it does is fill in all the bits to the right of the most significant bit.
force_inline s64 Next_Power_Of_Two(s64 v) {
  v -= 1;
  
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v |= v >> 32;
  
  v += 1;
  
  return v;
}

#define Stringify_(S) #S
#define Stringify(S) Stringify_(S)

#define Concat_(A,B) A##B
#define Concat(A,B) Concat_(A,B)

#if COMPILER_MSVC
# define debug_break() __debugbreak()
#elif COMPILER_CLANG || COMPILER_GCC
# define debug_break() __builtin_trap()
#else
# define debug_break()
# error Unknown trap intrinsic for this compiler.
#endif

#define AssertAlways(x) do{if(!(x)) {debug_break();}}while(0)
#if BUILD_DEBUG
# define Assert(x) AssertAlways(x)
#else
# define Assert(x) (void)(x)
#endif

#if LANG_CPP
# define C_LINKAGE_BEGIN extern "C"{
# define C_LINKAGE_END }
# define C_LINKAGE extern "C"
#else
# define C_LINKAGE_BEGIN
# define C_LINKAGE_END
# define C_LINKAGE
#endif

// Disable some of MSVC most aggressive Debug runtime checks in function header/footer (used in some simple/low-level functions)
#if COMPILER_MSVC
#define MSVC_RUNTIME_CHECKS_OFF      __pragma(runtime_checks("",off))     __pragma(check_stack(off)) __pragma(strict_gs_check(push,off))
#define MSVC_RUNTIME_CHECKS_RESTORE  __pragma(runtime_checks("",restore)) __pragma(check_stack())    __pragma(strict_gs_check(pop))
#else
#define MSVC_RUNTIME_CHECKS_OFF
#define MSVC_RUNTIME_CHECKS_RESTORE
#endif

// Scoped Macros/Functions for auto_reset and auto_release
// usage `Auto_Reset guard(arena);` within a scope.
#define auto_reset(x) \
    Auto_Reset Concat(_auto_reset_guard_, __LINE__)(x)
#define push_allocator(x) \
    Push_Allocator Concat(_push_alloc_guard_, __LINE__)(x)
#define push_alignment(x, y) \
    Push_Alignment Concat(_push_align_guard_, __LINE__)(x, y)
#define push_arena(x) \
    Push_Arena Concat(_push_arena_guard_, __LINE__)(x)
#define push_expandable_arena(x) \
    Push_Expandable_Arena Concat(_push_ex_arena_guard_, __LINE__)(x)
#define auto_release(x) \
    Auto_Release Concat(_auto_release_guard_, __LINE__)(x)
#define auto_release_temp() \
    auto_release(thread_context()->temp)

#define thread_context()      thread_local_context
#define temp()                allocator(thread_context()->temp)
#define context_allocator()   thread_context()->allocator
#define context_logger()      &thread_context()->logger
#define context_builder()     thread_context()->string_builder

// CHECK THAT THESE ARE CORRECT!
constexpr f32 TAU = 6.283185f;
constexpr f64 TAU_64 = 6.28318530717958648;
constexpr f32 PI = 3.1415927f;
constexpr f64 PI_64 = 3.141592653589793;

constexpr f32 FLOAT16_MAX = 65504.0;

constexpr f32 FLOAT32_MIN      = (f32)(0x00800000);
constexpr f32 FLOAT32_MAX      = (f32)(0x7F7FFFFF);
constexpr f32 FLOAT32_INFINITY = (f32)(0x7F800000);
constexpr f32 FLOAT32_NAN      = (f32)(0x7FBFFFFF);

constexpr f64 FLOAT64_MIN      = (f64)(0x0010000000000000ULL);
constexpr f64 FLOAT64_MAX      = (f64)(0x7FEFFFFFFFFFFFFFULL);
constexpr f64 FLOAT64_INFINITY = (f64)(0x7FF0000000000000ULL);
constexpr f64 FLOAT64_NAN      = (f64)(0x7FF7FFFFFFFFFFFFULL);

constexpr s8  S8_MIN  = -128;
constexpr s8  S8_MAX  = 127;
constexpr u8  U8_MAX  = 255;
constexpr s16 S16_MIN = -32768;
constexpr s16 S16_MAX = 32767;
constexpr u16 U16_MAX = 0xffff;
constexpr s32 S32_MIN = 0x80000000;
constexpr s32 S32_MAX = 0x7fffffff;
constexpr u32 U32_MAX = 0xffffffff;
constexpr s64 S64_MIN = 0x8000000000000000LL;
constexpr s64 S64_MAX = 0x7fffffffffffffffLL;
constexpr u64 U64_MAX = 0xffffffffffffffffULL;

struct Vec2 {
  union {
    struct { float x, y; };
    float data[2];
  };
};

struct Vec3 {
  union {
    struct { float x, y, z; };
    float data[3];
  };
};

struct Vec4 {
  union {
    struct { float x, y, z, w; };
    float data[4];
  };
};

template <typename T> T clamp (T v, T min, T max) {
  // T must be a scalar type:
  auto x = v;
  if (x < min) {
    x = min;
  }
  if (x > max) {
    x = max;
  }
  
  return x;
}

// #thread_primitives #move?
#if OS_WINDOWS
  struct Condition_Variable {
    CONDITION_VARIABLE condition_variable;
  };
  struct Semaphore {
    HANDLE event;
  };
  struct Mutex {
    CRITICAL_SECTION csection;
  };
  struct OS_Thread {
    HANDLE windows_thread;
    s32 windows_thread_id;
  };
  struct File {
    HANDLE handle;
  };
  internal void mutex_init (Mutex* mutex);
  internal void mutex_destroy (Mutex* mutex);
  internal void lock (Mutex* mutex);
  internal void unlock (Mutex* mutex);
#endif
#define POSIX_THREADS OS_LINUX || OS_MACOS || OS_IOS || OS_ANDROID
#if OS_MACOS
  struct Semaphore {
    task_t owner;
    semaphore_t event = 0;
  };
#endif
#if OS_LINUX || OS_ANDROID
  struct Semaphore {
    sem_t semaphore;
  };
#endif
#if OS_IS_UNIX // #posix threads
  struct OS_Thread {
    pthread_t thread_handle;
    Semaphore is_alive;
    Semaphore suspended;
    b32 is_done;
  };
#endif


#define lock_guard(x) \
    Mutex_Lock_Guard Concat(_auto_lock_guard_, __LINE__)(x)
    
struct Mutex_Lock_Guard {
  Mutex* mutex;
  
  Mutex_Lock_Guard (Mutex* mutex) {
    this->mutex = mutex;
    lock(mutex);
  }
  
  ~Mutex_Lock_Guard () {
    unlock(mutex);
  }
};

// Helper macros for raw arrays:
#define ArrayCount(array) sizeof(array) / sizeof(array[0])
// For-loop construct macros
#define for_each_index(_idx_, _until_) for (s64 _idx_ = 0; _idx_ < _until_; ++_idx_)
// For-loops for ArrayView<> compatible types
#define for_each(_idx_, _array_) for (s64 _idx_ = 0; _idx_ < (_array_).count; ++_idx_)
#define for_each_reverse(_idx_, _array_) for (s64 _idx_ = (_array_).count-1; _idx_ >= 0; _idx_--)
// #define for_each_starting_at(_it_, _array_, _start_) for (s64 _it_ = _start_; _it_ < (_array_).count; _it_ += 1)

