/*
 *
 *    Copyright (c) 2020-2023 Project CHIP Authors
 *    Copyright (c) 2013-2017 Nest Labs, Inc.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <stdint.h>

#include <lib/core/CHIPError.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVTags.h>
#include <lib/core/TLVTypes.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/DLLUtil.h>
#include <lib/support/Span.h>

/**
 * @namespace chip::TLV
 *
 * Definitions for working with data encoded in CHIP TLV format.
 *
 * CHIP TLV is a generalized encoding method for simple structured data. It shares many properties
 * with the commonly used JSON serialization format while being considerably more compact over the wire.
 */

namespace chip {
namespace TLV {

/**
 * Provides a unified Reader/Writer interface for editing/adding/deleting elements in TLV encoding.
 *
 * The TLVUpdater is a union of the TLVReader and TLVWriter objects and provides interface methods
 * for editing/deleting data in an encoding as well as adding new elements to the TLV encoding. The
 * TLVUpdater object essentially acts like two cursors, one for reading existing encoding and
 * another for writing (either for copying over existing data or writing new data).
 *
 * Semantically, the TLVUpdater object functions like a union of the TLVReader and TLVWriter. The
 * TLVUpdater methods have more or less similar meanings as similarly named counterparts in
 * TLVReader/TLVWriter. Where there are differences in the semantics, the differences are clearly
 * documented in the function's comment section in TLVUpdater.cpp.
 *
 * One particularly important note about the TLVUpdater's PutBytes() and PutString() methods is that
 * it can leave the encoding in a corrupt state with only the element header written when an
 * overflow occurs. Applications can call GetRemainingFreeLength() to make sure there is
 * @em approximately enough free space to write the encoding. Note that GetRemainingFreeLength()
 * only tells you the available free bytes and there is @em no way for the application to know the
 * length of encoded data that gets written. In the event of an overflow, both PutBytes() and
 * PutString() will return CHIP_ERROR_BUFFER_TOO_SMALL to the caller.
 *
 * Also, note that Next() method is overloaded to both skip the current element and also advance the
 * internal reader to the next element. Because skipping already encoded elements requires changing
 * the internal writer's free space state variables to account for the new freed space (made
 * available by skipping), the application is expected to call Next() on the updater after a Get()
 * method whose value it doesn't wish to write back (which is equivalent to skipping the current
 * element).
 *
 * @note The application is expected to use the TLVUpdater object atomically from the time it calls
 * Init() till it calls Finalize(). The same buffer should NOT be used with other TLVWriter objects.
 *
 * @note The TLVUpdater currently only supports single static buffers. TLVBackingStore is NOT supported.
 */
class DLL_EXPORT TLVUpdater
{
public:
    /**
     * Initialize a TLVUpdater object to edit a single input buffer.
     *
     * On calling this method, the TLV data in the buffer is moved to the end of the
     * buffer and a private TLVReader object is initialized on this relocated
     * buffer. A private TLVWriter object is also initialized on the free space that
     * is now available at the beginning. Applications can use the TLVUpdater object
     * to parse the TLV data and modify/delete existing elements or add new elements
     * to the encoding.
     *
     * @param[in]   buf     A pointer to a buffer containing the TLV data to be edited.
     * @param[in]   dataLen The length of the TLV data in the buffer.
     * @param[in]   maxLen  The total length of the buffer.
     *
     * @retval #CHIP_NO_ERROR                  If the method succeeded.
     * @retval #CHIP_ERROR_INVALID_ARGUMENT    If the buffer address is invalid.
     * @retval #CHIP_ERROR_BUFFER_TOO_SMALL    If the buffer is too small.
     *
     */
    CHIP_ERROR Init(uint8_t * buf, uint32_t dataLen, uint32_t maxLen);

    /**
     * Initialize a TLVUpdater object using a TLVReader.
     *
     * On calling this method, TLV data in the buffer pointed to by the TLVReader
     * is moved from the current read point to the end of the buffer. A new
     * private TLVReader object is initialized to read from this new location, while
     * a new private TLVWriter object is initialized to write to the freed up buffer
     * space.
     *
     * Note that if the TLVReader is already positioned "on" an element, it is first
     * backed-off to the start of that element. Also note that this backing off
     * works well with container elements, i.e., if the TLVReader was already  used
     * to call EnterContainer(), then there is nothing to back-off. But if the
     * TLVReader was positioned on the container element and EnterContainer() was
     * not yet called, then the TLVReader object is backed-off to the start of the
     * container head.
     *
     * The input TLVReader object will be destroyed before returning and the
     * application must not make use of the same on return.
     *
     * @param[in,out]   aReader     Reference to a TLVReader object that will be
     *                              destroyed before returning.
     * @param[in]       freeLen     The length of free space (in bytes) available
     *                              in the pre-encoded data buffer.
     *
     * @retval #CHIP_NO_ERROR                  If the method succeeded.
     * @retval #CHIP_ERROR_INVALID_ARGUMENT    If the buffer address is invalid.
     * @retval #CHIP_ERROR_NOT_IMPLEMENTED     If reader was initialized on a chain
     *                                          of buffers.
     */
    CHIP_ERROR Init(TLVReader & aReader, uint32_t freeLen);

    CHIP_ERROR Finalize() { return mUpdaterWriter.Finalize(); }

    // Common methods

    /**
     * Set the Implicit Profile ID for the TLVUpdater object.
     *
     * This method sets the implicit profile ID for the TLVUpdater object. When the
     * updater is asked to encode a new element, if the profile ID of the tag
     * associated with the new element matches the value of the @p profileId, the
     * updater will encode the tag in implicit form, thereby omitting the profile ID
     * in the process.
     *
     * @param[in]   profileId   The profile id of tags that should be encoded in
     *                          implicit form.
     */
    void SetImplicitProfileId(uint32_t profileId);
    uint32_t GetImplicitProfileId() const { return mUpdaterReader.ImplicitProfileId; }

    /**
     * Copies the current element from input TLV to output TLV.
     *
     * The Move() method copies the current element on which the TLVUpdater's reader
     * is positioned on, to the TLVUpdater's writer. The application should call
     * Next() and position the TLVUpdater's reader on an element before calling this
     * method. Just like the TLVReader::Next() method, if the reader is positioned
     * on a container element at the time of the call, all the members of the
     * container will be copied. If the reader is not positioned on any element,
     * nothing changes on calling this method.
     *
     * @retval #CHIP_NO_ERROR              If the TLVUpdater reader was
     *                                      successfully positioned on a new
     *                                      element.
     * @retval #CHIP_END_OF_TLV            If the TLVUpdater's reader is pointing
     *                                      to end of container.
     * @retval #CHIP_ERROR_INVALID_TLV_ELEMENT
     *                                      If the TLVIpdater's reader is not
     *                                      positioned on a valid TLV element.
     * @retval other                        Returns other error codes returned by
     *                                      TLVReader::Skip() method.
     *
     */
    CHIP_ERROR Move();

    /**
     * Move everything from the TLVUpdater's current read point till end of input
     * TLV buffer over to output.
     *
     * This method supports moving everything from the TLVUpdater's current read
     * point till the end of the reader buffer over to the TLVUpdater's writer.
     *
     * @note This method can be called with the TLVUpdater's reader positioned
     * anywhere within the input TLV. The reader can also be positioned under
     * multiple levels of nested containers and this method will still work.
     *
     * @note This method also changes the state of the TLVUpdater object to a state
     * it would be in if the application had painstakingly parsed each element from
     * the current read point till the end of the input encoding and copied them to
     * the output TLV.
     */
    void MoveUntilEnd();

    /**
     * Prepares a TLVUpdater object for reading elements of a container. It also
     * encodes a start of container object in the output TLV.
     *
     * The EnterContainer() method prepares the current TLVUpdater object to begin
     * reading the member elements of a TLV container (a structure, array or path).
     * For every call to EnterContainer() applications must make a corresponding
     * call to ExitContainer().
     *
     * When EnterContainer() is called the TLVUpdater's reader must be positioned on
     * the container element. The method takes as an argument a reference to a
     * TLVType value which will be used to save the context of the updater while it
     * is reading the container.
     *
     * When the EnterContainer() method returns, the updater is positioned
     * immediately @em before the first member of the container. Repeatedly calling
     * Next() will advance the updater through the members of the collection until
     * the end is reached, at which point the updater will return CHIP_END_OF_TLV.
     *
     * Once the application has finished reading a container it can continue reading
     * the elements after the container by calling the ExitContainer() method.
     *
     * @note This method implicitly encodes a start of container element in the
     * output TLV buffer.
     *
     * @param[out] outerContainerType       A reference to a TLVType value that will
     *                                      receive the context of the updater.
     *
     * @retval #CHIP_NO_ERROR              If the method succeeded.
     * @retval #CHIP_ERROR_INCORRECT_STATE If the TLVUpdater reader is not
     *                                      positioned on a container element.
     * @retval other                        Any other CHIP or platform error code
     *                                      returned by TLVWriter::StartContainer()
     *                                      or TLVReader::EnterContainer().
     *
     */
    CHIP_ERROR EnterContainer(TLVType & outerContainerType);

    /**
     * Completes the reading of a TLV container element and encodes an end of TLV
     * element in the output TLV.
     *
     * The ExitContainer() method restores the state of a TLVUpdater object after a
     * call to EnterContainer(). For every call to EnterContainer() applications
     * must make a corresponding call to ExitContainer(), passing the context value
     * returned by the EnterContainer() method.
     *
     * When ExitContainer() returns, the TLVUpdater reader is positioned immediately
     * before the first element that follows the container in the input TLV. From
     * this point applications can call Next() to advance through any remaining
     * elements.
     *
     * Once EnterContainer() has been called, applications can call ExitContainer()
     * on the updater at any point in time, regardless of whether all elements in
     * the underlying container have been read. Also, note that calling
     * ExitContainer() before reading all the elements in the container, will result
     * in the updated container getting truncated in the output TLV.
     *
     * @note Any changes made to the configuration of the updater between the calls
     * to EnterContainer() and ExitContainer() are NOT undone by the call to
     * ExitContainer(). For example, a change to the implicit profile id
     * (@p ImplicitProfileId) will not be reversed when a container is exited. Thus
     * it is the application's responsibility to adjust the configuration
     * accordingly at the appropriate times.
     *
     * @param[in] outerContainerType        The TLVType value that was returned by
     *                                      the EnterContainer() method.
     *
     * @retval #CHIP_NO_ERROR              If the method succeeded.
     * @retval #CHIP_ERROR_TLV_UNDERRUN    If the underlying TLV encoding ended
     *                                      prematurely.
     * @retval #CHIP_ERROR_INVALID_TLV_ELEMENT
     *                                      If the updater encountered an invalid or
     *                                      unsupported TLV element type.
     * @retval #CHIP_ERROR_INVALID_TLV_TAG If the updater encountered a TLV tag in
     *                                      an invalid context.
     * @retval other                        Any other CHIP or platform error code
     *                                      returned by TLVWriter::EndContainer() or
     *                                      TLVReader::ExitContainer().
     *
     */
    CHIP_ERROR ExitContainer(TLVType outerContainerType);

    void GetReader(TLVReader & containerReader) { containerReader = mUpdaterReader; }

    // Reader methods

    /**
     * Skip the current element and advance the TLVUpdater object to the next
     * element in the input TLV.
     *
     * The Next() method skips the current element in the input TLV and advances the
     * TLVUpdater's reader to the next element that resides in the same containment
     * context. In particular, if the reader is positioned at the outer most level
     * of a TLV encoding, calling Next() will advance it to the next, top most
     * element. If the reader is positioned within a TLV container element (a
     * structure, array or path), calling Next() will advance it to the next member
     * element of the container.
     *
     * Since Next() constrains reader motion to the current containment context,
     * calling Next() when the reader is positioned on a container element will
     * advance @em over the container, skipping its member elements (and the members
     * of any nested containers) until it reaches the first element after the
     * container.
     *
     * When there are no further elements within a particular containment context
     * the Next() method will return a #CHIP_END_OF_TLV error and the position of
     * the reader will remain unchanged.
     *
     * @note The Next() method implicitly skips the current element. Hence, the
     * TLVUpdater's private writer state variables will be adjusted to account for
     * the new freed space (made available by skipping). This means that the
     * application is expected to call Next() on the TLVUpdater object after a Get()
     * whose value the application does @em not write back (which from the
     * TLVUpdater's view is equivalent to skipping that element).
     *
     * @note Applications are also expected to call Next() when they are at the end
     * of a container, and want to add new elements there. This is particularly
     * important in situations where there is a fixed schema. Applications that have
     * fixed schemas and know where the container end is cannot just add new
     * elements at the end, because the TLVUpdater writer's state will not reflect
     * the correct free space available for the Put() operation. Hence, applications
     * must call Next() (and possibly also test for CHIP_END_OF_TLV) before adding
     * elements at the end of a container.
     *
     * @retval #CHIP_NO_ERROR              If the TLVUpdater reader was
     *                                      successfully positioned on a new
     *                                      element.
     * @retval other                        Returns the CHIP or platform error
     *                                      codes returned by the TLVReader::Skip()
     *                                      and TLVReader::Next() method.
     *
     */
    CHIP_ERROR Next();

    CHIP_ERROR Get(bool & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(int8_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(int16_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(int32_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(int64_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(uint8_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(uint16_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(uint32_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(uint64_t & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(float & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(double & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(ByteSpan & v) { return mUpdaterReader.Get(v); }
    CHIP_ERROR Get(CharSpan & v) { return mUpdaterReader.Get(v); }

    CHIP_ERROR GetBytes(uint8_t * buf, uint32_t bufSize) { return mUpdaterReader.GetBytes(buf, bufSize); }
    CHIP_ERROR DupBytes(uint8_t *& buf, uint32_t & dataLen) { return mUpdaterReader.DupBytes(buf, dataLen); }
    CHIP_ERROR GetString(char * buf, uint32_t bufSize) { return mUpdaterReader.GetString(buf, bufSize); }
    CHIP_ERROR DupString(char *& buf) { return mUpdaterReader.DupString(buf); }

    TLVType GetType() const { return mUpdaterReader.GetType(); }
    Tag GetTag() const { return mUpdaterReader.GetTag(); }
    uint32_t GetLength() const { return mUpdaterReader.GetLength(); }
    CHIP_ERROR GetDataPtr(const uint8_t *& data) { return mUpdaterReader.GetDataPtr(data); }
    CHIP_ERROR VerifyEndOfContainer() { return mUpdaterReader.VerifyEndOfContainer(); }
    TLVType GetContainerType() const { return mUpdaterReader.GetContainerType(); }
    uint32_t GetLengthRead() const { return mUpdaterReader.GetLengthRead(); }
    uint32_t GetRemainingLength() const { return mUpdaterReader.GetRemainingLength(); }

    // Writer methods
    CHIP_ERROR Put(Tag tag, int8_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, int16_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, int32_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, int64_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, uint8_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, uint16_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, uint32_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, uint64_t v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, int8_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, int16_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, int32_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, int64_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, uint8_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, uint16_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, uint32_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, uint64_t v, bool preserveSize) { return mUpdaterWriter.Put(tag, v, preserveSize); }
    CHIP_ERROR Put(Tag tag, float v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR Put(Tag tag, double v) { return mUpdaterWriter.Put(tag, v); }
    CHIP_ERROR PutBoolean(Tag tag, bool v) { return mUpdaterWriter.PutBoolean(tag, v); }
    CHIP_ERROR PutNull(Tag tag) { return mUpdaterWriter.PutNull(tag); }
    CHIP_ERROR PutBytes(Tag tag, const uint8_t * buf, uint32_t len) { return mUpdaterWriter.PutBytes(tag, buf, len); }
    CHIP_ERROR PutString(Tag tag, const char * buf) { return mUpdaterWriter.PutString(tag, buf); }
    CHIP_ERROR PutString(Tag tag, const char * buf, uint32_t len) { return mUpdaterWriter.PutString(tag, buf, len); }
    CHIP_ERROR CopyElement(TLVReader & reader) { return mUpdaterWriter.CopyElement(reader); }
    CHIP_ERROR CopyElement(Tag tag, TLVReader & reader) { return mUpdaterWriter.CopyElement(tag, reader); }
    CHIP_ERROR StartContainer(Tag tag, TLVType containerType, TLVType & outerContainerType)
    {
        return mUpdaterWriter.StartContainer(tag, containerType, outerContainerType);
    }
    CHIP_ERROR EndContainer(TLVType outerContainerType) { return mUpdaterWriter.EndContainer(outerContainerType); }
    uint32_t GetLengthWritten() { return mUpdaterWriter.GetLengthWritten(); }
    uint32_t GetRemainingFreeLength() const { return mUpdaterWriter.mRemainingLen; }

private:
    void AdjustInternalWriterFreeSpace();

    TLVWriter mUpdaterWriter;
    TLVReader mUpdaterReader;
    const uint8_t * mElementStartAddr;
};

} // namespace TLV
} // namespace chip
