/*
{{copyright}} 
*/

/*
{{version}} 
*/

/*
{{license}} 
*/

#pragma once

#include <cstdint>
#include <span>

namespace sdi_ToolBox::generic
{
//--------------------------------------------------------------
/**
 * @class CRC16_modbus
 * @brief CRC16 calculator for the Modbus protocol.
 *
 * Provides both static and incremental interfaces to compute the CRC16 checksum
 * as used in the Modbus protocol (polynomial 0xA001, initial value 0xFFFF).
 *
 * Usage examples:
 * @code
 * // One-shot CRC calculation
 * std::array<uint8_t, 8> data = { ... };
 * uint16_t crc = Crc16_modbus::computeCRC(data);
 *
 * // Incremental CRC calculation
 * Crc16_modbus crcHandle;
 * crcHandle.init();
 * for (uint8_t b : data) {
 *     crcHandle.update(b);
 * }
 * uint16_t crc = crcHandle.finalize();
 * @endcode
 */
class CRC16_modbus
{
  static constexpr uint16_t INITIAL_VALUE = 0xFFFF;

  public:
  /**
   * @brief Computes the CRC16 of the given data in one call.
   * @param data The data buffer to compute the CRC for.
   * @return The computed CRC16 value.
   */
  static uint16_t computeCRC(const std::span<const uint8_t> &data);

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

  /**
   * @brief Constructs and initializes the CRC with the given data.
   * @param data The data buffer to initialize and update the CRC with.
   */
  explicit CRC16_modbus(const std::span<const uint8_t> &data);

  /**
   * @brief Initializes or re-initializes the CRC to the default value (0xFFFF).
   */
  void init();

  /**
   * @brief Updates the CRC with a single byte.
   * @param val The byte value to update the CRC with.
   */
  void update(const uint8_t &val);

  /**
   * @brief Updates the CRC with a data buffer.
   * @param data The data buffer to update the CRC with.
   */
  void update(const std::span<const uint8_t> &data);

  /**
   * @brief Finalizes the CRC calculation and returns the CRC value.
   * @return The finalized CRC16 value.
   */
  uint16_t finalize() const;

  /**
   * @brief Returns the current CRC value.
   * @return The current CRC16 value.
   */
  [[nodiscard]] uint16_t getCRC() const;

  protected:
  uint16_t m_crc = INITIAL_VALUE;     // Current CRC value
};
//--------------------------------------------------------------

//--------------------------------------------------------------
/* Compute CRC in one go */
inline uint16_t CRC16_modbus::computeCRC(const std::span<const uint8_t> &data)
{
  const CRC16_modbus crc(data);
  return crc.finalize();
}
//--------------------------------------------------------------
/* Constructor */
inline CRC16_modbus::CRC16_modbus(const std::span<const uint8_t> &data)
{
  init();
  update(data);
}
//--------------------------------------------------------------
/* Reinit crc handle */
inline void CRC16_modbus::init()
{
  m_crc = INITIAL_VALUE;
}
//--------------------------------------------------------------
/* Update CRC */
inline void CRC16_modbus::update(const uint8_t &val)
{
  constexpr uint16_t PolynomialValue = 0xA001;

  m_crc ^= static_cast<uint16_t>(val);     // XOR byte into least sig. byte of crc
  for (int i = 8; i != 0; i--)             // Loop over each bit
  {
    if ((m_crc & 0x0001) != 0)     // If the LSB is set
    {
      m_crc >>= 1;     // Shift right and XOR PolynomialValue
      m_crc ^= PolynomialValue;
    }
    else     // Else LSB is not set
    {
      m_crc >>= 1;     // Just shift right
    }
  }
}
//--------------------------------------------------------------
/* Update CRC */
inline void CRC16_modbus::update(const std::span<const uint8_t> &data)
{
  for (const auto &c : data)
    update(c);
}
//--------------------------------------------------------------
/* Finalize and return CRC value */
inline uint16_t CRC16_modbus::finalize() const
{
  return getCRC();
}
//--------------------------------------------------------------
/* Return CRC value */
inline uint16_t CRC16_modbus::getCRC() const
{
  return m_crc;
}
//--------------------------------------------------------------
}     // namespace sdi_ToolBox::generic
