#pragma once

#include "../../api/Models.hpp"
#include "Buffers.hpp"

namespace render::vk {

/// Positions only buffer
class Shape final: public render::Shape, public Buffer {
public:
    const size_t size;

    static std::unique_ptr<Shape> Create(const std::vector<glm::vec3>&);

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription description{};
        description.binding = 0;
        description.stride = sizeof(glm::vec3);
        description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return description;
    }
    static VkVertexInputAttributeDescription getAttributeDescription() {
        VkVertexInputAttributeDescription attributeDescription{};
        attributeDescription.binding = 0;
        attributeDescription.location = 0;
        attributeDescription.format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescription.offset = 0;
        return attributeDescription;
    }

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

class ColoredShape final: public render::ColoredShape, public Buffer {
public:
    const size_t size;

    static std::unique_ptr<ColoredShape> Create(const std::vector<glm::vec3>&, const std::vector<glm::vec4>&);

    struct Vertex {
        alignas(16) glm::vec3 pos;
        alignas(16) glm::vec4 color;
    };

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription description{};
        description.binding = 0;
        description.stride = sizeof(Vertex);
        description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return description;
    }
    static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescription() {
        std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions{};
        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(Vertex, pos);
        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(Vertex, color);
        return attributeDescriptions;
    }

protected:
    ColoredShape(size_t size, VkBuffer ref, memory::ptr mem): Buffer(ref, std::move(mem)), size(size) {}

};

class Model final: public render::Model, public ShortIndexedVertexBuffer {
public:
    constexpr size_t getIndexSize() const { return indexSize; }

    static std::unique_ptr<Model> Create(const Data &);

    static void MakeDefault();

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription description{};
        description.binding = 0;
        description.stride = sizeof(PackedVertexData);
        description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return description;
    }
    static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions{};
        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R16G16B16_SFLOAT;
        attributeDescriptions[0].offset = offsetof(PackedVertexData, PosMat[0]);
        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R16_UINT;
        attributeDescriptions[1].offset = offsetof(PackedVertexData, PosMat[3]);
        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R16G16B16_SFLOAT;
        attributeDescriptions[2].offset = offsetof(PackedVertexData, Nrm);
        return attributeDescriptions;
    }

protected:
    Model(size_t size, VkBuffer vertex, VkBuffer index, memory::ptr mem):
        ShortIndexedVertexBuffer(vertex, index, std::move(mem)), indexSize(size) { }

    size_t indexSize;
};
class LodModel final: public render::LodModel, public ShortIndexedVertexBuffer {
public:
    static std::unique_ptr<LodModel> Create(const LodData&);

    static void MakeDefault();

    constexpr uint32_t getIndexStart() const { return getOffset(level); }
    constexpr uint32_t getIndexSize() const { return getOffset(level+1) - getOffset(level); }

protected:
    LodModel(size_t size, const std::vector<size_t>& off,
        VkBuffer vertex, VkBuffer index, memory::ptr mem):
        ShortIndexedVertexBuffer(vertex, index, std::move(mem))
    {
        indexSize = size;
        offsets = off;
    }
};

struct ViewProjUBO {
    alignas(16) glm::mat4 view;
    alignas(16) glm::mat4 proj;

    static VkDescriptorSetLayoutBinding getLayoutBinding() {
        VkDescriptorSetLayoutBinding uboLayoutBinding{};
        uboLayoutBinding.binding = 0;
        uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        uboLayoutBinding.descriptorCount = 1;
        uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
        uboLayoutBinding.pImmutableSamplers = nullptr;
        return uboLayoutBinding;
    }
};
struct VoxelUBO {
    alignas(16) glm::mat4 view;
    alignas(16) glm::mat4 proj;

    alignas(16) glm::vec3 lightInv;
    //NOTE: one "free" space
    alignas(16) glm::vec4 fog;

    static VkDescriptorSetLayoutBinding getLayoutBinding() {
        VkDescriptorSetLayoutBinding uboLayoutBinding{};
        uboLayoutBinding.binding = 0;
        uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
        uboLayoutBinding.descriptorCount = 1;
        uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
        uboLayoutBinding.pImmutableSamplers = nullptr;
        return uboLayoutBinding;
    }
};

struct ModelPush {
    alignas(16) glm::mat4 model;
};
struct ModelColorPush {
    alignas(16) glm::mat4 model;
    alignas(16) glm::vec4 color;
};
struct CurvaturePush {
    alignas(16) glm::vec4 sphereProj;
    alignas(4) float curvature;
};
struct UniqueCurvaturePush {
    alignas(16) glm::mat4 model;
    alignas(16) glm::vec4 sphereProj;
    alignas(4) float curvature;
};
}