/*
 *
 *    Copyright (c) 2020-2021 Project CHIP Authors
 *    All rights reserved.
 *
 *    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 defines structures and utility methods for working with BDX
 *      messages, mainly for writing to and reading from PacketBuffers.
 */

#pragma once

#include <lib/support/BitFlags.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/CodeUtils.h>
#include <protocols/Protocols.h>
#include <protocols/bdx/StatusCode.h>
#include <system/SystemPacketBuffer.h>

namespace chip {
namespace bdx {

inline constexpr uint16_t kMaxFileDesignatorLen = 0xFF;

inline constexpr char kProtocolName[] = "BDX";

enum class MessageType : uint8_t
{
    SendInit           = 0x01,
    SendAccept         = 0x02,
    ReceiveInit        = 0x04,
    ReceiveAccept      = 0x05,
    BlockQuery         = 0x10,
    Block              = 0x11,
    BlockEOF           = 0x12,
    BlockAck           = 0x13,
    BlockAckEOF        = 0x14,
    BlockQueryWithSkip = 0x15,
};

enum class TransferControlFlags : uint8_t
{
    // first 4 bits reserved for version
    kSenderDrive   = (1U << 4),
    kReceiverDrive = (1U << 5),
    kAsync         = (1U << 6),
};

enum class RangeControlFlags : uint8_t
{
    kDefLen      = (1U),
    kStartOffset = (1U << 1),
    kWiderange   = (1U << 4),
};

/**
 * @brief
 *   Interface for defining methods that apply to all BDX messages.
 */
struct BdxMessage
{
    /**
     * @brief
     *  Parse data from an PacketBuffer into a BdxMessage struct.
     *
     *  Note that this may store pointers that point into the passed PacketBuffer,
     *  so it is essential that the underlying PacketBuffer is not modified until after this
     *  struct is no longer needed.
     *
     * @param[in] aBuffer A PacketBufferHandle with a reference to the PacketBuffer containing the data.
     *
     * @return CHIP_ERROR Return an error if the message format is invalid and/or can't be parsed
     */
    CHECK_RETURN_VALUE
    virtual CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) = 0;

    /**
     * @brief
     *  Write the message fields to a buffer using the provided BufferWriter.
     *
     *  It is up to the caller to use BufferWriter::Fit() to verify that the write was
     *  successful. This method will also not check for correctness or completeness for
     *  any of the fields - it is the caller's responsibility to ensure that the fields
     *  align with BDX specifications.
     *
     * @param aBuffer A BufferWriter object that will be used to write the message
     */
    virtual Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const = 0;

    /**
     * @brief
     *  Returns the size of buffer needed to write the message.
     */
    virtual size_t MessageSize() const = 0;

#if CHIP_AUTOMATION_LOGGING
    /**
     * @brief
     * Log all parameters for this message.
     */
    virtual void LogMessage(bdx::MessageType messageType) const = 0;
#endif // CHIP_AUTOMATION_LOGGING

    virtual ~BdxMessage() = default;
};

/*
 * A structure for representing a SendInit or ReceiveInit message (both contain
 * identical parameters).
 */
struct TransferInit : public BdxMessage
{
    bool operator==(const TransferInit &) const;

    // Proposed Transfer Control (required)
    BitFlags<TransferControlFlags> TransferCtlOptions;
    uint8_t Version = 0; ///< The highest version supported by the sender

    // Range Control
    BitFlags<RangeControlFlags> mRangeCtlFlags;

    // All required
    uint16_t MaxBlockSize = 0; ///< Proposed max block size to use in transfer
    uint64_t StartOffset  = 0; ///< Proposed start offset of data. 0 for no offset
    uint64_t MaxLength    = 0; ///< Proposed max length of data in transfer, 0 for indefinite

    // File designator (required) and additional metadata (optional, TLV format)
    // WARNING: there is no guarantee at any point that these pointers will point to valid memory. The Buffer field should be used
    // to hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
    const uint8_t * FileDesignator = nullptr;
    uint16_t FileDesLength         = 0; ///< Length of file designator string (not including null-terminator)
    const uint8_t * Metadata       = nullptr;
    size_t MetadataLength          = 0;

    // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
    System::PacketBufferHandle Buffer;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

using SendInit    = TransferInit;
using ReceiveInit = TransferInit;

/*
 * A structure for representing a SendAccept message.
 */
struct SendAccept : public BdxMessage
{
    bool operator==(const SendAccept &) const;

    // Transfer Control (required, only one should be set)
    BitFlags<TransferControlFlags> TransferCtlFlags;

    uint8_t Version       = 0; ///< The agreed upon version for the transfer (required)
    uint16_t MaxBlockSize = 0; ///< Chosen max block size to use in transfer (required)

    // Additional metadata (optional, TLV format)
    // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
    // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
    const uint8_t * Metadata = nullptr;
    size_t MetadataLength    = 0;

    // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
    System::PacketBufferHandle Buffer;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

/**
 * A structure for representing ReceiveAccept messages.
 */
struct ReceiveAccept : public BdxMessage
{
    bool operator==(const ReceiveAccept &) const;

    // Transfer Control (required, only one should be set)
    BitFlags<TransferControlFlags> TransferCtlFlags;

    // Range Control
    BitFlags<RangeControlFlags> mRangeCtlFlags;

    // All required
    uint8_t Version       = 0; ///< The agreed upon version for the transfer
    uint16_t MaxBlockSize = 0; ///< Chosen max block size to use in transfer
    uint64_t StartOffset  = 0; ///< Chosen start offset of data. 0 for no offset.
    uint64_t Length       = 0; ///< Length of transfer. 0 if length is indefinite.

    // Additional metadata (optional, TLV format)
    // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
    // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
    const uint8_t * Metadata = nullptr;
    size_t MetadataLength    = 0;

    // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
    System::PacketBufferHandle Buffer;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

/**
 * A struct for representing messages contiaining just a counter field. Can be used to
 * represent BlockQuery, BlockAck, and BlockAckEOF.
 */
struct CounterMessage : public BdxMessage
{
    bool operator==(const CounterMessage &) const;

    uint32_t BlockCounter = 0;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

using BlockQuery  = CounterMessage;
using BlockAck    = CounterMessage;
using BlockAckEOF = CounterMessage;

/**
 * A struct that represents a message containing actual data (Block, BlockEOF).
 */
struct DataBlock : public BdxMessage
{
    bool operator==(const DataBlock &) const;

    uint32_t BlockCounter = 0;

    // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
    // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
    const uint8_t * Data = nullptr;
    size_t DataLength    = 0;

    // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
    System::PacketBufferHandle Buffer;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

using Block    = DataBlock;
using BlockEOF = DataBlock;

struct BlockQueryWithSkip : public BdxMessage
{
    bool operator==(const BlockQueryWithSkip &) const;

    uint32_t BlockCounter = 0;
    uint64_t BytesToSkip  = 0;

    CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) override;
    Encoding::LittleEndian::BufferWriter & WriteToBuffer(Encoding::LittleEndian::BufferWriter & aBuffer) const override;
    size_t MessageSize() const override;
#if CHIP_AUTOMATION_LOGGING
    void LogMessage(bdx::MessageType messageType) const override;
#endif // CHIP_AUTOMATION_LOGGING
};

} // namespace bdx

namespace Protocols {
template <>
struct MessageTypeTraits<bdx::MessageType>
{
    static constexpr const Protocols::Id & ProtocolId() { return BDX::Id; }

    static auto GetTypeToNameTable()
    {
        static const std::array<MessageTypeNameLookup, 10> typeToNameTable = {
            {
                { bdx::MessageType::SendInit, "SendInit" },
                { bdx::MessageType::SendAccept, "SendAccept" },
                { bdx::MessageType::ReceiveInit, "ReceiveInit" },
                { bdx::MessageType::ReceiveAccept, "ReceiveAccept" },
                { bdx::MessageType::BlockQuery, "BlockQuery" },
                { bdx::MessageType::Block, "Block" },
                { bdx::MessageType::BlockEOF, "BlockEOF" },
                { bdx::MessageType::BlockAck, "BlockAck" },
                { bdx::MessageType::BlockAckEOF, "BlockAckEOF" },
                { bdx::MessageType::BlockQueryWithSkip, "BlockQueryWithSkip" },
            },
        };

        return &typeToNameTable;
    }
};
} // namespace Protocols

} // namespace chip
