//{{copyright}}

//{{version}}

//{{license}}

#pragma once

#include "../../comm/SerialBus.h"

#include <STM32Headers.h>
#include <chrono>
#include <expected>
#include <span>

namespace sdi_uToolBox::stm32::comm
{
//--------------------------------------------------------------
class UARTBus : public sdi_uToolBox::comm::SerialBus
{
  public:
  using ReadBuf    = std::span<uint8_t>;
  using WriteBuf   = std::span<const uint8_t>;
  using Timeout    = std::chrono::milliseconds;
  using ReturnType = std::expected<size_t, ReturnStatus>;

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

  virtual ReturnStatus configure(UART_HandleTypeDef *handle);     // 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

  protected:
  UART_HandleTypeDef *m_uartHandle = nullptr;     // HAL UART handle
};
//--------------------------------------------------------------
/* Configure UART bus with HAL handle */
inline ReturnStatus UARTBus::configure(UART_HandleTypeDef *handle)
{
  if (!handle)
    return ReturnStatus::InvalidHandle;

  m_uartHandle = handle;
  return ReturnStatus::Ok;
}
//--------------------------------------------------------------
/* Open UART bus */
inline ReturnStatus UARTBus::open()
{
  if (!m_uartHandle)
    return ReturnStatus::InvalidHandle;

  return ReturnStatus::Error;
}
//--------------------------------------------------------------
/* Close UART bus */
inline ReturnStatus UARTBus::close()
{
  if (!m_uartHandle)
    return ReturnStatus::InvalidHandle;

  // Abort ongoing operations
  HAL_UART_AbortTransmit(m_uartHandle);
  HAL_UART_AbortReceive(m_uartHandle);

  return ReturnStatus::Error;
}
//--------------------------------------------------------------
/* Write data to UART device */
inline UARTBus::ReturnType UARTBus::write(const WriteBuf buf, const Timeout timeout) const
{
  const auto ret = HAL_UART_Transmit(m_uartHandle,
                                     const_cast<uint8_t *>(buf.data()),
                                     buf.size(),
                                     timeout.count());

  if (ret == HAL_OK)
    return buf.size();
  if (ret == HAL_BUSY)
    return std::unexpected(ReturnStatus::Busy);
  if (ret == HAL_TIMEOUT)
    return std::unexpected(ReturnStatus::Timeout);

  return std::unexpected(ReturnStatus::Error);
}
//--------------------------------------------------------------
/* Read data from UART device */
inline UARTBus::ReturnType UARTBus::read(const ReadBuf buf, const Timeout timeout) const
{
  const auto ret = HAL_UART_Receive(m_uartHandle,
                                    buf.data(),
                                    buf.size(),
                                    timeout.count());

  if (ret == HAL_OK)
    return buf.size();
  if (ret == HAL_BUSY)
    return std::unexpected(ReturnStatus::Busy);
  if (ret == HAL_TIMEOUT)
    return std::unexpected(ReturnStatus::Timeout);

  return std::unexpected(ReturnStatus::Error);
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
class UARTBusCallback
{
  friend class UARTBusCallbackRegistry;

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

  virtual void txCompleteCallback() const           = 0;
  virtual void rxCompleteCallback() const           = 0;
  virtual void rxEventCallback(uint16_t size) const = 0;
  virtual void errorCallback() const                = 0;

  protected:
  struct RegistryEntry
  {
    const UART_HandleTypeDef *handle   = nullptr;
    const UARTBusCallback    *instance = nullptr;
  };
};
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
class UARTBusCallbackRegistry
{
  static constexpr size_t MAX_UART_INSTANCES = 4;

  public:
  static bool                   registerInstance(const UART_HandleTypeDef *handle, const UARTBusCallback *instance);     // Register instance with UART handle
  static void                   unregisterInstance(const UARTBusCallback *instance);                                     // Unregister instance with UART handle
  static const UARTBusCallback *getInstance(const UART_HandleTypeDef *handle);                                           // Get instance from UART handle

  protected:
  static inline std::array<UARTBusCallback::RegistryEntry, MAX_UART_INSTANCES> s_registry;
};
//--------------------------------------------------------------
/* Register instance with UART handle */
inline bool UARTBusCallbackRegistry::registerInstance(const UART_HandleTypeDef *handle, const UARTBusCallback *instance)
{
  for (auto &entry : s_registry)
  {
    if (!entry.handle)
    {
      entry.handle   = handle;
      entry.instance = instance;
      return true;     // Successfully registered
    }
  }

  return false;     // Registry full
}
//--------------------------------------------------------------
/* Unregister instance with UART handle */
inline void UARTBusCallbackRegistry::unregisterInstance(const UARTBusCallback *instance)
{
  for (auto &entry : s_registry)
  {
    if (entry.instance == instance)
    {
      entry.handle   = nullptr;
      entry.instance = nullptr;
    }
  }
}
//--------------------------------------------------------------
/* Get instance from UART handle */
inline const UARTBusCallback *UARTBusCallbackRegistry::getInstance(const UART_HandleTypeDef *handle)
{
  for (const auto &entry : s_registry)
  {
    if (entry.handle == handle)
      return entry.instance;
  }
  return nullptr;
}
//--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32::comm
