/**
 * @file os.h
 * @brief Operating System implementations.
 */

#pragma once

#include <type_traits>

#include <nn/time.h>
#include <types.h>

namespace nn {
namespace os {
namespace detail {
struct InternalCriticalSection {
    u32 Image;
};

struct InternalConditionVariable {
    u32 Image;
};
}  // namespace detail

typedef u64 Tick;

struct LightEventType {
    std::aligned_storage_t<0xc, 4> storage;
};

// https://github.com/misson20000/nn-types/blob/master/nn_os.h
struct EventType {
    nn::os::EventType* _x0;
    nn::os::EventType* _x8;
    bool isSignaled;
    bool initiallySignaled;
    bool shouldAutoClear;
    bool isInit;
    u32 signalCounter;
    u32 signalCounter2;
    nn::os::detail::InternalCriticalSection crit;
    nn::os::detail::InternalConditionVariable condvar;
};
typedef EventType Event;

// https://github.com/misson20000/nn-types/blob/master/nn_os.h
struct ThreadType {
    u64 field_8;
    u64 field_10;
    u64 field_18;
    char field_20[32];
    uint32_t thread_status;
    char field_41;
    u16 field_42;
    uint32_t thread_prio_shift;
    uint64_t thread_stack_base_addr;
    uint64_t thread_stack_base_addr_mirror;
    uint64_t thread_stack_size;
    uint64_t thread_param;
    uint64_t thread_func;
    u64 field_70;
    u64 field_78;
    u64 field_80;
    char field_88[0x100];
    char thread_name[0x20];
    const char* thread_name_addr;
    nn::os::detail::InternalCriticalSection crit;
    nn::os::detail::InternalConditionVariable condvar;
    u32 thread_handle;
};
#ifdef SWITCH
static_assert(sizeof(ThreadType) == 0x1C0, "Wrong size");
#endif

struct MessageQueueType {
    u64 _x0;
    u64 _x8;
    u64 _x10;
    u64 _x18;
    void* Buffer;
    u32 MaxCount;
    u32 Count;
    u32 Offset;
    bool Initialized;
    detail::InternalCriticalSection _x38;
    detail::InternalConditionVariable _x3C;
    detail::InternalConditionVariable _x40;
};

struct ConditionVariableType {};

struct SemaphoreType {
    std::aligned_storage_t<0x28, 8> storage;
};

struct SystemEvent;
struct SystemEventType;

// ARG
void SetHostArgc(s32);
s32 GetHostArgc();
void SetHostArgv(char**);
char** GetHostArgv();

// MEMORY
void InitializeVirtualAddressMemory();
Result AllocateAddressRegion(u64*, u64);
Result AllocateMemory(u64*, u64);
Result AllocateMemoryPages(u64, u64);
void AllocateMemoryBlock(u64*, u64);
void FreeMemoryBlock(u64, u64);
void SetMemoryHeapSize(u64);

// MUTEX
struct MutexType {
    u8 curState;            // _0
    bool isRecursiveMutex;  // _1
    s32 lockLevel;          // _2
    u8 _6[0x20 - 0xE];
};

void InitializeMutex(nn::os::MutexType*, bool, s32);
void FinalizeMutex(nn::os::MutexType*);
void LockMutex(nn::os::MutexType*);
bool TryLockMutex(nn::os::MutexType*);
void UnlockMutex(nn::os::MutexType*);
bool IsMutexLockedByCurrentThread(nn::os::MutexType const*);

// QUEUE
void InitializeMessageQueue(nn::os::MessageQueueType*, u64* buf, u64 queueCount);
void FinalizeMessageQueue(nn::os::MessageQueueType*);

bool TrySendMessageQueue(MessageQueueType*, u64);
void SendMessageQueue(MessageQueueType*, u64);
bool TimedSendMessageQueue(MessageQueueType*, u64, nn::TimeSpan);

bool TryReceiveMessageQueue(u64* out, MessageQueueType*);
void ReceiveMessageQueue(u64* out, MessageQueueType*);
bool TimedReceiveMessageQueue(u64* out, MessageQueueType*, nn::TimeSpan);

bool TryPeekMessageQueue(u64*, MessageQueueType const*);
void PeekMessageQueue(u64*, MessageQueueType const*);
bool TimedPeekMessageQueue(u64*, MessageQueueType const*);

bool TryJamMessageQueue(nn::os::MessageQueueType*, u64);
void JamMessageQueue(nn::os::MessageQueueType*, u64);
bool TimedJamMessageQueue(nn::os::MessageQueueType*, u64, nn::TimeSpan);

// CONDITION VARIABLE
void InitializeConditionVariable(ConditionVariableType*);
void FinalizeConditionVariable(ConditionVariableType*);

void SignalConditionVariable(ConditionVariableType*);
void BroadcastConditionVariable(ConditionVariableType*);
void WaitConditionVariable(ConditionVariableType*);
u8 TimedWaitConditionVariable(ConditionVariableType*, MutexType*, nn::TimeSpan);

// THREAD
Result CreateThread(nn::os::ThreadType*, void (*)(void*), void* arg, void* srcStack, u64 stackSize,
                    s32 priority, s32 coreNum);
Result CreateThread(nn::os::ThreadType*, void (*)(void*), void* arg, void* srcStack, u64 stackSize,
                    s32 priority);
void DestroyThread(nn::os::ThreadType*);
void StartThread(nn::os::ThreadType*);
void SetThreadName(nn::os::ThreadType*, char const* threadName);
void SetThreadNamePointer(nn::os::ThreadType*, char const*);
char* GetThreadNamePointer(nn::os::ThreadType const*);
nn::os::ThreadType* GetCurrentThread();
void GetCurrentStackInfo(uintptr_t* stack_addr, size_t* stack_size);
s32 ChangeThreadPriority(nn::os::ThreadType* thread, s32 priority);
s32 GetThreadPriority(nn::os::ThreadType const* thread);
u64 GetThreadId(const nn::os::ThreadType* thread);
void YieldThread();
void SuspendThread(nn::os::ThreadType*);
void ResumeThread(nn::os::ThreadType*);
void SleepThread(nn::TimeSpan);
void WaitThread(nn::os::ThreadType*);
void SetThreadCoreMask(nn::os::ThreadType*, int, u64 mask);

// EVENTS
void InitializeEvent(EventType*, bool initiallySignaled, bool autoclear);
void FinalizeEvent(EventType*);
void SignalEvent(EventType*);
void WaitEvent(EventType*);
bool TryWaitEvent(EventType*);
bool TimedWaitEvent(EventType*, nn::TimeSpan);
void ClearEvent(EventType*);

// LIGHT EVENTS
void InitializeLightEvent(LightEventType*, bool initiallySignaled, bool autoclear);
void FinalizeLightEvent(LightEventType*);
void SignalLightEvent(LightEventType*);
void WaitLightEvent(LightEventType*);
bool TimedWaitLightEvent(LightEventType*, nn::TimeSpan);
void ClearLightEvent(LightEventType*);

TimeSpan ConvertToTimeSpan(Tick ticks);

// SEMAPHORES
void InitializeSemaphore(SemaphoreType* semaphore, s32 initial_count, s32 max_count);
void FinalizeSemaphore(SemaphoreType* semaphore);
void AcquireSemaphore(SemaphoreType* semaphore);
bool TryAcquireSemaphore(SemaphoreType* semaphore);
void ReleaseSemaphore(SemaphoreType* semaphore);

// EXCEPTION HANDLING
typedef union {
    u64 x;  ///< 64-bit AArch64 register view.
    u32 w;  ///< 32-bit AArch64 register view.
    u32 r;  ///< AArch32 register view.
} CpuRegister;
/// Armv8 NEON register.

typedef union {
    u128 v;    ///< 128-bit vector view.
    double d;  ///< 64-bit double-precision view.
    float s;   ///< 32-bit single-precision view.
} FpuRegister;

struct UserExceptionInfo {
    u32 ErrorDescription;  ///< See \ref ThreadExceptionDesc.
    u32 pad[3];

    CpuRegister CpuRegisters[29];  ///< GPRs 0..28. Note: also contains AArch32 registers.
    CpuRegister FP;                ///< Frame pointer.
    CpuRegister LR;                ///< Link register.
    CpuRegister SP;                ///< Stack pointer.
    CpuRegister PC;                ///< Program counter (elr_el1).

    u64 padding;

    FpuRegister FpuRegisters[32];  ///< 32 general-purpose NEON registers.

    u32 PState;  ///< pstate & 0xFF0FFE20
    u32 AFSR0;
    u32 AFSR1;
    u32 ESR;

    CpuRegister FAR;  ///< Fault Address Register.
};
void SetUserExceptionHandler(void (*)(UserExceptionInfo*), void*, ulong, UserExceptionInfo*);

// OTHER
void GenerateRandomBytes(void*, u64);
nn::os::Tick GetSystemTick();
nn::os::Tick GetSystemTickFrequency();
u64 GetThreadAvailableCoreMask();
void SetMemoryHeapSize(u64 size);

// Thread-local storage
struct TlsSlot {
    u32 slot;
};
Result AllocateTlsSlot(TlsSlot* slot_out, void (*)(u64));
void FreeTlsSlot(TlsSlot slot);
u64 GetTlsValue(TlsSlot slot);
void SetTlsValue(TlsSlot slot, u64 value);
u32 GetCurrentCoreNumber();

namespace detail {
extern s32 g_CommandLineParameter;
extern char** g_CommandLineParameterArgv;
};  // namespace detail
};  // namespace os
};  // namespace nn
