//{{copyright}}

//{{version}}

//{{license}}

#pragma once

#include "../../comm/SerialBus.h"
#include "../criticalSection.h"
#include "../../dateTime/timer.h"

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

namespace sdi_uToolBox::stm32::comm
{
//--------------------------------------------------------------
class HIDBus : 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:
  HIDBus();     // Default constructor
  virtual ~HIDBus()                  = default;     // Default destructor
  HIDBus(const HIDBus &)            = delete;      // Copy constructor
  HIDBus(HIDBus &&)                 = delete;      // Move constructor
  HIDBus &operator=(const HIDBus &) = delete;      // Copy assignment operator
  HIDBus &operator=(HIDBus &&)      = delete;      // Move assignment operator

  virtual ReturnStatus configure(USBD_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

  virtual void rxCompleteCallback(std::span<const uint8_t> buf);

  protected:
  USBD_HandleTypeDef *m_hidHandle = nullptr;     // HAL UART handle
  sdi_uToolBox::dateTime::Timer m_timer;
  sdi_toolBox::dateTime::ITimer* m_genericTimer = nullptr;

  mutable std::span<const uint8_t> m_rxBuffer;
};
//--------------------------------------------------------------
inline HIDBus::HIDBus()
{
  m_genericTimer = &m_timer;
}
//--------------------------------------------------------------
/* Configure UART bus with HAL handle */
inline ReturnStatus HIDBus::configure(USBD_HandleTypeDef *handle)
{
  if (!handle)
    return ReturnStatus::InvalidHandle;

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

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

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

  if (ret == USBD_OK)
    return buf.size();
  if (ret == USBD_BUSY)
    return std::unexpected(ReturnStatus::Busy);

  return std::unexpected(ReturnStatus::Error);
}
//--------------------------------------------------------------
/* Read data from UART device */
inline HIDBus::ReturnType HIDBus::read(const ReadBuf buf, const Timeout timeout) const
{
  m_genericTimer->reset();
  while (!m_genericTimer->isElapsed(timeout))
  {
    CriticalSection criticalSection;
    if (m_rxBuffer.empty())
      continue;

    if (m_rxBuffer.size() > buf.size())
      return std::unexpected(ReturnStatus::BufferOverflow);

    std::copy_n(m_rxBuffer.begin(), m_rxBuffer.size(), buf.begin());
    m_rxBuffer = {};

    return m_rxBuffer.size();
  }

  return std::unexpected(ReturnStatus::Timeout);
}
//--------------------------------------------------------------
  inline void HIDBus::rxCompleteCallback(const std::span<const uint8_t> buf)
{
  CriticalSection criticalSection;
  m_rxBuffer = buf;
}
  //--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32::comm
