//{{copyright}}

//{{version}}

//{{license}}

#pragma once

#include "../UARTBus.h"

#include <atomic>
#include <sdi_uToolBox/generic/FreeRTOS/mutex.h>
#include <sdi_uToolBox/generic/FreeRTOS/semaphore.h>

namespace sdi_uToolBox::stm32::comm::FreeRTOS
{
//--------------------------------------------------------------
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
class UARTBusIT : public stm32::comm::UARTBus
  , public UARTBusCallback
{
  public:
  using Mutex           = generic::FreeRTOS::Mutex;
  using BinarySemaphore = generic::FreeRTOS::BinarySemaphore;

  public:
  UARTBusIT() = default;                                // Default constructor
  virtual ~UARTBusIT();                                 // Default destructor
  UARTBusIT(const UARTBusIT &)            = delete;     // Copy constructor
  UARTBusIT(UARTBusIT &&)                 = delete;     // Move constructor
  UARTBusIT &operator=(const UARTBusIT &) = delete;     // Copy assignment operator
  UARTBusIT &operator=(UARTBusIT &&)      = delete;     // Move assignment operator

  ReturnStatus configure(UART_HandleTypeDef *handle) override;     // Configure UART bus with HAL handle

  ReturnStatus open() override;      // Open UART bus
  ReturnStatus close() override;     // Close UART bus

  ReturnType write(WriteBuf buf, Timeout timeout) const override;     // Write data to UART device
  ReturnType read(ReadBuf buf, Timeout timeout) const override;       // Read data from UART device
  ReturnType readUntilIdle(ReadBuf buf, Timeout timeout) const;       // Read data until IDLE event

  // Callback to be called from ISR when TX, RX is complete or error occurs
  void txCompleteCallback() const override;     // TX complete callback
  void rxCompleteCallback() const override;     // RX complete callback
  void rxEventCallback(uint16_t size) const override;     // IDLE line detected callback
  void errorCallback() const override;          // Error callback

  protected:
  mutable Mutex           m_txMtx;                    // Mutex for TX thread safety
  mutable Mutex           m_rxMtx;                    // Mutex for RX thread safety
  mutable BinarySemaphore m_txSemaphore{ false };     // Semaphore for TX completion
  mutable BinarySemaphore m_rxSemaphore{ false };     // Semaphore for RX completion

  mutable std::array<uint8_t, TX_BUFFER_SIZE> m_txBuffer;
  mutable std::array<uint8_t, RX_BUFFER_SIZE> m_rxBuffer;

  mutable std::atomic<size_t> m_lastTxSize{ 0 };
  mutable std::atomic<size_t> m_lastRxSize{ 0 };
  mutable std::atomic<bool>   m_txError{ false };
  mutable std::atomic<bool>   m_rxError{ false };
};

//--------------------------------------------------------------
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::~UARTBusIT()
{
  UARTBusCallbackRegistry::unregisterInstance(this);
}
//--------------------------------------------------------------
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline ReturnStatus UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::configure(UART_HandleTypeDef *handle)
{
  const auto ret = UARTBus::configure(handle);
  if (ret != ReturnStatus::Ok)
    return ret;

  UARTBusCallbackRegistry::registerInstance(handle, this);
  return ReturnStatus::Ok;
}
//--------------------------------------------------------------
/* Open UART bus */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline ReturnStatus UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::open()
{
  std::scoped_lock lock(m_txMtx, m_rxMtx);
  return UARTBus::open();
}
//--------------------------------------------------------------
/* Close UART bus */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline ReturnStatus UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::close()
{
  std::scoped_lock lock(m_txMtx, m_rxMtx);
  __HAL_UART_DISABLE_IT(m_uartHandle, UART_IT_IDLE);     // Disable IDLE interrupt
  return UARTBus::close();
}
//--------------------------------------------------------------
/* Write data to UART device */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline UARTBus::ReturnType UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::write(const WriteBuf buf, const Timeout timeout) const
{
  std::scoped_lock lock(m_txMtx);

  if (!m_uartHandle)
    return std::unexpected(ReturnStatus::InvalidHandle);

  if (buf.size() > TX_BUFFER_SIZE)
    return std::unexpected(ReturnStatus::Error);

  // Copy data to internal buffer
  std::copy_n(buf.begin(), buf.size(), m_txBuffer.begin());

  // Reset flags
  m_txError    = false;
  m_lastTxSize = 0;

  // Start transmission with interrupt
  const auto ret = HAL_UART_Transmit_IT(m_uartHandle,
                                        m_txBuffer.data(),
                                        buf.size());
  if (ret != HAL_OK)
  {
    if (ret == HAL_BUSY)
      return std::unexpected(ReturnStatus::Busy);
    return std::unexpected(ReturnStatus::Error);
  }

  // Wait for transmission to complete. The timeout is handled by the semaphore
  // The task will be unblocked when the semaphore is given in the ISR
  if (!m_txSemaphore.take(timeout))
  {
    HAL_UART_AbortTransmit(m_uartHandle);
    return std::unexpected(ReturnStatus::Timeout);
  }
  if (m_txError)
    return std::unexpected(ReturnStatus::Error);

  return m_lastTxSize;     // Return number of bytes transmitted
}
//--------------------------------------------------------------
/* Read data from UART device */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline UARTBus::ReturnType UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::read(const ReadBuf buf, const Timeout timeout) const
{
  std::scoped_lock lock(m_rxMtx);

  if (!m_uartHandle)
    return std::unexpected(ReturnStatus::InvalidHandle);

  if (buf.size() > RX_BUFFER_SIZE)
    return std::unexpected(ReturnStatus::Error);

  // Reset flags
  m_rxError    = false;
  m_lastRxSize = 0;

  // Start reception with interrupt
  const auto ret = HAL_UART_Receive_IT(m_uartHandle,
                                       m_rxBuffer.data(),
                                       buf.size());
  if (ret != HAL_OK)
  {
    if (ret == HAL_BUSY)
      return std::unexpected(ReturnStatus::Busy);
    return std::unexpected(ReturnStatus::Error);
  }

  // Wait for reception to complete. The timeout is handled by the semaphore
  // The task will be unblocked when the semaphore is given in the ISR
  if (!m_rxSemaphore.take(timeout))
  {
    HAL_UART_AbortReceive(m_uartHandle);
    return std::unexpected(ReturnStatus::Timeout);
  }

  if (m_rxError)
    return std::unexpected(ReturnStatus::Error);

  // Copy received data to user buffer
  std::copy_n(m_rxBuffer.begin(), m_lastRxSize.load(), buf.begin());

  return m_lastRxSize;     // Return number of bytes received
}
//--------------------------------------------------------------
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline UARTBus::ReturnType UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::readUntilIdle(ReadBuf buf, Timeout timeout) const
{
  std::scoped_lock lock(m_rxMtx);

  if (!m_uartHandle)
    return std::unexpected(ReturnStatus::InvalidHandle);

  if (buf.size() > RX_BUFFER_SIZE)
    return std::unexpected(ReturnStatus::Error);

  // Reset flags
  m_rxError    = false;
  m_lastRxSize = 0;


  // Start reception with interrupt
  const auto ret = HAL_UART_ReceiveToIdle_IT(m_uartHandle,
                                       m_rxBuffer.data(),
                                       buf.size());
  if (ret != HAL_OK)
  {
    if (ret == HAL_BUSY)
      return std::unexpected(ReturnStatus::Busy);
    return std::unexpected(ReturnStatus::Error);
  }

  // Wait for reception to complete or idled. The timeout is handled by the semaphore
  // The task will be unblocked when the semaphore is given in the ISR
  if (!m_rxSemaphore.take(timeout))
  {
    HAL_UART_AbortReceive(m_uartHandle);
    return std::unexpected(ReturnStatus::Timeout);
  }

  if (m_rxError)
    return std::unexpected(ReturnStatus::Error);

  // Copy received data to user buffer
  std::copy_n(m_rxBuffer.begin(), m_lastRxSize.load(), buf.begin());

  return m_lastRxSize;     // Return number of bytes received
}
//--------------------------------------------------------------
/* TX complete callback */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline void UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::txCompleteCallback() const
{
  m_lastTxSize = m_uartHandle->TxXferSize - m_uartHandle->TxXferCount;
  (void)m_txSemaphore.giveFromISR();
}
//--------------------------------------------------------------
/* RX complete callback */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline void UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::rxCompleteCallback() const
{
  m_lastRxSize = m_uartHandle->RxXferSize - m_uartHandle->RxXferCount;
  (void)m_rxSemaphore.giveFromISR();
}
//--------------------------------------------------------------
/* IDLE line detected callback */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline void UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::rxEventCallback(const uint16_t size) const
{
  if (m_uartHandle->RxState == HAL_UART_STATE_BUSY_RX)
  {
    // Stop IT transfer
    HAL_UART_AbortReceive_IT(m_uartHandle);

    // Calculate received bytes
    //m_lastRxSize = m_uartHandle->RxXferSize - m_uartHandle->RxXferCount;
    m_lastRxSize = size;

    (void)m_rxSemaphore.giveFromISR();
  }
}
//--------------------------------------------------------------
/* Error callback */
template<size_t TX_BUFFER_SIZE, size_t RX_BUFFER_SIZE>
inline void UARTBusIT<TX_BUFFER_SIZE, RX_BUFFER_SIZE>::errorCallback() const
{
  if (m_uartHandle->gState == HAL_UART_STATE_BUSY_TX)
  {
    m_txError = true;
    (void)m_txSemaphore.giveFromISR();
  }

  if (m_uartHandle->RxState == HAL_UART_STATE_BUSY_RX)
  {
    m_rxError = true;
    (void)m_rxSemaphore.giveFromISR();
  }
}
//--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32::comm::FreeRTOS
