/*
 *    Copyright (c) 2017, The OpenThread Authors.
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without
 *    modification, are permitted provided that the following conditions are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the copyright holder nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 *    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *    DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
 *    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *    LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file contains the definitions of a spinel encoder.
 */

#ifndef OT_LIB_SPINEL_SPINEL_ENCODER_HPP_
#define OT_LIB_SPINEL_SPINEL_ENCODER_HPP_

#include "openthread-spinel-config.h"

#include <openthread/ip6.h>
#include <openthread/message.h>
#include <openthread/ncp.h>
#include <openthread/platform/radio.h>

#include "spinel.h"
#include "spinel_buffer.hpp"

namespace ot {
namespace Spinel {

/**
 * Defines a spinel encoder.
 */
class Encoder
{
public:
    /**
     * Initializes a `Encoder` object.
     *
     * @param[in] aNcpBuffer   A reference to a `Spinel::Buffer` where the frames are written.
     */
    explicit Encoder(Spinel::Buffer &aNcpBuffer)
        : mNcpBuffer(aNcpBuffer)
        , mNumOpenStructs(0)
        , mSavedNumOpenStructs(0)
    {
    }

    /**
     * Begins a new frame to be added/written to the frame buffer.
     *
     * If there is a previous frame being written (for which `EndFrame()` has not yet been called), calling
     * `BeginFrame()` will discard and clear the previous unfinished frame.
     *
     * @param[in] aPriority             Priority level of the new input frame.
     *
     * @retval OT_ERROR_NONE            Successfully started a new frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to start a new frame.
     */
    otError BeginFrame(Spinel::Buffer::Priority aPriority);

    /**
     * Begins a new spinel command frame to be added/written to the frame buffer.
     *
     * If there is a previous frame being written (for which `EndFrame()` has not yet been called), calling
     * `BeginFrame()` will discard and clear the previous unfinished frame.
     *
     * The spinel transaction ID (TID) in the given spinel header is used to determine the priority level of the new
     * frame. Non-zero TID value indicates that the frame is a response and therefore it uses higher priority level.
     *
     * @param[in] aHeader               Spinel header for new the command frame.
     * @param[in] aCommand              Spinel command.
     *
     * @retval OT_ERROR_NONE            Successfully started a new frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to start a new frame.
     */
    otError BeginFrame(uint8_t aHeader, unsigned int aCommand);

    /**
     * Begins a new spinel property update command frame to be added/written to the frame buffer.
     *
     * If there is a previous frame being written (for which `EndFrame()` has not yet been called), calling
     * `BeginFrame()` will discard and clear the previous unfinished frame.
     *
     * The spinel transaction ID (TID) in the given spinel header is used to determine the priority level of the new
     * frame. Non-zero TID value indicates that the frame is a response and therefore it uses higher priority level.
     *
     * Saves the write position before the property key (see also `SavePosition()`) so that if fetching the
     * property fails and the property key should be switched to `LAST_STATUS` with an error status, the saved
     * position can be used to update the property key in the frame (see also `OverwriteWithLastStatusError()`)
     *
     * @param[in] aHeader               Spinel header for new the command frame.
     * @param[in] aCommand              Spinel command.
     * @param[in] aKey                  Spinel property key
     *
     * @retval OT_ERROR_NONE            Successfully started a new frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to start a new frame.
     */
    otError BeginFrame(uint8_t aHeader, unsigned int aCommand, spinel_prop_key_t aKey);

    /**
     * Overwrites the property key with `LAST_STATUS` in a property update command frame.
     *
     * Should be only used after a successful `BeginFrame(aHeader, aCommand, aPropertyKey)`, otherwise, its
     * behavior is undefined.
     *
     * Moves the write position back to saved position by `BeginFrame()` and replaces the property key
     * `SPINEL_PROP_LAST_STATUS` and writes the given spinel status error.
     *
     * @param[in] aStatus               Spinel error status
     *
     * @retval OT_ERROR_NONE            Successfully updated the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to update the frame.
     */
    otError OverwriteWithLastStatusError(spinel_status_t aStatus);

    /**
     * Finalizes/ends the current frame being written to the buffer.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new frame. Otherwise, this method
     * does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the frame and return error status
     * `OT_ERROR_NO_BUFS`.
     *
     * Ensures to close any open structure (previously opened using `OpenStruct()` but not closed using
     * `CloseStruct()`).
     *
     * @retval OT_ERROR_NONE            Successfully ended the input frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add message.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError EndFrame(void);

    /**
     * Encodes and writes a boolean value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aBool                The boolean value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given byte to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteBool(bool aBool) { return mNcpBuffer.InFrameFeedByte(aBool ? 0x01 : 0x00); }

    /**
     * Encodes and writes a `uint8_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUint8               The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUint8(uint8_t aUint8) { return mNcpBuffer.InFrameFeedByte(aUint8); }

    /**
     * Encodes and writes an `int8_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aInt8                The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteInt8(int8_t aInt8) { return WriteUint8(static_cast<uint8_t>(aInt8)); }

    /**
     * Encodes and writes a `uint16_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUint16              The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUint16(uint16_t aUint16);

    /**
     * Encodes and writes an `int16_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aInt16              The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteInt16(int16_t aInt16) { return WriteUint16(static_cast<uint16_t>(aInt16)); }

    /**
     * Encodes and writes a `uint32_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUint32              The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUint32(uint32_t aUint32);

    /**
     * Encodes and writes an `int32_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aInt32               The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteInt32(int32_t aInt32) { return WriteUint32(static_cast<uint32_t>(aInt32)); }

    /**
     * Encodes and writes a `uint64_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUint64              The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUint64(uint64_t aUint64);

    /**
     * Encodes and writes an `int64_t` value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aInt64               The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteInt64(int64_t aInt64) { return WriteUint64(static_cast<uint64_t>(aInt64)); }

    /**
     * Encodes (using spinel packed integer format) and writes a value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUint                The value to add to input frame.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUintPacked(unsigned int aUint);

    /**
     * Encodes and writes an IPv6 address to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aIp6Addr             A reference to the IPv6 address to be added (as `spinel_ipv6addr_t`)
     *
     * @retval OT_ERROR_NONE            Successfully added given address to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the IP address.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteIp6Address(const spinel_ipv6addr_t &aIp6Addr) { return WriteIp6Address(aIp6Addr.bytes); }

    /**
     * Encodes and writes an IPv6 address to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aIp6Addr             A reference to the IPv6 address to be added (as `otIp6Address`)
     *
     * @retval OT_ERROR_NONE            Successfully added given address to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the IP address.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteIp6Address(const otIp6Address &aIp6Addr) { return WriteIp6Address(aIp6Addr.mFields.m8); }

    /**
     * Encodes and writes an IPv6 address to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aIp6AddrBuf          A pointer to a buffer containing the IPv6 address.
     *
     * @retval OT_ERROR_NONE            Successfully added given address to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the IP address.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteIp6Address(const uint8_t *aIp6AddrBuf) { return WriteData(aIp6AddrBuf, sizeof(spinel_ipv6addr_t)); }

    /**
     * Encodes and writes an EUI64 value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aEui64               A reference to the EUI64 value as a `spinel_eui64_t` type.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the EUI64 value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteEui64(const spinel_eui64_t &aEui64) { return WriteEui64(aEui64.bytes); }

    /**
     * Encodes and writes an EUI64 value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aExtAddress          A reference to an `otExtAddress`
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the EUI64 value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteEui64(const otExtAddress &aExtAddress) { return WriteEui64(aExtAddress.m8); }

    /**
     * Encodes and writes an EUI64 value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aEui64               A pointer to a buffer containing the EUI64 value.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the EUI64 value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteEui64(const uint8_t *aEui64) { return WriteData(aEui64, sizeof(spinel_eui64_t)); }

    /**
     * Encodes and writes an EUI48 value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aEui48               A reference to the EUI48 value.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the EUI48 value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteEui48(const spinel_eui48_t &aEui48) { return WriteEui48(aEui48.bytes); }

    /**
     * Encodes and writes an EUI48 value to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aEui48               A pointer to a buffer containing the EUI64 value.
     *
     * @retval OT_ERROR_NONE            Successfully added given value to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the EUI48 value.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteEui48(const uint8_t *aEui48) { return WriteData(aEui48, sizeof(spinel_eui48_t)); }

    /**
     * Encodes and writes a UTF8 string to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aUtf8                A const character pointer (C string).
     *
     * @retval OT_ERROR_NONE            Successfully added given string to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the string.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteUtf8(const char *aUtf8);

    /**
     * Encodes and writes a sequence of bytes to current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aData                A pointer to data buffer.
     * @param[in]  aDataLen             The length (number of bytes) in the data buffer.
     *
     * @retval OT_ERROR_NONE            Successfully added given data to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the byte.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteData(const uint8_t *aData, uint16_t aDataLen) { return mNcpBuffer.InFrameFeedData(aData, aDataLen); }

    /**
     * Encodes and writes a data blob (sequence of bytes) with its length prepended before the data.
     *
     * The length of the data (in bytes) is prepended (with the length encoded as a `uint16`). The size of the length
     * field is not included in the length. This is similar to `SPINEL_DATATYPE_DATA_WLEN` type.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * @param[in]  aData                A pointer to data buffer.
     * @param[in]  aDataLen             The length (number of bytes) in the data buffer.
     *
     * @retval OT_ERROR_NONE            Successfully added given data to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the byte.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteDataWithLen(const uint8_t *aData, uint16_t aDataLen);

#if OPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE
    /**
     * Adds a message to the current input frame.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the frame and return error status
     * `OT_ERROR_NO_BUFS`.
     *
     * The ownership of the passed-in message @p aMessage changes to underlying `Spinel::Buffer` ONLY when the entire
     * frame is successfully finished (i.e., with a successful call to `EndFrame()` for the current frame being
     * written), and in this case the `otMessage` instance will be freed once the frame is removed from the
     * `Spinel::Buffer`. However, if the frame gets discarded before it is finished (e.g., running out of buffer space),
     * the  `otMessage` instance remains unchanged.
     *
     * @param[in] aMessage              A message to be added to current frame.
     *
     * @retval OT_ERROR_NONE            Successfully added the message to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the message.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     * @retval OT_ERROR_INVALID_ARGS    If @p aMessage is nullptr.
     */
    otError WriteMessage(otMessage *aMessage) { return mNcpBuffer.InFrameFeedMessage(aMessage); }
#endif

    /**
     * Encodes and writes a set of variables to the current input frame using a given spinel packing format
     * string.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * Note that the encoded buffer should fit in `kPackFormatBufferSize` bytes.
     *
     * @param[in]  aPackFormat          A string giving the spinel packing format.
     * @param[in]  ...                  Variable arguments corresponding to the types given in @p aPackFormat (see
     *                                  `spinel_datatype_pack()`).
     *
     * @retval OT_ERROR_NONE            Successfully added given data to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the byte.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WritePacked(const char *aPackFormat, ...);

    /**
     * Encodes and writes a set of variables to the current input frame using a given spinel packing format
     * string.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the current input frame and return the
     * error status `OT_ERROR_NO_BUFS`.
     *
     * Note that the encoded buffer should fit in `kPackFormatBufferSize` bytes.
     *
     * @param[in]  aPackFormat          A string giving the spinel packing format.
     * @param[in]  aArgs                Variable arguments corresponding to the types given in @p aPackFormat (see
     *                                  `spinel_datatype_pack()`).
     *
     * @retval OT_ERROR_NONE            Successfully added given data to the frame.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to add the byte.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError WriteVPacked(const char *aPackFormat, va_list aArgs);

    /**
     * Opens a struct in the current input frame.
     *
     * After a successful call to this method, all the subsequent `Write<SomeType>()` methods add the field/value to
     * the current open struct until the struct is closed using `CloseStruct()` method. Structures can be nested. Up to
     * `kMaxNestedStructs` nested structs can be opened at the same time.
     *
     * Before using this method `BeginFrame()` must be called to start and prepare a new input frame. Otherwise, this
     * method does nothing and returns error status `OT_ERROR_INVALID_STATE`.
     *
     * If no buffer space is available, this method will discard and clear the frame and return error status
     * `OT_ERROR_NO_BUFS`.
     *
     * @retval OT_ERROR_NONE            Successfully opened the struct.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to open the struct.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame or if we reached
     *                                  the maximum number of nested open structures.
     */
    otError OpenStruct(void);

    /**
     * Closes the most recently opened struct (using `OpenStruct()`) in the current input frame.
     *
     * Each call to `CloseStruct()` must correspond to an earlier successfully opened struct. If a frame is ended using
     * `EndFrame()` with remaining open structs, the `EndFrame()` method will close all the remaining structs.
     *
     * If no buffer space is available, this method will discard and clear the frame and return error status
     * `OT_ERROR_NO_BUFS`.
     *
     * @retval OT_ERROR_NONE            Successfully closed the most recently opened struct.
     * @retval OT_ERROR_NO_BUFS         Insufficient buffer space available to open the struct.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame or if there is no
     *                                  open struct to close
     */
    otError CloseStruct(void);

    /**
     * Saves the current write position in the input frame.
     *
     * The saved position can later be used to discard a portion of written/encoded frame and move the write pointer
     * back to the saved position (using `ResetToSaved()`).
     *
     * @retval OT_ERROR_NONE            Successfully saved current write position in @p aPosition.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     */
    otError SavePosition(void);

    /**
     * Resets the write position of input frame back to a previously saved position. Any added content
     * after the write position is discarded.
     *
     * The saved position must belong to the same input frame saved earlier with `SavePosition()`. This method cannot
     * be used if the input frame has an added `otMessage`.
     *
     * @retval OT_ERROR_NONE            Successfully reset the write position of current input frame.
     * @retval OT_ERROR_INVALID_STATE   `BeginFrame()` has not been called earlier to start the frame.
     * @retval OT_ERROR_INVALID_ARGS    The saved position is not valid (does not belong to same input frame), or
     *                                  the input frame has an added `otMessage`.
     */
    otError ResetToSaved(void);

    /**
     * Clear NCP buffer on reset command.
     */
    void ClearNcpBuffer(void);

private:
    static constexpr uint16_t kPackFormatBufferSize = 96; // Used when encoding using `WritePacked()`.
    static constexpr uint8_t  kMaxNestedStructs     = 4;  // Maximum number of nested structs.

    Spinel::Buffer               &mNcpBuffer;
    Spinel::Buffer::WritePosition mStructPosition[kMaxNestedStructs];
    uint8_t                       mNumOpenStructs;

    uint8_t                       mSavedNumOpenStructs;
    Spinel::Buffer::WritePosition mSavedPosition;
};

} // namespace Spinel
} // namespace ot

#endif // OT_LIB_SPINEL_SPINEL_ENCODER_HPP_
