/*
 *    Copyright (c) 2024 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.
 */
#pragma once

#include <app/util/attribute-metadata.h>
#include <app/util/ember-io-storage.h>
#include <lib/core/TLVReader.h>
#include <lib/core/TLVTypes.h>
#include <lib/core/TLVWriter.h>
#include <lib/support/BufferReader.h>
#include <lib/support/BufferWriter.h>
#include <lib/support/Span.h>

namespace chip {
namespace app {
namespace Ember {

/// This class represents a pointer to an ember-encoded attribute in a specific memory location.
///
/// Ember attributes are stored as raw bytes for numeric types (i.e. memcpy-like storage except
/// unaligned) and strings are Pascal-like (short with 1-byte length prefix or long with 2-byte length
/// prefix).
///
/// Class is to be used as a one-shot:
///   - create it out of metadata + data span
///   - call Decode (which modifies the input data span)
class EmberAttributeDataBuffer
{
public:
#if CHIP_CONFIG_BIG_ENDIAN_TARGET
    using EndianWriter = Encoding::BigEndian::BufferWriter;
    using EndianReader = Encoding::BigEndian::Reader;
#else
    using EndianWriter = Encoding::LittleEndian::BufferWriter;
    using EndianReader = Encoding::LittleEndian::Reader;
#endif

    enum class PascalStringType
    {
        kShort,
        kLong,
    };

    static constexpr bool kIsFabricScoped = false;

    EmberAttributeDataBuffer(const EmberAfAttributeMetadata * meta, MutableByteSpan & data) :
        mIsNullable(meta->IsNullable()), mAttributeType(chip::app::Compatibility::Internal::AttributeBaseType(meta->attributeType)),
        mDataBuffer(data)
    {}

    /// Reads the data pointed into by `reader` and updates the data
    /// internally into mDataBuffer (which is then reflected outwards)
    ///
    /// Generally should be called ONLY ONCE as the internal mutable byte span gets
    /// modified by this call.
    CHIP_ERROR Decode(chip::TLV::TLVReader & reader);

    /// Writes the data encoded in the underlying buffer into the given `writer`
    ///
    /// The data in the internal data buffer is assumed to be already formatted correctly
    /// HOWEVER the size inside it will not be fully considered (i.e. encoding will use
    /// the data encoding line integer or string sizes and NOT the databuffer max size)
    CHIP_ERROR Encode(chip::TLV::TLVWriter & writer, TLV::Tag tag) const;

private:
    /// Decodes the UNSIGNED integer stored in `reader` and places its content into `writer`
    /// Takes into account internal mIsNullable.
    CHIP_ERROR DecodeUnsignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer);

    /// Decodes the SIGNED integer stored in `reader` and places its content into `writer`
    /// Takes into account internal mIsNullable.
    CHIP_ERROR DecodeSignedInteger(chip::TLV::TLVReader & reader, EndianWriter & writer);

    /// Encodes the given integer into `writer`.
    /// Takes into account internal mIsNullable.
    CHIP_ERROR EncodeInteger(chip::TLV::TLVWriter & writer, TLV::Tag tag, EndianReader & reader) const;

    /// Decodes the string/byte string contained in `reader` and stores it into `writer`.
    /// String is encoded using a pascal-prefix of size `stringType`.
    /// Takes into account internal mIsNullable.
    ///
    /// The string in `reader` is expected to be of type `tlvType`
    CHIP_ERROR DecodeAsString(chip::TLV::TLVReader & reader, PascalStringType stringType, TLV::TLVType tlvType,
                              EndianWriter & writer);

    const bool mIsNullable;                    // Contains if the attribute metadata marks the field as NULLABLE
    const EmberAfAttributeType mAttributeType; // Initialized with the attribute type from the metadata
    MutableByteSpan & mDataBuffer;             // output buffer, modified by `Decode`
};

} // namespace Ember

namespace DataModel {

/// Helper method to forward the decode of this type to the class specific implementation
inline CHIP_ERROR Decode(TLV::TLVReader & reader, Ember::EmberAttributeDataBuffer & buffer)
{
    return buffer.Decode(reader);
}

inline CHIP_ERROR Encode(TLV::TLVWriter & writer, TLV::Tag tag, Ember::EmberAttributeDataBuffer & buffer)
{
    return buffer.Encode(writer, tag);
}

} // namespace DataModel
} // namespace app
} // namespace chip
