/*
 *
 *    Copyright (c) 2021 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.
 */

/**
 *    @file
 *      This file contains an implementation of TLVBackingStore using PacketBuffers.
 */

#pragma once

#include <lib/core/TLV.h>
#include <system/SystemPacketBuffer.h>

#include <utility>

namespace chip {
namespace System {

/**
 * An implementation of TLVBackingStore using PacketBuffers.
 */
class TLVPacketBufferBackingStore : public chip::TLV::TLVBackingStore
{
public:
    TLVPacketBufferBackingStore() : mHeadBuffer(nullptr), mCurrentBuffer(nullptr), mUseChainedBuffers(false) {}
    TLVPacketBufferBackingStore(chip::System::PacketBufferHandle && buffer, bool useChainedBuffers = false)
    {
        Init(std::move(buffer), useChainedBuffers);
    }
    ~TLVPacketBufferBackingStore() override {}

    /**
     * Take ownership of a backing packet buffer.
     *
     * @param[in]    buffer  A handle to a packet buffer, to be used as backing store for a TLV class.
     * @param[in]    useChainedBuffers
     *                       If true, advance to the next buffer in the chain once all data or space
     *                       in the current buffer has been consumed; a write will allocate new
     *                       packet buffers if necessary.
     *
     * @note This must take place before initializing a TLV class with this backing store.
     */
    void Init(chip::System::PacketBufferHandle && buffer, bool useChainedBuffers = false)
    {
        mHeadBuffer        = std::move(buffer);
        mCurrentBuffer     = mHeadBuffer.Retain();
        mUseChainedBuffers = useChainedBuffers;
    }
    void Adopt(chip::System::PacketBufferHandle && buffer) { Init(std::move(buffer), mUseChainedBuffers); }

    /**
     * Release ownership of the backing packet buffer.
     *
     * @note TLV operations must no longer be performed on this store.
     */
    CHECK_RETURN_VALUE chip::System::PacketBufferHandle Release()
    {
        mCurrentBuffer = nullptr;
        return std::move(mHeadBuffer);
    }

    // TLVBackingStore overrides:
    CHIP_ERROR OnInit(chip::TLV::TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen) override;
    CHIP_ERROR GetNextBuffer(chip::TLV::TLVReader & reader, const uint8_t *& bufStart, uint32_t & bufLen) override;
    CHIP_ERROR OnInit(chip::TLV::TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
    CHIP_ERROR GetNewBuffer(chip::TLV::TLVWriter & writer, uint8_t *& bufStart, uint32_t & bufLen) override;
    CHIP_ERROR FinalizeBuffer(chip::TLV::TLVWriter & writer, uint8_t * bufStart, uint32_t bufLen) override;
    virtual bool GetNewBufferWillAlwaysFail() override
    {
        // For non-chained buffers, caller is given one chunk of contiguous memory. All calls to
        // GetNewBuffer will fail with CHIP_ERROR_NO_MEMORY.
        return !mUseChainedBuffers;
    }

protected:
    chip::System::PacketBufferHandle mHeadBuffer;
    chip::System::PacketBufferHandle mCurrentBuffer;
    bool mUseChainedBuffers;
};

class DLL_EXPORT PacketBufferTLVReader : public TLV::ContiguousBufferTLVReader
{
public:
    /**
     * Initializes a TLVReader object to read from a PacketBuffer.
     *
     * @param[in]    buffer  A handle to PacketBuffer, to be used as backing
     *                       store for a TLV class.  If the buffer is chained,
     *                       only the head of the chain will be used.
     */
    void Init(chip::System::PacketBufferHandle && buffer)
    {
        mBuffer = std::move(buffer);
        TLV::ContiguousBufferTLVReader::Init(mBuffer->Start(), mBuffer->DataLength());
    }

private:
    PacketBufferHandle mBuffer;
};

class DLL_EXPORT PacketBufferTLVWriter : public chip::TLV::TLVWriter
{
public:
    /**
     * Initializes a TLVWriter object to write to a PacketBuffer.
     *
     * @param[in]    buffer  A handle to PacketBuffer, to be used as backing store for a TLV class.
     * @param[in]    useChainedBuffers
     *                       If true, advance to the next buffer in the chain once all space
     *                       in the current buffer has been consumed. Once all existing buffers
     *                       have been used, new PacketBuffers will be allocated as necessary.
     */
    void Init(chip::System::PacketBufferHandle && buffer, bool useChainedBuffers = false)
    {
        mBackingStore.Init(std::move(buffer), useChainedBuffers);
        chip::TLV::TLVWriter::Init(mBackingStore);
    }
    /**
     * Finish the writing of a TLV encoding and release ownership of the underlying PacketBuffer.
     *
     * @param[in,out] outBuffer     The backing packet buffer.
     *
     * @retval #CHIP_NO_ERROR       If the encoding was finalized successfully.
     * @retval #CHIP_ERROR_TLV_CONTAINER_OPEN
     *                              If a container writer has been opened on the current writer and not
     *                              yet closed.
     * @retval #CHIP_ERROR_INVALID_ARGUMENT
     *                              If the apparent data length does not fit in uint16_t.
     *
     * @note No further TLV operations may be performed, unless or until this PacketBufferTLVWriter is re-initialized.
     */
    CHIP_ERROR Finalize(chip::System::PacketBufferHandle * outBuffer)
    {
        CHIP_ERROR err = Finalize();
        *outBuffer     = mBackingStore.Release();
        return err;
    }
    /**
     * Free the underlying PacketBuffer.
     *
     * @note No further TLV operations may be performed, unless or until this PacketBufferTLVWriter is re-initialized.
     */
    void Reset() { static_cast<void>(mBackingStore.Release()); }

private:
    CHIP_ERROR Finalize() { return chip::TLV::TLVWriter::Finalize(); }
    TLVPacketBufferBackingStore mBackingStore;
};

} // namespace System
} // namespace chip
