/*
{{copyright}} 
*/

/*
{{version}} 
*/

/*
{{license}} 
*/

#pragma once

#include "../../generic/FreeRTOS/mutex.h"
#include "../HIDBus.h"

namespace sdi_uToolBox::stm32::FreeRTOS
{
//--------------------------------------------------------------
template<size_t QUEUE_SIZE>
class HIDBus : public stm32::HIDBus<0>
{
  public:
  HIDBus();                                                    // Constructor
  ~HIDBus() override                            = default;     // Destructor
  HIDBus(const HIDBus &other)                = delete;      // Copy constructor
  HIDBus(HIDBus &&other) noexcept            = delete;      // Move constructor
  HIDBus &operator=(const HIDBus &other)     = delete;      // Copy assignment
  HIDBus &operator=(HIDBus &&other) noexcept = delete;      // Move assignment

  void onReceiveData(std::span<const uint8_t> buf) override;

  size_t write(const std::span<const uint8_t> buffer) override;
  size_t read(const std::span<uint8_t> buffer) override;                                         // Reads a block of data from the stream
  size_t read(const std::span<uint8_t> buffer, const std::chrono::milliseconds ms) override;     // If available, wait for block of data from the stream
  bool   isDataAvailable() const override;                                                       // Checks if the stream has data available for reading

  protected:
  sdi_uToolBox::generic::FreeRTOS::Mutex m_mtx;
  QueueHandle_t m_uartRxQueue = nullptr;
};
//--------------------------------------------------------------

//--------------------------------------------------------------
/* Constructor */
template<size_t QUEUE_SIZE>
inline HIDBus<QUEUE_SIZE>::HIDBus()
{
  // Initialization of FreeRTOS queue
  m_uartRxQueue = xQueueCreate(QUEUE_SIZE, sizeof(uint8_t));
}
//--------------------------------------------------------------
/* Receive data from interuption */
template<size_t QUEUE_SIZE>
void HIDBus<QUEUE_SIZE>::onReceiveData(std::span<const uint8_t> buf)
{
  auto xHigherPriorityTaskWoken = pdFALSE;

  // Copy each bytes into the queue
  for (const auto byte : buf)
  {
    xQueueSendFromISR(m_uartRxQueue,
                      &byte,
                      &xHigherPriorityTaskWoken);

    // If the data reception has woken up a higher priority task,
    // force a context switch immediately after the ISR
    portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
  }
}
//--------------------------------------------------------------
template<size_t QUEUE_SIZE>
size_t HIDBus<QUEUE_SIZE>::write(const std::span<const uint8_t> buffer)
{
  std::lock_guard lock(m_mtx);
  return stm32::HIDBus<0>::write(buffer);
}
//--------------------------------------------------------------
/* Reads a block of data from the stream */
template<size_t QUEUE_SIZE>
inline size_t HIDBus<QUEUE_SIZE>::read(const std::span<uint8_t> buffer)
{
  return read(buffer, std::chrono::milliseconds(0));
}
//--------------------------------------------------------------
/* If available, wait for block of data from the stream */
template<size_t QUEUE_SIZE>
inline size_t HIDBus<QUEUE_SIZE>::read(const std::span<uint8_t> buffer, const std::chrono::milliseconds ms)
{
  for (size_t i = 0; i < buffer.size(); i++)
  {
    uint8_t received_byte;
    if (xQueueReceive(m_uartRxQueue,
                      &received_byte,
                      pdMS_TO_TICKS(ms.count())) != pdPASS)
      return i;
    buffer[i] = received_byte;
  }
  return buffer.size();
}
//--------------------------------------------------------------
/* Checks if the stream has data available for reading */
template<size_t QUEUE_SIZE>
inline bool HIDBus<QUEUE_SIZE>::isDataAvailable() const
{
  const auto count = uxQueueMessagesWaiting(m_uartRxQueue);
  return count > 0;
}
//--------------------------------------------------------------

//--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32::FreeRTOS
