#include <cstddef>
#include <cstdint>

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

#include "freertos/isr_drivers.hpp"

namespace
{
constexpr std::uintptr_t io_base = 0xC0000000UL;
constexpr std::uintptr_t io_set_irq = io_base + 0x020UL;
constexpr std::uintptr_t io_clear_irq = io_base + 0x030UL;
constexpr std::uint32_t irq_number = 5U;
constexpr std::uint32_t irq_mask = 1UL << irq_number;
constexpr std::uint32_t gpio_led_mask = 1U;
constexpr std::uint32_t stack_words = 256U;

volatile std::uint32_t & io_register( std::uintptr_t address ) noexcept
{
    return *reinterpret_cast< volatile std::uint32_t * >( address );
}

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

std::uint32_t read_mcause() noexcept
{
    std::uint32_t value;
    __asm__ volatile( "csrr %0, mcause" : "=r"( value ) );
    return value;
}

using RxQueue = freertos::StaticIsrQueue< std::uint8_t, 1U >;
RxQueue * g_rx_queue;
TaskHandle_t g_receiver_handle;
}

extern "C"
{
freertos::UartRegisters g_uart_registers;
freertos::GpioRegisters g_gpio_registers;
StaticQueue_t g_rx_queue_control;
std::uint8_t g_rx_queue_storage[ 1U ];
StaticTask_t g_receiver_tcb;
StaticTask_t g_stimulus_tcb;
StaticTask_t g_verifier_tcb;
StackType_t g_receiver_stack[ stack_words ];
StackType_t g_stimulus_stack[ stack_words ];
StackType_t g_verifier_stack[ stack_words ];

volatile std::uint32_t g_mcause;
volatile std::uint32_t g_irq_after_clear;
volatile std::uint32_t g_uart_status_before_isr;
volatile std::uint32_t g_uart_status_after_isr;
volatile std::uint32_t g_gpio_edge_before_isr;
volatile std::uint32_t g_gpio_edge_after_isr;
volatile std::uint32_t g_rx_queued;
volatile std::uint32_t g_rx_dropped;
volatile std::uint32_t g_queued_byte;
volatile std::uint32_t g_dropped_byte;
volatile std::uint32_t g_notification_sent;
volatile std::uint32_t g_notification_taken;
volatile std::uint32_t g_higher_priority_task_woken;
volatile std::uint32_t g_yield_requested;
volatile std::uint32_t g_isr_yield_calls;
volatile std::uint32_t g_receiver_byte;
volatile std::uint32_t g_receiver_tick;
volatile std::uint32_t g_stimulus_after_seen_by_receiver;
volatile std::uint32_t g_receiver_done;
volatile std::uint32_t g_stimulus_after;
volatile std::uint32_t g_receiver_done_at_stimulus_resume;
volatile std::uintptr_t g_isr_sp;
volatile std::uintptr_t g_receiver_sp;
volatile std::uintptr_t g_stimulus_sp;
volatile std::size_t g_heap_before_queue;
volatile std::size_t g_heap_after_queue;
volatile std::uint32_t g_isr_drivers_pass;

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

__attribute__( ( noinline, used ) )
void freertos_risc_v_application_interrupt_handler()
{
    freertos::IsrContext context;
    freertos::Uart uart{ g_uart_registers };
    freertos::GpioPin gpio{ g_gpio_registers };
    auto const uart_isr = uart.isr_view();
    auto const gpio_isr = gpio.isr_view();

    g_mcause = read_mcause();
    g_isr_sp = read_sp();
    g_uart_status_before_isr = g_uart_registers.status;
    g_gpio_edge_before_isr = g_gpio_registers.edge_status;
    isr_drivers_debug_checkpoint( 2U );

    for( std::uint32_t index = 0U;
         index < 2U && uart_isr.rx_ready_from_isr();
         ++index )
    {
        std::uint8_t const byte = uart_isr.read_rx_from_isr();
        auto const result = g_rx_queue->send_from_isr( byte, context );
        if( result == freertos::IsrSendResult::queued )
        {
            ++g_rx_queued;
            g_queued_byte = byte;
        }
        else
        {
            ++g_rx_dropped;
            g_dropped_byte = byte;
        }
    }

    if( gpio_isr.edge_pending_from_isr( gpio_led_mask ) )
    {
        gpio_isr.clear_edge_from_isr( gpio_led_mask );
        freertos::TaskNotificationRef notification{ g_receiver_handle };
        g_notification_sent = notification.give_from_isr( context ) ? 1U : 0U;
    }

    g_uart_status_after_isr = g_uart_registers.status;
    g_gpio_edge_after_isr = g_gpio_registers.edge_status;
    io_register( io_clear_irq ) = irq_mask;
    g_irq_after_clear = io_register( io_clear_irq );
    g_higher_priority_task_woken = context.wake_requested() ? 1U : 0U;
    g_yield_requested = context.yield_if_needed() ? 1U : 0U;
    g_isr_yield_calls = context.yield_calls();
    isr_drivers_debug_checkpoint( 3U );
}
}

namespace
{
extern "C" void receiver_entry( void * )
{
    g_receiver_sp = read_sp();
    std::uint8_t byte = 0U;
    isr_drivers_debug_checkpoint( 1U );
    if( !g_rx_queue->receive( byte, portMAX_DELAY ) )
    {
        lab_fail( 0x4B150101U );
    }
    g_receiver_byte = byte;
    g_receiver_tick = xTaskGetTickCount();
    g_notification_taken = ulTaskNotifyTake( pdTRUE, 0U );
    g_stimulus_after_seen_by_receiver = g_stimulus_after;
    freertos::GpioPin gpio{ g_gpio_registers };
    gpio.task_view().write_output( gpio_led_mask, ( byte & 1U ) != 0U );
    g_receiver_done = 1U;
    isr_drivers_debug_checkpoint( 4U );
    vTaskDelete( nullptr );
    lab_fail( 0x4B150102U );
}

extern "C" void stimulus_entry( void * )
{
    g_stimulus_sp = read_sp();
    vTaskDelay( 1U );
    freertos::Uart uart{ g_uart_registers };
    freertos::GpioPin gpio{ g_gpio_registers };
    uart.task_view().inject_rx_pair_for_test( 0x41U, 0x42U );
    gpio.task_view().inject_edge_for_test( gpio_led_mask );
    isr_drivers_debug_checkpoint( 5U );
    io_register( io_set_irq ) = irq_mask;
    __asm__ volatile( "nop" ::: "memory" );
    g_stimulus_after = 1U;
    g_receiver_done_at_stimulus_resume = g_receiver_done;
    isr_drivers_debug_checkpoint( 6U );
    vTaskDelete( nullptr );
    lab_fail( 0x4B150201U );
}

extern "C" void verifier_entry( void * )
{
    TickType_t const started = xTaskGetTickCount();
    while( g_stimulus_after == 0U || g_receiver_done == 0U )
    {
        if( xTaskGetTickCount() - started > 20U )
        {
            lab_fail( 0x4B150301U );
        }
        vTaskDelay( 1U );
    }

    g_isr_drivers_pass =
        g_mcause == 0x8000000bU &&
        g_irq_after_clear == 0U &&
        g_uart_status_before_isr == 1U &&
        g_uart_status_after_isr == 0U &&
        g_gpio_edge_before_isr == 1U &&
        g_gpio_edge_after_isr == 0U &&
        g_rx_queued == 1U &&
        g_rx_dropped == 1U &&
        g_queued_byte == 0x41U &&
        g_dropped_byte == 0x42U &&
        g_notification_sent == 1U &&
        g_notification_taken == 1U &&
        g_higher_priority_task_woken == 1U &&
        g_yield_requested == 1U &&
        g_isr_yield_calls == 1U &&
        g_receiver_byte == 0x41U &&
        g_receiver_tick == 1U &&
        g_stimulus_after_seen_by_receiver == 0U &&
        g_receiver_done_at_stimulus_resume == 1U &&
        g_gpio_registers.direction == gpio_led_mask &&
        g_gpio_registers.data == gpio_led_mask &&
        g_isr_sp != g_receiver_sp &&
        g_isr_sp != g_stimulus_sp &&
        g_heap_before_queue == g_heap_after_queue ? 1U : 0U;

    isr_drivers_debug_checkpoint( 7U );
    if( g_isr_drivers_pass == 0U )
    {
        lab_fail( 0x4B150302U );
    }
    lab_puts( "PASS task01: real IRQ, UART queue, GPIO notify, one yield\n" );
    lab_put_u32( g_mcause );
    lab_put_u32( g_receiver_byte );
    lab_put_u32( g_dropped_byte );
    lab_put_u32( g_higher_priority_task_woken );
    lab_put_u32( g_isr_yield_calls );
    lab_exit( 0U );
}
}

int main()
{
    lab_heap_initialize();
    g_heap_before_queue = xPortGetFreeHeapSize();
    RxQueue queue{ g_rx_queue_control, g_rx_queue_storage };
    if( !queue.valid() ) { lab_fail( 0x4B150401U ); }
    g_rx_queue = &queue;
    g_heap_after_queue = xPortGetFreeHeapSize();

    freertos::Uart uart{ g_uart_registers };
    freertos::GpioPin gpio{ g_gpio_registers };
    uart.task_view().enable_rx_irq();
    gpio.task_view().configure_output( gpio_led_mask );
    h3irq_enable( irq_number, true );
    h3irq_set_priority( irq_number, 8U );

    g_receiver_handle =
        xTaskCreateStatic( receiver_entry, "rx-worker", stack_words, nullptr, 3U,
                           g_receiver_stack, &g_receiver_tcb );
    TaskHandle_t const stimulus =
        xTaskCreateStatic( stimulus_entry, "irq-source", stack_words, nullptr, 2U,
                           g_stimulus_stack, &g_stimulus_tcb );
    TaskHandle_t const verifier =
        xTaskCreateStatic( verifier_entry, "verifier", stack_words, nullptr, 1U,
                           g_verifier_stack, &g_verifier_tcb );
    if( g_receiver_handle == nullptr || stimulus == nullptr || verifier == nullptr )
    {
        lab_fail( 0x4B150402U );
    }
    vTaskStartScheduler();
    lab_fail( 0x4B150403U );
}
