/*
 *
 *    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 <lib/core/CHIPError.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/ScopedBuffer.h>
#include <lib/support/Span.h>

#include <cstdint>
#include <type_traits>

namespace chip {
namespace detail {

class GenericAppendOnlyBuffer
{
public:
    GenericAppendOnlyBuffer(size_t elementSize) : mElementSize(elementSize) {}
    ~GenericAppendOnlyBuffer();

    GenericAppendOnlyBuffer(GenericAppendOnlyBuffer && other);
    GenericAppendOnlyBuffer & operator=(GenericAppendOnlyBuffer &&);

    GenericAppendOnlyBuffer()                                            = delete;
    GenericAppendOnlyBuffer & operator=(const GenericAppendOnlyBuffer &) = delete;

    /// Ensure that at least the specified number of elements
    /// can be appended to the internal buffer;
    ///
    /// This will cause the internal buffer to become an allocated buffer
    [[nodiscard]] CHIP_ERROR EnsureAppendCapacity(size_t numElements);

    [[nodiscard]] bool IsEmpty() const { return mElementCount == 0; }

    /// Number of elements stored in the object.
    [[nodiscard]] size_t Size() const { return mElementCount; }

protected:
    /// Appends a single element of mElementSize size.
    ///
    /// ALWAYS COPIES the given element internally.
    /// Sufficient capacity MUST exist to append.
    CHIP_ERROR AppendSingleElementRaw(const void * buffer);

    /// Appends a list of elements from a raw array.
    ///
    /// This ALWAYS COPIES the elements internally.
    /// Additional capacity is AUTOMATICALLY ADDED.
    ///
    /// buffer MUST NOT point inside "own" buffer as mBuffer may be reallocated
    /// as part of the appending.
    CHIP_ERROR AppendElementArrayRaw(const void * __restrict__ buffer, size_t numElements);

    /// Appends a list of elements from a raw array.
    ///
    /// If the buffer contains no elements, this will just REFERENCE the given
    /// buffer, so its lifetime MUST be longer than the lifetime of this buffer and
    /// its usage.
    ///
    /// If the buffer already contains some elements, this will AUTOMATICALLY
    /// add additional capacity and COPY the elements at the end of the internal array.
    CHIP_ERROR ReferenceExistingElementArrayRaw(const void * buffer, size_t numElements);

    /// release ownership of any used buffer.
    ///
    /// Returns the current buffer details and releases ownership of it (clears internal state)
    void ReleaseBuffer(void *& buffer, size_t & size, bool & allocated);

private:
    const size_t mElementSize; // size of one element in the buffer
    uint8_t * mBuffer       = nullptr;
    size_t mElementCount    = 0;     // how many elements are stored in the class
    size_t mCapacity        = 0;     // how many elements can be stored in total in mBuffer
    bool mBufferIsAllocated = false; // if mBuffer is an allocated buffer
};

/// Represents a RAII instance owning a buffer.
///
/// It auto-frees the owned buffer on destruction via `Platform::MemoryFree`.
///
/// This class is designed to be a storage class for `GenericAppendOnlyBuffer` and
/// its subclasses (i.e. GenericAppendOnlyBuffer uses PlatformMemory and this class
/// does the same. They MUST be kept in sync.)
class ScopedBuffer
{
public:
    ScopedBuffer(void * buffer) : mBuffer(buffer) {}
    ~ScopedBuffer();

    ScopedBuffer(const ScopedBuffer &)             = delete;
    ScopedBuffer & operator=(const ScopedBuffer &) = delete;

    ScopedBuffer(ScopedBuffer && other) : mBuffer(other.mBuffer) { other.mBuffer = nullptr; }
    ScopedBuffer & operator=(ScopedBuffer && other);

private:
    void * mBuffer;
};

} // namespace detail

template <typename T>
class ReadOnlyBuffer : public Span<const T>, detail::ScopedBuffer
{
public:
    ReadOnlyBuffer() : ScopedBuffer(nullptr) {}
    ReadOnlyBuffer(const T * buffer, size_t size, bool allocated) :
        Span<const T>(buffer, size), ScopedBuffer(allocated ? const_cast<void *>(static_cast<const void *>(buffer)) : nullptr)
    {}
    ~ReadOnlyBuffer() = default;

    ReadOnlyBuffer & operator=(ReadOnlyBuffer && other)
    {
        *static_cast<Span<const T> *>(this) = other;
        *static_cast<ScopedBuffer *>(this)  = std::move(other);
        return *this;
    }
};

template <typename T>
class ReadOnlyBufferBuilder : public detail::GenericAppendOnlyBuffer
{
public:
    using SpanType = Span<const T>;

    // we do not call destructors, just malloc things.
    // Note that classes should also be trivially assignable (we do NOT call the assignment operator)
    // This makes this class somewhat dangerous...
    //
    // Note: ideally we would want `is_trivially_copyable_v` as well however our chip::Optional
    //       implementation does not actually report that.
    static_assert(std::is_trivially_destructible_v<T>);

    ReadOnlyBufferBuilder() : GenericAppendOnlyBuffer(sizeof(T)) {}

    ReadOnlyBufferBuilder(const ReadOnlyBufferBuilder &)                   = delete;
    ReadOnlyBufferBuilder & operator=(const ReadOnlyBufferBuilder & other) = delete;

    ReadOnlyBufferBuilder(ReadOnlyBufferBuilder && other) : GenericAppendOnlyBuffer{ std::move(other) } {}

    ReadOnlyBufferBuilder & operator=(ReadOnlyBufferBuilder && other)
    {
        detail::GenericAppendOnlyBuffer::operator=(std::move(other));
        return *this;
    }

    /// Reference methods attempt to reference the existing array IN PLACE
    /// so its lifetime is assumed to be longer than the usage of this list.
    [[nodiscard]] CHIP_ERROR ReferenceExisting(SpanType span) { return ReferenceExistingElementArrayRaw(span.data(), span.size()); }

    /// Reference methods attempt to reference the existing array IN PLACE
    /// so its lifetime is assumed to be longer than the usage of this list.
    ///
    /// IMPORTANT: do NOT use `ReferenceExisting({a,b,c,...})` as this does not meet the lifetime requirement
    template <size_t N>
    [[nodiscard]] CHIP_ERROR ReferenceExisting(const T (&buffer)[N])
    {
        return ReferenceExistingElementArrayRaw(buffer, N);
    }

    /// Append always attempts to append/extend existing memory.
    ///
    /// Automatically attempts to allocate sufficient space to fulfill the element
    /// requirements.
    ///
    /// `span` MUST NOT point inside "own" buffer (and generally will not
    /// as this class does not expose buffer access except by releasing ownership
    /// via `Take`)
    [[nodiscard]] CHIP_ERROR AppendElements(SpanType span) { return AppendElementArrayRaw(span.data(), span.size()); }

    /// Append always attempts to append/extend existing memory.
    ///
    /// Automatically attempts to allocate sufficient space to fulfill the element
    /// requirements.
    ///
    /// Can be used like `AppendElements({foo, bar, baz})`
    template <size_t N>
    [[nodiscard]] CHIP_ERROR AppendElements(const T (&buffer)[N])
    {
        return AppendElementArrayRaw(buffer, N);
    }

    /// Append a single element.
    /// Sufficent append capacity MUST exist.
    [[nodiscard]] CHIP_ERROR Append(const T & value) { return AppendSingleElementRaw(&value); }

    /// Once a list is built, the data is taken as a scoped SPAN that owns its data
    /// and the original list is cleared
    ReadOnlyBuffer<T> TakeBuffer()
    {
        void * buffer;
        size_t size;
        bool allocated;
        ReleaseBuffer(buffer, size, allocated);

        return ReadOnlyBuffer<T>(static_cast<const T *>(buffer), size, allocated);
    }
};

} // namespace chip
