/*
{{copyright}} 
*/

/*
{{version}} 
*/

/*
{{license}} 
*/

#pragma once

#include "../../comm/I2CBus.h"

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

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

  ReturnStatus configure(I2C_HandleTypeDef *handle);     // Configure I2C bus with HAL handle

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

  ReturnType   write(uint8_t address, WriteBuf buf, Timeout timeout) const override;     // Write data to I2C device
  ReturnType   read(uint8_t address, ReadBuf buf, Timeout timeout) const override;       // Read data from I2C device
  ReturnStatus isDeviceReady(uint8_t address) const override;                            // Check if I2C device is ready

  protected:
  I2C_HandleTypeDef *m_i2cHandle = nullptr;     // HAL I2C handle
};
//--------------------------------------------------------------

//--------------------------------------------------------------
/* Configure I2C bus with HAL handle */
inline ReturnStatus I2CBus::configure(I2C_HandleTypeDef *handle)
{
  if (!handle)
    return ReturnStatus::InvalidHandle;

  m_i2cHandle = handle;
  return ReturnStatus::Ok;
}
//--------------------------------------------------------------
/* Open I2C bus */
inline ReturnStatus I2CBus::open()
{
  return ReturnStatus::Error;
}
//--------------------------------------------------------------
/* Close I2C bus */
inline ReturnStatus I2CBus::close()
{
  return ReturnStatus::Error;
}
//--------------------------------------------------------------
/* Write data to I2C device */
inline I2CBus::ReturnType I2CBus::write(const uint8_t address, const WriteBuf buf, const Timeout timeout) const
{
  const auto ret = HAL_I2C_Master_Transmit(m_i2cHandle,
                                           address << 1,
                                           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_ERROR && m_i2cHandle->ErrorCode == HAL_I2C_ERROR_TIMEOUT)
    return std::unexpected(ReturnStatus::Timeout);

  return std::unexpected(ReturnStatus::Error);
}
//--------------------------------------------------------------
/* Read data from I2C device */
inline I2CBus::ReturnType I2CBus::read(const uint8_t address, const ReadBuf buf, const Timeout timeout) const
{
  const auto ret = HAL_I2C_Master_Receive(m_i2cHandle,
                                          address << 1,
                                          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_ERROR && m_i2cHandle->ErrorCode == HAL_I2C_ERROR_TIMEOUT)
    return std::unexpected(ReturnStatus::Timeout);

  return std::unexpected(ReturnStatus::Error);
}
//--------------------------------------------------------------
/* Check if I2C device is ready */
inline ReturnStatus I2CBus::isDeviceReady(const uint8_t address) const
{
  const auto ret = HAL_I2C_IsDeviceReady(m_i2cHandle,
                                         address << 1,
                                         5,
                                         100);

  if (ret == HAL_OK)
    return ReturnStatus::Ok;
  if (ret == HAL_BUSY)
    return ReturnStatus::Busy;
  if (ret == HAL_ERROR && m_i2cHandle->ErrorCode == HAL_I2C_ERROR_TIMEOUT)
    return ReturnStatus::Timeout;

  return ReturnStatus::Error;
}
//--------------------------------------------------------------
}     // namespace sdi_uToolBox::stm32::comm
