#pragma once

#include "../../api/Buffers.hpp"
#include "Memory.hpp"
#include <vector>

namespace render::vk {

/// Self-container Vulkan buffer
class Buffer: public render::Buffer {
public:
    ~Buffer();

    VkBuffer getRef() const { return ref; }

    static void MakeDefault();

    struct info {
        VkBuffer ref = NULL;
        VkDeviceSize offset = 0;
    };

protected:
    Buffer(VkBuffer ref, memory::ptr mem):
        ref(ref), memory(std::move(mem)) { }
    VkBuffer ref;
    memory::ptr memory;
};

/// Writable (aka Host visible) vulkan buffer
class WritableBuffer: public render::WritableBuffer, public Buffer {
public:
    static std::unique_ptr<WritableBuffer> Create(size_t size, Usage usage = Usage::TRANSFER_SRC, const data_view write = data_view());

    void write(const data_view, size_t offset) override;
    void read(data_ref, size_t offset) override;

    explicit operator bool() const { return ref && memory->ptr; }

protected:
    WritableBuffer(VkBuffer ref, memory::ptr mem, VkDeviceSize memOffset = 0):
        vk::Buffer(ref, std::move(mem)), memOffset(memOffset) { }
    const VkDeviceSize memOffset;
};

/// Fast (aka Device local local) vulkan buffer
class FastBuffer: public render::FastBuffer, public Buffer {
public:

protected:
    FastBuffer(VkBuffer ref, memory::ptr mem):
        vk::Buffer(ref, std::move(mem)) { }
};

class ShortIndexedVertexBuffer: public render::ShortIndexedVertexBuffer {
public:
    static std::unique_ptr<ShortIndexedVertexBuffer> Create(const data_view vertices, const data_view indices);
    ~ShortIndexedVertexBuffer();

    static void ClearUnused(uint32_t image);

    VkBuffer getVertex() const { return vertex; }
    VkBuffer getIndex() const { return index; }
    void setLastUse(uint32_t image) { lastUseImage = image; }

    explicit operator bool() const { return vertex && index; }

protected:
    ShortIndexedVertexBuffer(VkBuffer vertex, VkBuffer index, memory::ptr mem):
        vertex(vertex), index(index), memory(std::move(mem)), lastUseImage(UINT32_MAX) { }
    VkBuffer vertex;
    VkBuffer index;
    memory::ptr memory;
    uint32_t lastUseImage;
};

class BufferGroup {
public:
    BufferGroup(): memory(memory::GetNull()) { }
    ~BufferGroup();

    void allocate(const std::vector<Buffer::requirement> &, bool writable);
    VkBuffer at(size_t i) const { return refs.at(i).ref; }
    /// Requires last allocate to be writable
    void write(size_t, const data_view);

    explicit operator bool() const { return memory && !refs.empty(); }

private:
    void free();
    std::vector<Buffer::info> refs;
    memory::ptr memory;
};

memory::ptr createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, const render::data_view view, Buffer::info &out);
memory::ptr createBuffers(const std::vector<Buffer::requirement> &requirements, VkMemoryPropertyFlags properties, std::vector<Buffer::info> &out);
}