#ifndef FREERTOS_CPP_KERNEL_HPP
#define FREERTOS_CPP_KERNEL_HPP

#include <stdint.h>
#include <type_traits>

extern "C"
{
#include "FreeRTOS.h"
#include "task.h"
}

#if defined( __GNUC__ )
    #define FREERTOS_CPP_ALWAYS_INLINE inline __attribute__( ( always_inline ) )
#else
    #define FREERTOS_CPP_ALWAYS_INLINE inline
#endif

namespace freertos
{

enum class SchedulerState : uint8_t
{
    suspended,
    not_started,
    running
};

class Scheduler final
{
public:
    Scheduler() = delete;

    static FREERTOS_CPP_ALWAYS_INLINE void start() noexcept
    {
        vTaskStartScheduler();
    }

    static FREERTOS_CPP_ALWAYS_INLINE SchedulerState state() noexcept
    {
        const BaseType_t raw = xTaskGetSchedulerState();
        if( raw == taskSCHEDULER_RUNNING )
        {
            return SchedulerState::running;
        }
        if( raw == taskSCHEDULER_NOT_STARTED )
        {
            return SchedulerState::not_started;
        }
        return SchedulerState::suspended;
    }

    static FREERTOS_CPP_ALWAYS_INLINE void suspend() noexcept
    {
        vTaskSuspendAll();
    }

    [[nodiscard]] static FREERTOS_CPP_ALWAYS_INLINE bool resume() noexcept
    {
        return xTaskResumeAll() != pdFALSE;
    }
};

namespace this_task
{

FREERTOS_CPP_ALWAYS_INLINE TaskHandle_t current() noexcept
{
    return xTaskGetCurrentTaskHandle();
}

FREERTOS_CPP_ALWAYS_INLINE TickType_t tick_count() noexcept
{
    return xTaskGetTickCount();
}

FREERTOS_CPP_ALWAYS_INLINE void yield() noexcept
{
    taskYIELD();
}

} // namespace this_task

class CriticalSection final
{
public:
    FREERTOS_CPP_ALWAYS_INLINE CriticalSection() noexcept
    {
        taskENTER_CRITICAL();
    }

    FREERTOS_CPP_ALWAYS_INLINE ~CriticalSection() noexcept
    {
        taskEXIT_CRITICAL();
    }

    CriticalSection( const CriticalSection & ) = delete;
    CriticalSection & operator=( const CriticalSection & ) = delete;
    CriticalSection( CriticalSection && ) = delete;
    CriticalSection & operator=( CriticalSection && ) = delete;
};

class SchedulerSuspendGuard final
{
public:
    FREERTOS_CPP_ALWAYS_INLINE SchedulerSuspendGuard() noexcept
    {
        Scheduler::suspend();
    }

    FREERTOS_CPP_ALWAYS_INLINE ~SchedulerSuspendGuard() noexcept
    {
        ( void ) Scheduler::resume();
    }

    SchedulerSuspendGuard( const SchedulerSuspendGuard & ) = delete;
    SchedulerSuspendGuard & operator=( const SchedulerSuspendGuard & ) = delete;
    SchedulerSuspendGuard( SchedulerSuspendGuard && ) = delete;
    SchedulerSuspendGuard & operator=( SchedulerSuspendGuard && ) = delete;
};

static_assert( !std::is_default_constructible< Scheduler >::value );
static_assert( !std::is_copy_constructible< CriticalSection >::value );
static_assert( !std::is_move_constructible< CriticalSection >::value );
static_assert( !std::is_copy_constructible< SchedulerSuspendGuard >::value );
static_assert( !std::is_move_constructible< SchedulerSuspendGuard >::value );

} // namespace freertos

#undef FREERTOS_CPP_ALWAYS_INLINE

#endif
