#include <cstdint>

extern "C"
{
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include "lab_freertos.h"
#include "lab_io.h"
}

#include "freertos/software_timer.hpp"

namespace
{

constexpr std::uint32_t marker = 0x4B3134A5U;
constexpr std::uint32_t stack_words = 256U;

enum class TimerKind : std::uint32_t
{
    one_shot = 1U,
    periodic = 2U
};

struct CallbackContext
{
    TimerKind kind;
    std::uint32_t marker_value;

    void on_timer( TimerHandle_t timer ) noexcept;
};

using OneShot = freertos::OneShotTimer< CallbackContext >;
using Periodic = freertos::PeriodicTimer< CallbackContext >;

struct VerifierContext
{
    OneShot * one_shot;
    Periodic * periodic;
};

std::uintptr_t read_sp() noexcept
{
    std::uintptr_t value;
    __asm__ volatile( "mv %0, sp" : "=r"( value ) );
    return value;
}

bool daemon_name_is_timer_service( const char * name ) noexcept
{
    return name != nullptr && name[ 0 ] == 'T' && name[ 1 ] == 'm' &&
           name[ 2 ] == 'r' && name[ 3 ] == ' ' && name[ 4 ] == 'S';
}

} // namespace

extern "C"
{
StaticTimer_t g_one_shot_storage;
StaticTimer_t g_periodic_storage;
StaticTask_t g_verifier_tcb;
StackType_t g_verifier_stack[ stack_words ];

volatile std::uint32_t g_start_commands_accepted;
volatile std::uint32_t g_callbacks_when_start_returned;
volatile std::uint32_t g_reset_command_accepted;
volatile std::uint32_t g_change_command_accepted;
volatile std::uint32_t g_stop_command_accepted;
volatile std::uint32_t g_verifier_entry_tick;
volatile std::uint32_t g_reset_tick;
volatile std::uint32_t g_change_tick;
volatile std::uint32_t g_one_shot_count;
volatile std::uint32_t g_periodic_count;
volatile std::uint32_t g_one_shot_tick;
volatile std::uint32_t g_periodic_ticks[ 3 ];
volatile std::uint32_t g_one_shot_active_after_expiry;
volatile std::uint32_t g_periodic_active_after_stop;
volatile std::uint32_t g_daemon_handle_match;
volatile std::uint32_t g_daemon_name_match;
volatile std::uintptr_t g_callback_sp;
volatile std::uintptr_t g_verifier_sp;
volatile std::size_t g_heap_before_timer_create;
volatile std::size_t g_heap_after_timer_create;
volatile std::uint32_t g_timer_pass;

__attribute__( ( noinline, used ) )
void software_timer_debug_checkpoint( std::uint32_t point )
{
    ( void ) point;
    __asm__ volatile( "" ::: "memory" );
}
}

namespace
{

CallbackContext g_one_shot_context{ TimerKind::one_shot, marker };
CallbackContext g_periodic_context{ TimerKind::periodic, marker };
VerifierContext g_verifier_context{};

void CallbackContext::on_timer( TimerHandle_t timer ) noexcept
{
    if( marker_value != marker || timer == nullptr )
    {
        lab_fail( 0x4B140101U );
    }

    TaskHandle_t const current = xTaskGetCurrentTaskHandle();
    TaskHandle_t const daemon = xTimerGetTimerDaemonTaskHandle();
    g_daemon_handle_match = current == daemon ? 1U : 0U;
    g_daemon_name_match = daemon_name_is_timer_service( pcTaskGetName( current ) ) ? 1U : 0U;
    g_callback_sp = read_sp();

    if( kind == TimerKind::one_shot )
    {
        ++g_one_shot_count;
        g_one_shot_tick = xTaskGetTickCount();
        software_timer_debug_checkpoint( 5U );
        return;
    }

    std::uint32_t const index = g_periodic_count;
    if( index >= 3U )
    {
        lab_fail( 0x4B140102U );
    }
    g_periodic_ticks[ index ] = xTaskGetTickCount();
    g_periodic_count = index + 1U;
    software_timer_debug_checkpoint( 3U + index );
}

extern "C" void verifier_entry( void * raw_context )
{
    auto * const context = static_cast< VerifierContext * >( raw_context );
    if( context == nullptr || context->one_shot == nullptr || context->periodic == nullptr )
    {
        lab_fail( 0x4B140201U );
    }

    g_verifier_sp = read_sp();
    g_verifier_entry_tick = xTaskGetTickCount();
    software_timer_debug_checkpoint( 1U );
    if( g_callbacks_when_start_returned != 0U )
    {
        lab_fail( 0x4B140202U );
    }

    vTaskDelay( 1U );
    g_reset_tick = xTaskGetTickCount();
    g_reset_command_accepted =
        context->one_shot->reset() == freertos::TimerCommand::accepted ? 1U : 0U;
    software_timer_debug_checkpoint( 2U );

    while( g_periodic_count < 2U )
    {
        vTaskDelay( 1U );
    }

    g_change_tick = xTaskGetTickCount();
    g_change_command_accepted =
        context->periodic->change_period( 3U ) == freertos::TimerCommand::accepted ? 1U : 0U;
    software_timer_debug_checkpoint( 6U );

    while( g_one_shot_count < 1U || g_periodic_count < 3U )
    {
        vTaskDelay( 1U );
    }

    g_one_shot_active_after_expiry = context->one_shot->active() ? 1U : 0U;
    g_stop_command_accepted =
        context->periodic->stop() == freertos::TimerCommand::accepted ? 1U : 0U;
    vTaskDelay( 1U );
    g_periodic_active_after_stop = context->periodic->active() ? 1U : 0U;
    std::uint32_t const count_after_stop = g_periodic_count;
    vTaskDelay( 4U );

    g_timer_pass =
        g_start_commands_accepted == 2U &&
        g_callbacks_when_start_returned == 0U &&
        g_reset_command_accepted == 1U &&
        g_change_command_accepted == 1U &&
        g_stop_command_accepted == 1U &&
        g_reset_tick == 1U &&
        g_change_tick == 4U &&
        g_one_shot_count == 1U &&
        g_one_shot_tick == 6U &&
        g_periodic_count == 3U &&
        g_periodic_ticks[ 0 ] == 2U &&
        g_periodic_ticks[ 1 ] == 4U &&
        g_periodic_ticks[ 2 ] == 7U &&
        g_one_shot_active_after_expiry == 0U &&
        g_periodic_active_after_stop == 0U &&
        count_after_stop == 3U &&
        g_daemon_handle_match == 1U &&
        g_daemon_name_match == 1U &&
        g_callback_sp != g_verifier_sp &&
        g_heap_before_timer_create == g_heap_after_timer_create ? 1U : 0U;

    software_timer_debug_checkpoint( 7U );
    if( g_timer_pass == 0U )
    {
        lab_fail( 0x4B140203U );
    }

    lab_puts( "PASS task01: static one-shot/periodic timer daemon\n" );
    lab_put_u32( g_start_commands_accepted );
    lab_put_u32( g_one_shot_tick );
    lab_put_u32( g_periodic_ticks[ 0 ] );
    lab_put_u32( g_periodic_ticks[ 1 ] );
    lab_put_u32( g_periodic_ticks[ 2 ] );
    lab_exit( 0U );
}

} // namespace

int main()
{
    lab_heap_initialize();
    g_heap_before_timer_create = xPortGetFreeHeapSize();

    OneShot one_shot( g_one_shot_storage, "one-shot", 5U, g_one_shot_context );
    Periodic periodic( g_periodic_storage, "periodic", 2U, g_periodic_context );
    if( !one_shot.valid() || !periodic.valid() )
    {
        lab_fail( 0x4B140301U );
    }

    g_heap_after_timer_create = xPortGetFreeHeapSize();
    g_start_commands_accepted =
        ( one_shot.start() == freertos::TimerCommand::accepted ? 1U : 0U ) +
        ( periodic.start() == freertos::TimerCommand::accepted ? 1U : 0U );
    g_callbacks_when_start_returned = g_one_shot_count + g_periodic_count;

    g_verifier_context.one_shot = &one_shot;
    g_verifier_context.periodic = &periodic;
    if( xTaskCreateStatic( verifier_entry,
                           "verifier",
                           stack_words,
                           &g_verifier_context,
                           2U,
                           g_verifier_stack,
                           &g_verifier_tcb ) == nullptr )
    {
        lab_fail( 0x4B140302U );
    }

    vTaskStartScheduler();
    lab_fail( 0x4B140303U );
}
