#include <cassert>
#include <cstdint>
#include <new>
#include <type_traits>

#include "freertos/software_timer.hpp"

struct FakeTimer
{
    TickType_t period;
    bool auto_reload;
    bool active;
    void * context;
    TimerCallbackFunction_t callback;
};

extern "C" TimerHandle_t xTimerCreateStatic( const char *, TickType_t period,
                                               BaseType_t auto_reload, void * context,
                                               TimerCallbackFunction_t callback,
                                               StaticTimer_t * storage )
{
    static_assert( sizeof( FakeTimer ) <= sizeof( StaticTimer_t ) );
    return new ( storage->bytes ) FakeTimer{ period, auto_reload != pdFALSE,
                                             false, context, callback };
}

extern "C" BaseType_t xTimerStart( TimerHandle_t timer, TickType_t )
{
    timer->active = true;
    return pdPASS;
}

extern "C" BaseType_t xTimerReset( TimerHandle_t timer, TickType_t )
{
    timer->active = true;
    return pdPASS;
}

extern "C" BaseType_t xTimerChangePeriod( TimerHandle_t timer, TickType_t period, TickType_t )
{
    timer->period = period;
    timer->active = true;
    return pdPASS;
}

extern "C" BaseType_t xTimerStop( TimerHandle_t timer, TickType_t )
{
    timer->active = false;
    return pdPASS;
}

extern "C" BaseType_t xTimerIsTimerActive( TimerHandle_t timer )
{
    return timer->active ? pdTRUE : pdFALSE;
}

extern "C" TickType_t xTimerGetPeriod( TimerHandle_t timer )
{
    return timer->period;
}

extern "C" void * pvTimerGetTimerID( TimerHandle_t timer )
{
    return timer->context;
}

static void fire( TimerHandle_t timer )
{
    assert( timer->active );
    if( !timer->auto_reload )
    {
        timer->active = false;
    }
    timer->callback( timer );
}

struct Probe
{
    std::uint32_t calls{};
    TimerHandle_t seen{};

    void on_timer( TimerHandle_t timer ) noexcept
    {
        ++calls;
        seen = timer;
    }
};

int main()
{
    StaticTimer_t one_storage{};
    StaticTimer_t periodic_storage{};
    Probe one_probe{};
    Probe periodic_probe{};

    freertos::OneShotTimer< Probe > one( one_storage, "one", 5U, one_probe );
    freertos::PeriodicTimer< Probe > periodic( periodic_storage, "periodic", 2U,
                                                periodic_probe );

    static_assert( !std::is_copy_constructible_v< decltype( one ) > );
    static_assert( !std::is_move_constructible_v< decltype( periodic ) > );
    assert( one.valid() && periodic.valid() );
    assert( one.start() == freertos::TimerCommand::accepted );
    assert( periodic.start() == freertos::TimerCommand::accepted );
    assert( one_probe.calls == 0U && periodic_probe.calls == 0U );

    fire( one.native_handle() );
    fire( periodic.native_handle() );
    assert( one_probe.calls == 1U && one_probe.seen == one.native_handle() );
    assert( !one.active() && periodic.active() );

    assert( one.reset() == freertos::TimerCommand::accepted );
    assert( one.active() );
    assert( periodic.change_period( 3U ) == freertos::TimerCommand::accepted );
    assert( periodic.period() == 3U );
    fire( periodic.native_handle() );
    assert( periodic_probe.calls == 2U );
    assert( periodic.stop() == freertos::TimerCommand::accepted );
    assert( !periodic.active() );
}
