//{{copyright}}

//{{version}}

//{{license}}

#pragma once

#include "../generic/crc.h"

#include <array>
#include <cstring>
#include <span>
#include <string>

/**
 * @namespace sdi_ToolBox::comm::uBinaryFrame
 * @brief Utilities for robust binary frame serialization, deserialization, and protocol handling.
 *
 * This namespace provides classes and constants to manage binary communication frames with automatic
 * escaping, CRC calculation, and protocol validation. The supported frame format is:
 *   [SOF1][SOF2][SIZE][PAYLOAD][CRC16][EOF]
 * where special bytes are escaped as needed.
 *
 * Main components:
 * - @ref FrameReceiver : Receives and extracts a complete binary frame from a byte stream.
 * - @ref MessageParser : Parses and validates a binary frame, providing typed access to the payload.
 * - @ref MessageBuilder : Builds a binary message with CRC calculation and escaping.
 * - @ref FramePacker : Packs a message into a complete frame ready for transmission (header, CRC, escaping, etc.).
 */
namespace sdi_ToolBox::comm::uBinaryFrame
{
constexpr uint8_t SOF1_ = 0xAA;     // Start of Frame byte (0b10101010 - useful for bus synchronisation)
constexpr uint8_t SOF2_ = 0x55;     // Start of Frame byte (0b01010101 - useful for bus synchronisation)
constexpr uint8_t EOF_  = 0x03;     // End of Frame byte   (End of Text code in ASCII table)
constexpr uint8_t ESC_  = 0x1B;     // Escape byte         (Escape code in ASCII table)

//--------------------------------------------------------------
/**
 * @class FrameReceiver
 * @brief Stateful receiver for extracting a complete binary frame from a byte stream.
 *
 * Handles frame synchronization, byte un-escaping, and buffering.
 * The frame is considered complete when the protocol's end-of-frame byte is detected.
 *
 * @tparam BUFFER_SIZE Maximum size of the internal receive buffer.
 *
 * Usage:
 *  - Call push(byte) for each received byte.
 *  - When push() returns true or isFrameAvailable() is true, use getFrame() to access the frame data.
 *
 * @code
 * FrameReceiver<128> receiver;
 * for (uint8_t byte : incomingBytes) {
 *     if (receiver.push(byte)) {
 *         auto frame = receiver.getFrame();
 *         // Process frame...
 *
 *         receiver.reset();
 *     }
 * }
 * @endcode
 */
template<size_t BUFFER_SIZE>
class FrameReceiver
{
  enum class State : uint8_t
  {
    WAIT_SOF1,
    WAIT_SOF2,
    RECEIVE_DATA,
    WAIT_EOF,
    ESCAPE_NEXT,
    END,
  };

  public:
  FrameReceiver()                                        = default;     // Default constructor
  ~FrameReceiver()                                       = default;     // Default destructor
  FrameReceiver(const FrameReceiver &obj)                = default;     // Copy constructor
  FrameReceiver(FrameReceiver &&obj) noexcept            = default;     // Move constructor
  FrameReceiver &operator=(const FrameReceiver &obj)     = default;     // Copy assignment operator
  FrameReceiver &operator=(FrameReceiver &&obj) noexcept = default;     // Move assignment operator

  /**
   * @brief Resets the receiver state and clears the internal buffer.
   *
   * After calling this method, the receiver is ready to process a new frame.
   */
  void reset();

  /**
   * @brief Pushes a byte into the receiver buffer and updates the state machine.
   * @param byte The received byte to process.
   * @return true if a complete frame has been received and is available, false otherwise.
   *
   * @note If a frame is available, use getFrame() to access its content.
   */
  [[nodiscard]] bool push(uint8_t byte);

  /**
   * @brief Checks if a complete frame is available in the buffer.
   * @return true if a frame is available, false otherwise.
   */
  [[nodiscard]] bool isFrameAvailable() const;

  /**
   * @brief Returns a span to the complete frame data.
   * @return A span containing the frame bytes, or an empty span if no frame is available.
   *
   * @note The returned span is only valid until the next reset() or push().
   */
  [[nodiscard]] std::span<const uint8_t> getFrame() const;

  protected:
  std::array<uint8_t, BUFFER_SIZE> m_buffer{};                     // Receiver buffer
  size_t                           m_index = 0;                    // Current index in the buffer
  State                            m_state = State::WAIT_SOF1;     // Current state of the receiver

  private:
  void appendByte(uint8_t byte);     // Append a byte to the buffer
};
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FrameReceiver<BUFFER_SIZE>::reset()
{
  m_index = 0;
  m_state = State::WAIT_SOF1;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FrameReceiver<BUFFER_SIZE>::push(const uint8_t byte)
{
  switch (m_state)
  {
    // Initial state: waiting for SOF1
    case State::WAIT_SOF1:
    {
      if (byte == SOF1_)
      {
        m_index = 0;
        m_state = State::WAIT_SOF2;     // Next byte should be SOF2
      }
      break;
    }

    // Waiting for SOF2
    case State::WAIT_SOF2:
    {
      if (byte == SOF2_)
        m_state = State::RECEIVE_DATA;     // Start receiving data
      else
        m_state = State::WAIT_SOF1;     // Abort frame reception
      break;
    }

    // Receiving data
    case State::RECEIVE_DATA:
    {
      if (byte == ESC_)
        m_state = State::ESCAPE_NEXT;     // Next byte is escaped
      else if (byte == SOF1_)
        m_state = State::WAIT_SOF2;     // Restart frame reception
      else if (byte == SOF2_)
        m_state = State::WAIT_SOF1;     // Abort frame reception
      else if (byte == EOF_)
      {
        m_state = State::END;     // Frame complete
        return true;
      }
      else
        appendByte(byte);
      break;
    }

    // Handling escaped byte
    case State::ESCAPE_NEXT:
    {
      appendByte(byte);
      m_state = State::RECEIVE_DATA;     // Return to data reception
      break;
    }

    // Frame complete
    case State::END:
    default:
      return true;
  }

  return false;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FrameReceiver<BUFFER_SIZE>::isFrameAvailable() const
{
  return m_state == State::END;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> FrameReceiver<BUFFER_SIZE>::getFrame() const
{
  if (m_state == State::END)
    return { m_buffer.data(), m_index };
  return {};
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FrameReceiver<BUFFER_SIZE>::appendByte(const uint8_t byte)
{
  if (m_index < BUFFER_SIZE)
    m_buffer[m_index++] = byte;
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
/**
 * @class MessageParser
 * @brief Parses and validates a binary frame, providing typed access to the payload.
 *
 * Checks frame structure, size, CRC, and end-of-frame marker.
 * Allows extraction of typed values or raw data from the payload.
 *
 * Usage:
 *  - Construct with a frame buffer.
 *  - Check validity with isValid() or getStatus().
 *  - Use get<T>(offset, value) to extract typed data from the payload.
 *
 * @code
 * MessageParser parser(frame);
 * if (parser.isValid()) {
 *     uint16_t id;
 *     parser.get(0, id);
 *     std::array<uint8_t, 8> data;
 *     parser.get(2, std::span<uint8_t>(data));
 *     // Use id and data...
 * }
 * @endcode
 */
class MessageParser
{
  public:
  enum class MessageStatus : uint8_t
  {
    VALID,
    INVALID,
    BAD_SIZE,
    BAD_CRC,
  };

  public:
  MessageParser()                                        = delete;      // Default constructor
  ~MessageParser()                                       = default;     // Default destructor
  MessageParser(const MessageParser &obj)                = default;     // Copy constructor
  MessageParser(MessageParser &&obj) noexcept            = default;     // Move constructor
  MessageParser &operator=(const MessageParser &obj)     = default;     // Copy assignment operator
  MessageParser &operator=(MessageParser &&obj) noexcept = default;     // Move assignment operator

  /**
   * @brief Constructs a MessageParser and checks the integrity of the provided frame.
   * @param buffer The buffer containing the frame to parse.
   */
  explicit MessageParser(std::span<const uint8_t> buffer);

  /**
   * @brief Checks if the parsed message is valid.
   * @return true if the message is valid, false otherwise.
   */
  [[nodiscard]] bool isValid() const;

  /**
   * @brief Gets the current message validity status.
   * @return The status as a MessageStatus enum value.
   */
  [[nodiscard]] MessageStatus getStatus() const;

  /**
   * @brief Gets the message payload buffer.
   * @return A span containing the payload bytes.
   */
  [[nodiscard]] std::span<const uint8_t> getBuffer() const;

  /**
   * @brief Gets the size of the message payload.
   * @return The size of the payload in bytes.
   */
  [[nodiscard]] size_t size() const;     // Get message payload size

  /**
   * @brief Extracts a value of type T from the message payload at the specified offset.
   * @tparam T The type of the value to extract (POD type or std::span<uint8_t>).
   * @param offset The offset in the payload to read from.
   * @param value Reference to store the extracted value.
   * @return The new offset after reading the value, or the original offset if reading failed.
   *
   * @note No exception is thrown. If extraction fails, value is not modified.
   */
  template<class T>
  [[nodiscard]] size_t get(size_t offset, T &value) const;

  protected:
  std::span<const uint8_t> m_buffer;                                     // Message buffer
  MessageStatus            m_messageStatus = MessageStatus::INVALID;     // Message validity status

  private:
  void messageCheck();     // Check message integrity
};
//--------------------------------------------------------------
/* Constructor */
inline MessageParser::MessageParser(const std::span<const uint8_t> buffer)
{
  m_buffer = buffer;
  messageCheck();
}
//--------------------------------------------------------------
inline bool MessageParser::isValid() const
{
  return m_messageStatus == MessageStatus::VALID;
}
//--------------------------------------------------------------
/* Get message validity status */
inline MessageParser::MessageStatus MessageParser::getStatus() const
{
  return m_messageStatus;
}
//--------------------------------------------------------------
/* Get message payload buffer */
inline std::span<const uint8_t> MessageParser::getBuffer() const
{
  return m_buffer;
}
//--------------------------------------------------------------
/* Get message payload size */
inline size_t MessageParser::size() const
{
  return m_buffer.size();
}
//--------------------------------------------------------------
template<class T>
inline size_t MessageParser::get(size_t offset, T &value) const
{
  static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) ||     // POD type
                std::is_same_v<T, std::span<uint8_t>> ||                      // std::span<uint8_t> type
                std::is_same_v<T, std::string>);                              // std::string type

  if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
  {
    // POD type
    constexpr size_t typeSize = sizeof(T);
    if (offset + typeSize <= m_buffer.size())
    {
      std::memcpy(&value, m_buffer.data() + offset, typeSize);
      return offset + typeSize;     // Return new offset
    }
  }
  else if constexpr (std::is_same_v<T, std::span<uint8_t>>)
  {
    // std::span<uint8_t> type
    if (offset + value.size() <= m_buffer.size())
    {
      std::memcpy(value.data(), m_buffer.data() + offset, value.size());
      return offset + value.size();     // Return new offset
    }
  }
  else if constexpr (std::is_same_v<T, std::string>)
  {
    // std::string type
    uint8_t strSize;
    offset = get(offset, strSize);

    value.resize(strSize);
    std::span<uint8_t> strSpan(reinterpret_cast<uint8_t *>(value.data()), strSize);
    offset = get(offset, strSpan);
  }

  return offset;     // Unable to read value, return original offset
}
//--------------------------------------------------------------
/* Check message integrity */
inline void MessageParser::messageCheck()
{
  /* Message format : [SOF1(1)][SOF2(1)][SIZE(1)][PAYLOAD(n)][CRC16(2)][EOF(1)] */
  // Check SIZE
  const auto size = m_buffer[0];
  if (constexpr size_t CRCSize = 2; m_buffer.size() != CRCSize + size + sizeof(size))
  {
    m_messageStatus = MessageStatus::BAD_SIZE;
    return;
  }

  // Check CRC16
  const auto     loCRC       = *(m_buffer.rbegin() + 1);
  const auto     hiCRC       = *m_buffer.rbegin();
  const uint16_t frameCRC    = static_cast<uint16_t>(hiCRC << 8) | static_cast<uint16_t>(loCRC);
  const uint16_t computedCRC = generic::CRC16_modbus::computeCRC(m_buffer.subspan(1, m_buffer.size() - 3));
  if (frameCRC != computedCRC)
  {
    m_messageStatus = MessageStatus::BAD_CRC;
    return;
  }

  m_buffer        = m_buffer.subspan(1, size);
  m_messageStatus = MessageStatus::VALID;
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
/**
 * @class MessageBuilderHelper
 * @brief Utility class to simplify the usage of MessageBuilder templates.
 *
 * This helper provides convenient methods and type aliases to work with MessageBuilder
 * instances without exposing template parameters in function signatures.
 */
class MessageBuilderHelper
{
  public:
  MessageBuilderHelper()                                               = default;     // Default constructor
  virtual ~MessageBuilderHelper()                                      = default;     // Default destructor
  MessageBuilderHelper(const MessageBuilderHelper &obj)                = default;     // Copy constructor
  MessageBuilderHelper(MessageBuilderHelper &&obj) noexcept            = default;     // Move constructor
  MessageBuilderHelper &operator=(const MessageBuilderHelper &obj)     = default;     // Copy assignment operator
  MessageBuilderHelper &operator=(MessageBuilderHelper &&obj) noexcept = default;     // Move assignment operator

  /**
   * @brief Gets the current message buffer as a span.
   * @return A span containing the current message bytes.
   */
  [[nodiscard]] virtual std::span<const uint8_t> getBuffer() const = 0;

  /**
   * @brief Checks if the message buffer is full.
   * @return true if the buffer is full, false otherwise.
   */
  [[nodiscard]] virtual bool isFull() const = 0;

  /**
   * @brief Gets the current size of the message buffer.
   * @return The number of bytes currently in the message buffer.
   */
  [[nodiscard]] virtual size_t getBufferSize() const = 0;

  /**
   * @brief Gets the current CRC16 value of the message.
   * @return The current CRC16 value.
   */
  [[nodiscard]] virtual uint16_t getCRC() const = 0;
};
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
/**
 * @class MessageBuilder
 * @brief Builds a binary message payload with CRC calculation and escaping support.
 *
 * Supports appending POD types or byte spans, and computes CRC incrementally.
 * Provides methods to reset, append, and retrieve the current buffer and CRC.
 *
 * @tparam BUFFER_SIZE Maximum size of the internal message buffer.
 *
 * Usage:
 *  - Use append() or operator<< to add data.
 *  - Retrieve the buffer with getBuffer() and CRC with getCRC().
 *
 * @code
 * MessageBuilder<64> builder;
 * builder << uint16_t(0x1234) << std::span<const uint8_t>(data, dataLen);
 * auto buffer = builder.getBuffer();
 * auto crc = builder.getCRC();
 * @endcode
 */
template<size_t BUFFER_SIZE>
class MessageBuilder : public MessageBuilderHelper
{
  public:
  MessageBuilder();                                                       // Default constructor
  virtual ~MessageBuilder()                                = default;     // Default destructor
  MessageBuilder(const MessageBuilder &obj)                = default;     // Copy constructor
  MessageBuilder(MessageBuilder &&obj) noexcept            = default;     // Move constructor
  MessageBuilder &operator=(const MessageBuilder &obj)     = default;     // Copy assignment operator
  MessageBuilder &operator=(MessageBuilder &&obj) noexcept = default;     // Move assignment operator

  /**
   * @brief Resets the message builder to an empty state.
   * @return Reference to the MessageBuilder for chaining.
   */
  MessageBuilder &reset();     // Reset the message builder

  /**
   * @brief Appends a value of type T to the message buffer.
   * @tparam T The type of the value to append (POD type or std::span<uint8_t>).
   * @param value The value to append.
   * @return Reference to the MessageBuilder for chaining.
   *
   * @note If appending the value would exceed the buffer size, it is ignored.
   */
  template<class T>
  MessageBuilder &append(const T &value);     // Append value of type T to the message

  /**
   * @brief Overloads the << operator to append a value of type T to the message buffer.
   * @tparam T The type of the value to append (POD type or std::span<uint8_t>).
   * @param value The value to append.
   * @return Reference to the MessageBuilder for chaining.
   *
   * @note If appending the value would exceed the buffer size, it is ignored.
   */
  template<class T>
  MessageBuilder &operator<<(const T &value);

  [[nodiscard]] std::span<const uint8_t> getBuffer() const override;
  [[nodiscard]] bool                     isFull() const override;
  [[nodiscard]] size_t                   getBufferSize() const override;
  [[nodiscard]] uint16_t                 getCRC() const override;

  protected:
  std::array<uint8_t, BUFFER_SIZE> m_buffer{};      // Receiver buffer
  size_t                           m_index = 0;     // Current index in the buffer

  generic::CRC16_modbus m_CRC;     // Current CRC value
};

//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline MessageBuilder<BUFFER_SIZE>::MessageBuilder()
{
  m_CRC.init();
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::reset()
{
  m_index = 0;
  m_CRC.init();
  return *this;     // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
template<class T>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::append(const T &value)
{
  static_assert((std::is_standard_layout_v<T> && std::is_trivial_v<T>) ||     // POD type
                std::is_same_v<T, std::span<uint8_t>> ||                      // std::span<uint8_t> type
                std::is_same_v<T, std::span<const uint8_t>> ||                // std::span<const uint8_t> type
                std::is_same_v<T, std::string> ||                             // std::string type
                std::is_same_v<T, const std::string>);                        // const std::string type

  if constexpr (std::is_standard_layout_v<T> && std::is_trivial_v<T>)
  {
    // POD type
    constexpr size_t typeSize = sizeof(T);
    if (m_index + typeSize <= BUFFER_SIZE)
    {
      std::array<uint8_t, typeSize> dataBuffer;
      std::memcpy(dataBuffer.data(), &value, typeSize);

      std::memcpy(m_buffer.data() + m_index, dataBuffer.data(), dataBuffer.size());
      m_CRC.update(dataBuffer);
      m_index += typeSize;
    }
  }
  else if constexpr (std::is_same_v<T, std::span<uint8_t>> ||
                     std::is_same_v<T, std::span<const uint8_t>>)
  {
    // std::span<uint8_t> type
    if (m_index + value.size() <= BUFFER_SIZE)
    {
      std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
      m_CRC.update(value);
      m_index += value.size();
    }
  }
  else if constexpr (std::is_same_v<T, std::string> || std::is_same_v<T, const std::string>)
  {
    // std::string type
    if (m_index + (value.size() + sizeof(uint8_t)) <= BUFFER_SIZE)
    {
      append(static_cast<uint8_t>(value.size()));

      std::memcpy(m_buffer.data() + m_index, value.data(), value.size());
      m_CRC.update(std::span(reinterpret_cast<const uint8_t *>(value.data()), value.size()));
      m_index += value.size();
    }
  }

  return *this;     // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
template<class T>
inline MessageBuilder<BUFFER_SIZE> &MessageBuilder<BUFFER_SIZE>::operator<<(const T &value)
{
  append(value);
  return *this;     // Return reference to self for chaining
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> MessageBuilder<BUFFER_SIZE>::getBuffer() const
{
  return { m_buffer.data(), m_index };
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool MessageBuilder<BUFFER_SIZE>::isFull() const
{
  return m_index >= BUFFER_SIZE;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline size_t MessageBuilder<BUFFER_SIZE>::getBufferSize() const
{
  return m_index;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline uint16_t MessageBuilder<BUFFER_SIZE>::getCRC() const
{
  return m_CRC.getCRC();
}
//--------------------------------------------------------------

/* --- */

//--------------------------------------------------------------
/**
 * @class FramePacker
 * @brief Packs a message payload into a complete binary frame ready for transmission.
 *
 * Adds protocol headers, escapes special bytes, appends CRC and end-of-frame marker.
 * Ensures the packed frame fits in the provided buffer size.
 *
 * @tparam BUFFER_SIZE Maximum size of the output frame buffer.
 *
 * Usage:
 *  - Call packMessage(payload, crc) to build the frame.
 *  - Use isValid() to check for overflow.
 *  - Retrieve the packed frame with getPackedFrame().
 *
 * @code
 * FramePacker<256> packer;
 * if (packer.packMessage(payload, crc)) {
 *     auto frame = packer.getPackedFrame();
 *     // Send frame...
 * }
 * @endcode
 */
template<size_t BUFFER_SIZE>
class FramePacker
{
  static constexpr size_t CRC_SIZE = 4;     // CRC16(2 x2) --> (CRC is 4 bytes to support escaped CRC)

  public:
  FramePacker() = default;                                                  // Default constructor
  explicit FramePacker(std::span<const uint8_t> message, uint16_t crc);     // Constructor
  ~FramePacker()                                     = default;             // Default destructor
  FramePacker(const FramePacker &obj)                = default;             // Copy constructor
  FramePacker(FramePacker &&obj) noexcept            = default;             // Move constructor
  FramePacker &operator=(const FramePacker &obj)     = default;             // Copy assignment operator
  FramePacker &operator=(FramePacker &&obj) noexcept = default;             // Move assignment operator

  /**
   * @brief Packs the input message into a complete frame with headers, CRC, and escaping.
   * @param message The message payload to pack.
   * @param crc The CRC16 value to append to the frame.
   * @return true if packing was successful without overflow, false otherwise.
   */
  bool packMessage(std::span<const uint8_t> message, uint16_t crc);

  /**
   * @brief Checks if the packed frame is valid (no overflow occurred).
   * @return true if the packed frame is valid, false otherwise.
   */
  [[nodiscard]] bool isValid() const;

  /**
   * @brief Gets the packed frame buffer as a span.
   * @return A span containing the packed frame bytes.
   */
  [[nodiscard]] std::span<const uint8_t> getPackedFrame() const;

  protected:
  std::array<uint8_t, BUFFER_SIZE> m_frame{};       // Output buffer
  size_t                           m_index = 0;     // Current index in output buffer

  std::array<uint8_t, CRC_SIZE> m_CRCBuffer{};     // CRC buffer with escaping
  size_t                        m_CRCSize = 0;     // Current size of CRC buffer with escaping

  bool m_overflow = false;     // Flag indicating if overflow occurred during packing

  private:
  void appendByte(uint8_t byte);        // Append byte with escaping to output buffer
  void appendRawByte(uint8_t byte);     // Append raw byte to output buffer

  void packCRC(uint16_t crc);     // Pack CRC into CRC buffer with escaping
};
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline FramePacker<BUFFER_SIZE>::FramePacker(const std::span<const uint8_t> message, const uint16_t crc)
{
  packMessage(message, crc);
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FramePacker<BUFFER_SIZE>::packMessage(const std::span<const uint8_t> message, const uint16_t crc)
{
  // Reset state
  m_index    = 0;
  m_overflow = false;

  // Prepare CRC buffer with escaping
  packCRC(crc);

  // Append header
  appendRawByte(SOF1_);                                 // Append SOF1
  appendRawByte(SOF2_);                                 // Append SOF2
  appendByte(static_cast<uint8_t>(message.size()));     // Append payload size

  // Append payload
  for (const auto &byte : message)
    appendByte(byte);

  // Append CRC
  for (size_t i = 0; i < m_CRCSize; i++)
    appendRawByte(m_CRCBuffer[i]);

  // Append EOF
  appendRawByte(EOF_);

  return !m_overflow;     // Return true if packing was successful
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline bool FramePacker<BUFFER_SIZE>::isValid() const
{
  return !m_overflow;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline std::span<const uint8_t> FramePacker<BUFFER_SIZE>::getPackedFrame() const
{
  return std::span<const uint8_t>(m_frame.data(), m_index);
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::appendByte(const uint8_t byte)
{
  if (byte == SOF1_ || byte == SOF2_ || byte == EOF_ || byte == ESC_)
  {
    appendRawByte(ESC_);     // Append escape byte
  }
  appendRawByte(byte);     // Append actual byte
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::appendRawByte(const uint8_t byte)
{
  if (m_index < BUFFER_SIZE)
    m_frame[m_index++] = byte;
  else
    m_overflow = true;
}
//--------------------------------------------------------------
template<size_t BUFFER_SIZE>
inline void FramePacker<BUFFER_SIZE>::packCRC(const uint16_t crc)
{
  m_CRCSize           = 0;
  const uint8_t loCRC = static_cast<uint8_t>(crc & 0x00FF);
  const uint8_t hiCRC = static_cast<uint8_t>((crc >> 8) & 0x00FF);

  if (loCRC == SOF1_ || loCRC == SOF2_ || loCRC == EOF_ || loCRC == ESC_)
    m_CRCBuffer[m_CRCSize++] = ESC_;
  m_CRCBuffer[m_CRCSize++] = loCRC;

  if (hiCRC == SOF1_ || hiCRC == SOF2_ || hiCRC == EOF_ || hiCRC == ESC_)
    m_CRCBuffer[m_CRCSize++] = ESC_;
  m_CRCBuffer[m_CRCSize++] = hiCRC;
}
//--------------------------------------------------------------
}     // namespace sdi_ToolBox::comm::uBinaryFrame
