#pragma once

#include "Baselib_Timer.h"
#include "Baselib_ErrorState.h"

#ifdef __cplusplus
BASELIB_C_INTERFACE
{
#endif

// Unique thread id that can be used to compare different threads or stored for bookkeeping etc..
typedef intptr_t Baselib_Thread_Id;

// Baselib_Thread_Id that is guaranteed not to represent a thread
static const Baselib_Thread_Id Baselib_Thread_InvalidId = 0;

// Max number of characters for threadnames internal to baselib. Used for name in Baselib_Thread_Config
// In practice thread implementation on some platforms support even fewer characters for names
static const size_t Baselib_Thread_MaxThreadNameLength = 64;

// Yields the execution context of the current thread to other threads, potentially causing a context switch.
//
// The operating system may decide to not switch to any other thread.
BASELIB_API void Baselib_Thread_YieldExecution(void);

// Return the thread id of the current thread, i.e. the thread that is calling this function
BASELIB_API Baselib_Thread_Id Baselib_Thread_GetCurrentThreadId(void);


// We currently do not allow creating threads from C# bindings,
// since there is right now no way accessible way to inform the garbage collector about new baselib threads.
// I.e. any managed allocation on a baselib thread created from C# would never be garbage collected!
#ifndef BASELIB_BINDING_GENERATION

// The minimum guaranteed number of max concurrent threads that works on all platforms.
//
// This only applies if all the threads are created with Baselib.
// In practice, it might not be possible to create this many threads either. If memory is exhausted,
// by for example creating threads with very large stacks, that might translate to a lower limit in practice.
// Note that on many platforms the actual limit is way higher.
static const int Baselib_Thread_MinGuaranteedMaxConcurrentThreads = 64;

typedef struct Baselib_Thread Baselib_Thread;

typedef void (*Baselib_Thread_EntryPointFunction)(void* arg);

typedef struct Baselib_Thread_Config
{
    // Nullterminated name of the created thread (optional)
    // Useful exclusively for debugging - which tooling it is shown by and how it can be queried is platform dependent.
    // Truncated to Baselib_Thread_MaxThreadNameLength number of characters and copied to an internal buffer
    const char* name;

    // The minimum size in bytes to allocate for the thread stack. (optional)
    // If not set, a platform/system specific default stack size will be used.
    // If the value set does not conform to platform specific minimum values or alignment requirements,
    // the actual stack size used will be bigger than what was requested.
    uint64_t stackSize;

    // Required, this is set by calling Baselib_Thread_ConfigCreate with a valid entry point function.
    Baselib_Thread_EntryPointFunction entryPoint;

    // Argument to the entry point function, does only need to be set if entryPoint takes an argument.
    void* entryPointArgument;
} Baselib_Thread_Config;

// Creates and starts a new thread.
//
// On some platforms the thread name is not set until the thread has begun executing, which is not guaranteed
// to have happened when the creation function returns. There is typically a platform specific limit on the length of
// the thread name. If config.name is longer than this limit, the name will be automatically truncated.
//
// \param config        A pointer to a config object. entryPoint needs to be a valid function pointer, all other properties can be zero/null.
//
// Possible error codes:
// - Baselib_ErrorCode_InvalidArgument:            config.entryPoint is null
// - Baselib_ErrorCode_OutOfSystemResources:       there is not enough memory to create a thread with that stack size or the system limit of number of concurrent threads has been reached
BASELIB_API Baselib_Thread* Baselib_Thread_Create(Baselib_Thread_Config config, Baselib_ErrorState* errorState);


// Waits until a thread has finished its execution.
//
// Also frees its resources.
// If called and completed successfully, no Baselib_Thread function can be called again on the same Baselib_Thread.
//
// \param thread                 A pointer to a thread object.
// \param timeoutInMilliseconds  Time to wait for the thread to finish
//
// Possible error codes:
// - Baselib_ErrorCode_InvalidArgument:       thread is null
// - Baselib_ErrorCode_ThreadCannotJoinSelf:  the thread parameter points to the current thread, i.e. the thread that is calling this function
// - Baselib_ErrorCode_Timeout:               timeout is reached before the thread has finished
BASELIB_API void Baselib_Thread_Join(Baselib_Thread* thread, uint32_t timeoutInMilliseconds, Baselib_ErrorState* errorState);

// Return the thread id of the thread given as argument
//
// \param thread        A pointer to a thread object.
BASELIB_API Baselib_Thread_Id Baselib_Thread_GetId(Baselib_Thread* thread);

// Returns true if there is support in baselib for threads on this platform, otherwise false.
BASELIB_API bool Baselib_Thread_SupportsThreads(void);

#endif // !BASELIB_BINDING_GENERATION

#ifdef __cplusplus
} // BASELIB_C_INTERFACE
#endif