/**
 * @file Buffer.h
 * @author apio (cloudapio.eu)
 * @brief A managed wrapper around a resizable buffer of arbitrary memory.
 *
 * @copyright Copyright (c) 2023, the Luna authors.
 *
 */

#pragma once
#include <luna/Result.h>
#include <luna/Types.h>

/**
 * @brief A managed wrapper around a resizable buffer of arbitrary memory.
 */
class Buffer
{
  public:
    Buffer();
    ~Buffer();

    Buffer(Buffer&& other);
    Buffer(const Buffer& other) = delete; // For now.

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

    /**
     * @brief Create a Buffer object, allocating a specific amount of memory for it.
     *
     * @param size The number of bytes to allocate.
     * @return Result<Buffer> An error, or the newly created buffer.
     */
    static Result<Buffer> create_sized(usize size);

    /**
     * @brief Resize the buffer.
     *
     * The existing data is kept, unless the new size is smaller than the old size, in which case only the first
     * new_size bytes are kept.
     *
     * @param new_size The new size of the buffer, in bytes.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> try_resize(usize new_size);

    /**
     * @brief Expand the buffer and return a pointer to the beginning of this new expanded area.
     *
     * @param size The amount of bytes to expand the buffer by.
     * @return Result<u8*> An error, or a pointer to the new area of the buffer.
     */
    Result<u8*> slice_at_end(usize size);

    /**
     * @brief Return a pointer to an area of the buffer, expanding it if necessary.
     *
     * @param offset The offset inside the buffer to start at.
     * @param size The amount of bytes to reserve.
     * @return Result<u8*> An error, or a pointer to the requested area.
     */
    Result<u8*> slice(usize offset, usize size);

    /**
     * @brief Add data to the end of the buffer.
     *
     * @param data A pointer to the data to add.
     * @param size The amount of bytes to add.
     * @return Result<void> Whether the operation succeeded.
     */
    Result<void> append_data(const u8* data, usize size);

    /**
     * @brief Remove data from the beginning of the buffer and return it.
     *
     * @param data A pointer to store the removed data in.
     * @param size The amount of bytes to remove.
     * @return usize The amount of bytes actually removed (may be less if the buffer was smaller than the requested
     * size).
     */
    usize dequeue_data(u8* data, usize size);

    /**
     * @brief Return a pointer to the data contained in the buffer.
     *
     * This pointer may be invalid after the buffer is resized.
     *
     * @return u8* The contained data.
     */
    u8* data()
    {
        return m_data;
    }

    /**
     * @brief Return a pointer to the data contained in the buffer.
     *
     * This pointer may be invalid after the buffer is resized.
     *
     * @return const u8* The contained data.
     */
    const u8* data() const
    {
        return m_data;
    }

    /**
     * @brief Return a pointer to the data contained in the buffer, moving the data out of the buffer.
     *
     * This call will empty the buffer, making it the caller's responsibility to manage the data, including freeing it
     * when no longer used.
     *
     * @return u8* The released data.
     */
    u8* release_data();

    /**
     * @brief Return a pointer to the end of the data contained in the buffer.
     *
     * This pointer points past the data; as such, dereferencing it directly is undefined behavior.
     *
     * @return u8* The end of the data.
     */
    u8* end()
    {
        return m_data + m_size;
    }

    /**
     * @brief Return a pointer to the end of the data contained in the buffer.
     *
     * This pointer points past the data; as such, dereferencing it directly is undefined behavior.
     *
     * @return const u8* The end of the data.
     */
    const u8* end() const
    {
        return m_data + m_size;
    }

    /**
     * @brief Return the size of the buffer in bytes.
     *
     * @return usize The buffer's size.
     */
    usize size() const
    {
        return m_size;
    }

    /**
     * @brief Check whether the buffer is empty.
     *
     * @return true The buffer is empty.
     * @return false The buffer is not empty.
     */
    bool is_empty() const
    {
        return m_size == 0;
    }

  private:
    Buffer(u8* data, usize size);

    u8* m_data { nullptr };
    usize m_size { 0 };
};