#ifndef FREERTOS_CPP_TASK_STORAGE_HPP
#define FREERTOS_CPP_TASK_STORAGE_HPP

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

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

#ifndef FREERTOS_STORAGE_CHECKPOINT
#define FREERTOS_STORAGE_CHECKPOINT( point, object ) \
    do                                                \
    {                                                 \
        ( void ) ( point );                           \
        ( void ) ( object );                          \
    } while( 0 )
#endif

namespace freertos
{

enum class StorageTaskState : uint8_t
{
    constructed,
    ready,
    running,
    completed,
    start_failed
};

template< typename Derived >
class DynamicTask
{
public:
    constexpr DynamicTask( const char * name,
                           configSTACK_DEPTH_TYPE stack_words,
                           UBaseType_t priority ) noexcept
        : name_ { name }, stack_words_ { stack_words }, priority_ { priority }
    {
    }

    DynamicTask( const DynamicTask & ) = delete;
    DynamicTask & operator=( const DynamicTask & ) = delete;
    DynamicTask( DynamicTask && ) = delete;
    DynamicTask & operator=( DynamicTask && ) = delete;

    bool start() noexcept
    {
        static_assert( std::is_base_of< DynamicTask< Derived >,
                                        Derived >::value );
        if( state_ != StorageTaskState::constructed )
        {
            return false;
        }
        state_ = StorageTaskState::ready;
        const BaseType_t result = xTaskCreate(
            &DynamicTask::trampoline, name_, stack_words_,
            static_cast< Derived * >( this ), priority_, &handle_ );
        if( result != pdPASS )
        {
            handle_ = nullptr;
            state_ = StorageTaskState::start_failed;
            return false;
        }
        FREERTOS_STORAGE_CHECKPOINT( 2U, this );
        return true;
    }

    TaskHandle_t native_handle() const noexcept { return handle_; }
    StorageTaskState state() const noexcept { return state_; }

private:
    static void trampoline( void * context )
    {
        auto * const derived = static_cast< Derived * >( context );
        auto & base = *static_cast< DynamicTask * >( derived );
        base.state_ = StorageTaskState::running;
        FREERTOS_STORAGE_CHECKPOINT( 5U, derived );
        derived->run();
        base.state_ = StorageTaskState::completed;
        base.handle_ = nullptr;
        FREERTOS_STORAGE_CHECKPOINT( 7U, derived );
        vTaskDelete( nullptr );
        for( ;; ) {}
    }

    TaskHandle_t handle_ {};
    const char * const name_;
    const configSTACK_DEPTH_TYPE stack_words_;
    const UBaseType_t priority_;
    volatile StorageTaskState state_ { StorageTaskState::constructed };
};

template< size_t StackWords >
struct StaticTaskStorage final
{
    StaticTask_t tcb {};
    StackType_t stack[ StackWords ] {};
};

template< typename Derived, size_t StackWords >
class StaticTask
{
public:
    static_assert( StackWords > 0U, "static task needs stack storage" );

    constexpr StaticTask( const char * name,
                          UBaseType_t priority,
                          StaticTaskStorage< StackWords > & storage ) noexcept
        : name_ { name }, priority_ { priority }, storage_ { storage }
    {
    }

    StaticTask( const StaticTask & ) = delete;
    StaticTask & operator=( const StaticTask & ) = delete;
    StaticTask( StaticTask && ) = delete;
    StaticTask & operator=( StaticTask && ) = delete;

    bool start() noexcept
    {
        static_assert( std::is_base_of< StaticTask< Derived, StackWords >,
                                        Derived >::value );
        if( state_ != StorageTaskState::constructed )
        {
            return false;
        }
        state_ = StorageTaskState::ready;
        handle_ = xTaskCreateStatic(
            &StaticTask::trampoline,
            name_,
            static_cast< configSTACK_DEPTH_TYPE >( StackWords ),
            static_cast< Derived * >( this ),
            priority_,
            storage_.stack,
            &storage_.tcb );
        if( handle_ == nullptr )
        {
            state_ = StorageTaskState::start_failed;
            return false;
        }
        FREERTOS_STORAGE_CHECKPOINT( 4U, this );
        return true;
    }

    TaskHandle_t native_handle() const noexcept { return handle_; }
    StorageTaskState state() const noexcept { return state_; }
    const StaticTask_t * tcb_storage() const noexcept { return &storage_.tcb; }
    const StackType_t * stack_storage() const noexcept { return storage_.stack; }
    static constexpr size_t stack_words() noexcept { return StackWords; }
    static constexpr size_t stack_bytes() noexcept
    {
        return StackWords * sizeof( StackType_t );
    }

private:
    static void trampoline( void * context )
    {
        auto * const derived = static_cast< Derived * >( context );
        auto & base = *static_cast< StaticTask * >( derived );
        base.state_ = StorageTaskState::running;
        FREERTOS_STORAGE_CHECKPOINT( 6U, derived );
        derived->run();
        base.state_ = StorageTaskState::completed;
        base.handle_ = nullptr;
        FREERTOS_STORAGE_CHECKPOINT( 8U, derived );
        vTaskDelete( nullptr );
        for( ;; ) {}
    }

    TaskHandle_t handle_ {};
    const char * const name_;
    const UBaseType_t priority_;
    StaticTaskStorage< StackWords > & storage_;
    volatile StorageTaskState state_ { StorageTaskState::constructed };
};

} // namespace freertos

#endif
