#ifndef SVK_TYPES_HPP
#define SVK_TYPES_HPP

#include "svk_functions.hpp"
#include "svk_vector_utils.hpp"

#include <memory>
#include <optional>
#include <string>
#include <vector>
#include <array>
#include <functional>
#include <deque>
#include <iostream>

#include <vulkan/vulkan.h>
#include <vulkan/vk_enum_string_helper.h>
#include <vk_mem_alloc.h>



#define VK_CHECK(x)                                                     \
    do {                                                                \
        VkResult err = x;                                               \
        if (err) {                                                      \
            std::cout << "Detected Vulkan error: " << string_VkResult(err) << std::endl; \
            abort();                                                    \
        }                                                               \
    } while (0)

struct AllocatedImage {
    VkImage image;
    VkImageView imageView;
    VmaAllocation allocation;
    VkExtent3D imageExtent;
    VkFormat imageFormat;
    VkSampler *textureSampler;
};

struct AllocatedBuffer {
    VkBuffer buffer;
    VmaAllocation allocation;
    VmaAllocationInfo info;
};

enum VertexType{
    VERTEX_P = 0,
    VERTEX_PN = 1,
    VERTEX_PT = 2,
    VERTEX_PNT = VERTEX_PN | VERTEX_PT,
};

constexpr uint32_t getAttributeDescriptionCount(VertexType type)
{
    uint32_t result = 1;
    if (type & VERTEX_PN)
        ++result;
    if (type & VERTEX_PT)
        ++result;
    return result;
}

template <VertexType type>
struct Vertex {
    vec3 position;
    vec3 normal;
    vec2 texCoord;

//    bool operator==(const Vertex& other) const
//    {
//        return position == other.position && normal == other.normal && texCoord == other.texCoord;
//    }

    static const uint32_t bindingDescriptionCount = 1;
    static VkVertexInputBindingDescription getBindingDescription()
    {
        VkVertexInputBindingDescription bindingDescription{};
        bindingDescription.binding = 0;
        bindingDescription.stride = sizeof(Vertex);
        bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
        return bindingDescription;
    }

    static const uint32_t normalOffset = offsetof(Vertex, normal);
    static const VertexType vertexType = type;
    static const uint32_t attributeDescriptionCount = getAttributeDescriptionCount(vertexType);
    static std::array<VkVertexInputAttributeDescription, attributeDescriptionCount> getAttributeDescription()
    {
        std::array<VkVertexInputAttributeDescription, attributeDescriptionCount> attributeDescription{};
        attributeDescription[0].location = 0;
        attributeDescription[0].binding = 0;
        attributeDescription[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescription[0].offset = offsetof(Vertex, position);

        if (vertexType & VERTEX_PN){
            attributeDescription[1].location = 1;
            attributeDescription[1].binding = 0;
            attributeDescription[1].format = VK_FORMAT_R32G32B32_SFLOAT;
            attributeDescription[1].offset = offsetof(Vertex, normal);
        }
        else if (vertexType & VERTEX_PT){
            attributeDescription[1].location = 1;
            attributeDescription[1].binding = 0;
            attributeDescription[1].format = VK_FORMAT_R32G32_SFLOAT;
            attributeDescription[1].offset = offsetof(Vertex, texCoord);
        }

        if (vertexType == VERTEX_PNT){
            attributeDescription[2].location = 2;
            attributeDescription[2].binding = 0;
            attributeDescription[2].format = VK_FORMAT_R32G32_SFLOAT;
            attributeDescription[2].offset = offsetof(Vertex, texCoord);
        }

        return attributeDescription;
    }
};

template<VertexType type>
struct std::hash<Vertex<type>>
{
    size_t operator()(Vertex<type> const& vertex) const
    {
        return ((hash<vec3>()(vertex.position) ^
                (hash<vec3>()(vertex.normal) << 1)) >> 1) ^
                (hash<vec2>()(vertex.texCoord) << 1);
    }
};

template <VertexType type>
struct SVKModel{
    std::vector<Vertex<type>> vertices;
    std::vector<uint32_t> indices;
    AllocatedBuffer indexBuffer;
    AllocatedBuffer vertexBuffer;
};

template <VertexType type>
struct Object{
    struct UniformData
    {
        uint32_t binding;
        AllocatedBuffer* buffer;
        size_t size;

        UniformData(uint32_t const& pBinding, AllocatedBuffer* pBuffer, size_t const& pSize)
        : binding{pBinding}, buffer{pBuffer}, size{pSize} {}
    };
	VkPipeline* pipeline;
	VkPipelineLayout* pipelineLayout;
	VkDescriptorSetLayout* descriptorSetLayout;
	SVKModel<type>* model;
	std::vector<std::pair<uint32_t, AllocatedImage*>> textures;
    std::vector<UniformData> uniformBuffers;

	void init(VkPipeline* pPipeline,
			  VkPipelineLayout* pPipelineLayout,
			  VkDescriptorSetLayout* pDescriptorSetLayout,
			  SVKModel<type>* pModel)
	{
		throwIfNullptr(pPipeline, "Object.init() called with pPipeline as nullptr");
		throwIfNullptr(pPipelineLayout, "Object.init() called with pPipelineLayout as nullptr");
		throwIfNullptr(pDescriptorSetLayout, "Object.init() called with pDescriptorSetLayout as nullptr");
		throwIfNullptr(pModel, "Object.init() called with pModel as nullptr");
		pipeline = pPipeline;
		pipelineLayout = pPipelineLayout;
		descriptorSetLayout = pDescriptorSetLayout;
		model = pModel;
	}

	void addTexture(uint32_t const& textureBinding,
			  		AllocatedImage* texture)
	{
		throwIfNullptr(texture, "Object.addTexture() called with pTexture as nullptr");
		textures.emplace_back(textureBinding, texture);
	}

    void addUniformBuffer(uint32_t const& uniformBinding,
                          AllocatedBuffer* uniformBuffer,
                          size_t const& size)
    {
        throwIfNullptr(uniformBuffer, "Object.addUniformBuffer() called with pTexture as nullptr");
        uniformBuffers.emplace_back(uniformBinding, uniformBuffer, size);
    }
};

struct TextureData		// Create A Structure for .tga loading.
{
	unsigned char *imageData;			// Image Data (Up To 32 Bits)
	unsigned int bpp;				// Image Color Depth In Bits Per Pixel.
	unsigned int width;				// Image Width
	unsigned int height;				// Image Height
	unsigned int w;				// Image Width "raw"
	unsigned int h;				// Image Height "raw"
	unsigned int texID;				// Texture ID Used To Select A Texture
	float texWidth, texHeight;
};


#define SDL_MAX_UINT8   ((Uint8)0xFF)           /* 255 */
#define SDL_MIN_UINT8   ((Uint8)0x00)           /* 0 */
typedef uint8_t Uint8;

#endif // SVK_TYPES_HPP
