/*
{{copyright}} 
*/

/*
{{version}} 
*/

/*
{{license}} 
*/

#pragma once

#include "UARTBus.h"
#include "criticalSection.h"

#include <sdi_toolBox/generic/circularBuffer.h>

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

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

  bool isTxReady();                       // Returns true if the TX stream is ready and available
  void on_transmitComplete_fromISR();     // Interrupt-based transmission is over

  protected:
  volatile bool m_txBufferIsEmpty = true;

  sdi_toolBox::generic::CircularBuffer<uint8_t, QUEUE_SIZE> m_rxBuffer;

  private:
  void initDevice() override;                                                      // Device initialization
  bool startReceiving() override;                                                  // Start specific receiving method (IT, DMA, ...)
  void receiveData_fromISR(const uint8_t *buffer, const size_t size) override;     // Receive data from interuption
};
//--------------------------------------------------------------

//--------------------------------------------------------------
/* Writes a block of data to the stream */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline size_t UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::write(const std::span<const uint8_t> buffer)
{
  if (!isTxReady())
    return 0;

  if (UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartHdl->gState != HAL_UART_STATE_READY)
    return 0;

  const auto len = std::min(buffer.size(), UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartTXBuffer.size());
  std::memcpy(UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartTXBuffer.data(),
              buffer.data(),
              len);

  if (HAL_OK != HAL_UART_Transmit_IT(UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartHdl,
                                     UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartRXBuffer.data(),
                                     UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartRXBuffer.size()))
    return 0;

  m_txBufferIsEmpty = false;
  return len;
}
//--------------------------------------------------------------
/* Reads a block of data from the stream */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline size_t UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::read(const std::span<uint8_t> buffer)
{
  CriticalSection criticalSection;
  for (size_t i = 0; i < buffer.size(); i++)
  {
    const auto data = m_rxBuffer.pop();
    if (!data)
      return i;
    buffer[i] = *data;
  }
  return buffer.size();
}
//--------------------------------------------------------------
/* Checks if the stream has data available for reading */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline bool UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::isDataAvailable() const
{
  CriticalSection criticalSection;
  return !m_rxBuffer.isEmpty();
}
//--------------------------------------------------------------
/* Returns true if the TX stream is ready and available */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline bool UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::isTxReady()
{
  return m_txBufferIsEmpty;
}
//--------------------------------------------------------------
/* Interrupt-based transmission is over */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline void UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::on_transmitComplete_fromISR()
{
  m_txBufferIsEmpty = true;
}
//--------------------------------------------------------------
/* Start specific receiving method (IT, DMA, ...) */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline bool UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::startReceiving()
{
  return (HAL_OK != HAL_UARTEx_ReceiveToIdle_IT(UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartHdl,
                                                UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartRXBuffer.data(),
                                                UARTBus<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE>::m_uartRXBuffer.size()));
}
//--------------------------------------------------------------
/* Device initialization */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
void UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::initDevice()
{
  // TODO
}
//--------------------------------------------------------------
/* Receive data from interuption */
template<size_t UART_RX_BUFFER_SIZE, size_t UART_TX_BUFFER_SIZE, size_t QUEUE_SIZE>
inline void UARTBusIT<UART_RX_BUFFER_SIZE, UART_TX_BUFFER_SIZE, QUEUE_SIZE>::receiveData_fromISR(const uint8_t *buffer, const size_t size)
{
  // Operations performed within an interrupt routine are considered atomic
  for (size_t i = 0; i < size; i++)
    m_rxBuffer.push(buffer[i]);
}
//--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32
