#ifndef FREERTOS_ISR_DRIVERS_HPP
#define FREERTOS_ISR_DRIVERS_HPP

#include <cstddef>
#include <cstdint>
#include <type_traits>

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

namespace freertos
{

class IsrContext final
{
public:
    void merge_wake_request( BaseType_t local_request ) noexcept
    {
        if( local_request != pdFALSE )
        {
            higher_priority_task_woken_ = pdTRUE;
        }
    }

    [[nodiscard]] bool wake_requested() const noexcept
    {
        return higher_priority_task_woken_ != pdFALSE;
    }

    [[nodiscard]] std::uint32_t yield_calls() const noexcept
    {
        return yield_calls_;
    }

    [[nodiscard]] bool yield_if_needed() noexcept
    {
        ++yield_calls_;
        bool const requested = wake_requested();
        portYIELD_FROM_ISR( higher_priority_task_woken_ );
        return requested;
    }

private:
    BaseType_t higher_priority_task_woken_{ pdFALSE };
    std::uint32_t yield_calls_{ 0U };
};

enum class IsrSendResult
{
    queued,
    dropped_newest
};

template< typename T, std::size_t Capacity >
class StaticIsrQueue final
{
    static_assert( Capacity > 0U );
    static_assert( std::is_trivially_copyable_v< T > );

public:
    StaticIsrQueue( StaticQueue_t & control, std::uint8_t * bytes ) noexcept
        : handle_( xQueueCreateStatic( static_cast< UBaseType_t >( Capacity ),
                                       static_cast< UBaseType_t >( sizeof( T ) ),
                                       bytes,
                                       &control ) )
    {
    }

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

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

    [[nodiscard]] IsrSendResult send_from_isr( const T & item,
                                               IsrContext & context ) noexcept
    {
        BaseType_t local_wake = pdFALSE;
        BaseType_t const result = xQueueSendFromISR( handle_, &item, &local_wake );
        context.merge_wake_request( local_wake );
        return result == pdPASS ? IsrSendResult::queued
                                : IsrSendResult::dropped_newest;
    }

    [[nodiscard]] bool receive( T & item, TickType_t timeout ) noexcept
    {
        return xQueueReceive( handle_, &item, timeout ) == pdPASS;
    }

private:
    QueueHandle_t handle_;
};

class TaskNotificationRef final
{
public:
    explicit constexpr TaskNotificationRef( TaskHandle_t task ) noexcept
        : task_( task )
    {
    }

    [[nodiscard]] bool give_from_isr( IsrContext & context ) const noexcept
    {
        BaseType_t local_wake = pdFALSE;
        vTaskNotifyGiveFromISR( task_, &local_wake );
        context.merge_wake_request( local_wake );
        return true;
    }

private:
    TaskHandle_t task_;
};

struct UartRegisters
{
    volatile std::uint32_t status;
    volatile std::uint32_t rx_data[ 2 ];
    volatile std::uint32_t rx_count;
    volatile std::uint32_t control;
};

class UartTaskView final
{
public:
    explicit constexpr UartTaskView( UartRegisters & registers ) noexcept
        : registers_( &registers )
    {
    }

    void enable_rx_irq() const noexcept { registers_->control = 1U; }

    void inject_rx_pair_for_test( std::uint8_t first,
                                  std::uint8_t second ) const noexcept
    {
        registers_->rx_data[ 0 ] = first;
        registers_->rx_data[ 1 ] = second;
        registers_->rx_count = 2U;
        registers_->status = 1U;
    }

private:
    UartRegisters * registers_;
};

class UartIsrView final
{
public:
    explicit constexpr UartIsrView( UartRegisters & registers ) noexcept
        : registers_( &registers )
    {
    }

    [[nodiscard]] bool rx_ready_from_isr() const noexcept
    {
        return ( registers_->status & 1U ) != 0U &&
               registers_->rx_count != 0U;
    }

    [[nodiscard]] std::uint8_t read_rx_from_isr() const noexcept
    {
        configASSERT( rx_ready_from_isr() );
        std::uint8_t const byte =
            static_cast< std::uint8_t >( registers_->rx_data[ 0 ] );
        registers_->rx_data[ 0 ] = registers_->rx_data[ 1 ];
        --registers_->rx_count;
        if( registers_->rx_count == 0U )
        {
            registers_->status = 0U;
        }
        return byte;
    }

private:
    UartRegisters * registers_;
};

class Uart final
{
public:
    explicit constexpr Uart( UartRegisters & registers ) noexcept
        : registers_( &registers ) {}

    [[nodiscard]] UartTaskView task_view() const noexcept
    {
        return UartTaskView{ *registers_ };
    }

    [[nodiscard]] UartIsrView isr_view() const noexcept
    {
        return UartIsrView{ *registers_ };
    }

private:
    UartRegisters * registers_;
};

struct GpioRegisters
{
    volatile std::uint32_t direction;
    volatile std::uint32_t data;
    volatile std::uint32_t edge_status;
};

class GpioTaskView final
{
public:
    explicit constexpr GpioTaskView( GpioRegisters & registers ) noexcept
        : registers_( &registers ) {}

    void configure_output( std::uint32_t mask ) const noexcept
    {
        registers_->direction |= mask;
    }

    void write_output( std::uint32_t mask, bool high ) const noexcept
    {
        if( high ) { registers_->data |= mask; }
        else { registers_->data &= ~mask; }
    }

    void inject_edge_for_test( std::uint32_t mask ) const noexcept
    {
        registers_->edge_status |= mask;
    }

private:
    GpioRegisters * registers_;
};

class GpioIsrView final
{
public:
    explicit constexpr GpioIsrView( GpioRegisters & registers ) noexcept
        : registers_( &registers ) {}

    [[nodiscard]] bool edge_pending_from_isr( std::uint32_t mask ) const noexcept
    {
        return ( registers_->edge_status & mask ) != 0U;
    }

    void clear_edge_from_isr( std::uint32_t mask ) const noexcept
    {
        registers_->edge_status &= ~mask;
    }

private:
    GpioRegisters * registers_;
};

class GpioPin final
{
public:
    explicit constexpr GpioPin( GpioRegisters & registers ) noexcept
        : registers_( &registers ) {}

    [[nodiscard]] GpioTaskView task_view() const noexcept
    {
        return GpioTaskView{ *registers_ };
    }

    [[nodiscard]] GpioIsrView isr_view() const noexcept
    {
        return GpioIsrView{ *registers_ };
    }

private:
    GpioRegisters * registers_;
};

} // namespace freertos

#endif
