#ifndef FREERTOS_SOFTWARE_TIMER_HPP
#define FREERTOS_SOFTWARE_TIMER_HPP

#include <type_traits>

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

namespace freertos
{

enum class TimerPolicy
{
    one_shot,
    periodic
};

enum class TimerCommand
{
    rejected = 0,
    accepted = 1
};

template< typename Context, TimerPolicy Policy >
class SoftwareTimer final
{
public:
    SoftwareTimer( StaticTimer_t & storage,
                   const char * name,
                   TickType_t period,
                   Context & context ) noexcept
        : handle_( xTimerCreateStatic( name,
                                       period,
                                       Policy == TimerPolicy::periodic ? pdTRUE : pdFALSE,
                                       &context,
                                       &SoftwareTimer::trampoline,
                                       &storage ) )
    {
    }

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

    [[nodiscard]] bool valid() const noexcept
    {
        return handle_ != nullptr;
    }

    [[nodiscard]] TimerCommand start( TickType_t queue_wait = 0U ) noexcept
    {
        return command_result( xTimerStart( handle_, queue_wait ) );
    }

    [[nodiscard]] TimerCommand reset( TickType_t queue_wait = 0U ) noexcept
    {
        return command_result( xTimerReset( handle_, queue_wait ) );
    }

    [[nodiscard]] TimerCommand change_period( TickType_t period,
                                               TickType_t queue_wait = 0U ) noexcept
    {
        return command_result( xTimerChangePeriod( handle_, period, queue_wait ) );
    }

    [[nodiscard]] TimerCommand stop( TickType_t queue_wait = 0U ) noexcept
    {
        return command_result( xTimerStop( handle_, queue_wait ) );
    }

    [[nodiscard]] bool active() const noexcept
    {
        return xTimerIsTimerActive( handle_ ) != pdFALSE;
    }

    [[nodiscard]] TickType_t period() const noexcept
    {
        return xTimerGetPeriod( handle_ );
    }

    [[nodiscard]] TimerHandle_t native_handle() const noexcept
    {
        return handle_;
    }

private:
    static TimerCommand command_result( BaseType_t result ) noexcept
    {
        return result == pdPASS ? TimerCommand::accepted : TimerCommand::rejected;
    }

    static void trampoline( TimerHandle_t timer ) noexcept
    {
        auto * const context = static_cast< Context * >( pvTimerGetTimerID( timer ) );
        configASSERT( context != nullptr );
        context->on_timer( timer );
    }

    TimerHandle_t handle_;
};

template< typename Context >
using OneShotTimer = SoftwareTimer< Context, TimerPolicy::one_shot >;

template< typename Context >
using PeriodicTimer = SoftwareTimer< Context, TimerPolicy::periodic >;

static_assert( !std::is_copy_constructible_v< OneShotTimer< int > > );
static_assert( !std::is_move_constructible_v< OneShotTimer< int > > );

} // namespace freertos

#endif
