#pragma once
#ifndef GEOMETRY_H
#define	GEOMETRY_H



//vertex structure
struct Vertex {
    glm::vec3 position;
    glm::vec3 color;
    glm::vec3 normal;
    glm::vec2 texCoord;
    glm::vec3 tangent;
    bool operator==(const Vertex& other) const {
        return position == other.position && color == other.color && normal == other.normal;
    }
};

struct Material {
    glm::vec3 ambient;
    glm::vec3 diffuse;
    glm::vec3 specular;
    float shininess;
};

/*!
 *	A struct that contains all data for a geometry object on the CPU-side
 */
struct GeometryData {
    // A vector of vertex positions.
    std::vector<glm::vec3> positions;

    // A vector of vertex indices.
    // Each triple of indices defines one triangle.
    std::vector<uint32_t> indices;

    /*!
     * Vertex colors
     */
    std::vector<glm::vec4> colors;


    // A vector of vertex normals.
    std::vector<glm::vec3> normals;

    // A vector of vertex texture coordinates.
    std::vector<glm::vec2> textureCoordinates;

    std::vector<glm::vec3> tangents;
};

struct Geometry {
    // A handle to a GPU buffer that contains vertex position data.
    VkBuffer positionsBuffer;

    // A handle to a GPU buffer that contains face indices data.
    VkBuffer indicesBuffer;

    // The total number of indices contained within the indicesBuffer.
    uint32_t numberOfIndices;

    // A handle to a GPU buffer that contains vertex normal data.
    VkBuffer colorsBuffer;


    // A handle to a GPU buffer that contains vertex normal data.
    VkBuffer normalsBuffer;

    // A handle to a GPU buffer that contains vertex texture coordinate data.
    VkBuffer textureCoordinatesBuffer;

    VkBuffer tangentBuffer;
};

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/hash.hpp>

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

//cornell box
float cubeWidthCornell = 3.0f;
float cubeHeightCornell = 3.0f;
float cubeDepthCornell = 3.0f;

float halfWidthCornell = cubeWidthCornell / 2.0f;
float halfHeightCornell = cubeHeightCornell / 2.0f;
float halfDepthCornell = cubeDepthCornell / 2.0f;

std::vector<Vertex> cornellVertices = {
    // Left Face (Red)
    {{glm::vec3(-halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(1.0f, 0.0f, 0.0f)}, {glm::vec3(1, 0, 0)}}, //0
    {{glm::vec3(-halfWidthCornell, -halfHeightCornell,  halfDepthCornell)}, {glm::vec3(1.0f, 0.0f, 0.0f)}, {glm::vec3(1, 0, 0)}}, //1
    {{glm::vec3(-halfWidthCornell,  halfHeightCornell,  halfDepthCornell)}, {glm::vec3(1.0f, 0.0f, 0.0f)}, {glm::vec3(1, 0, 0)}}, //2
    {{glm::vec3(-halfWidthCornell,  halfHeightCornell, -halfDepthCornell)}, {glm::vec3(1.0f, 0.0f, 0.0f)}, {glm::vec3(1, 0, 0)}}, //3

    // Right Face (Green)
    {{glm::vec3(halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.0f, 1.0f, 0.0f)}, {glm::vec3(-1, 0, 0)}}, //4
    {{glm::vec3(halfWidthCornell, -halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.0f, 1.0f, 0.0f)}, {glm::vec3(-1, 0, 0)}}, //5
    {{glm::vec3(halfWidthCornell,  halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.0f, 1.0f, 0.0f)},  {glm::vec3(-1, 0, 0)}}, //6
    {{glm::vec3(halfWidthCornell,  halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.0f, 1.0f, 0.0f)},  {glm::vec3(-1, 0, 0)}}, //7

    // Top Face (Off-white)
    {{glm::vec3(-halfWidthCornell,  halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.96f, 0.93f, 0.85f)},  {glm::vec3(0, -1, 0)}}, //8
    {{glm::vec3(-halfWidthCornell,  halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.96f, 0.93f, 0.85f)},  {glm::vec3(0, -1, 0)}}, //9
    {{glm::vec3(halfWidthCornell,  halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.96f, 0.93f, 0.85f)},  {glm::vec3(0, -1, 0)}}, //10
    {{glm::vec3(halfWidthCornell,  halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.96f, 0.93f, 0.85f)},  {glm::vec3(0, -1, 0)}}, //11

    // Bottom Face (Grey)
    {{glm::vec3(-halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.64f, 0.64f, 0.64f)},  {glm::vec3(0, 1, 0)}}, //12
    {{glm::vec3(-halfWidthCornell, -halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.64f, 0.64f, 0.64f)},  {glm::vec3(0, 1, 0)}}, //13
    {{glm::vec3(halfWidthCornell, -halfHeightCornell,  halfDepthCornell)}, {glm::vec3(0.64f, 0.64f, 0.64f)},  {glm::vec3(0, 1, 0)}}, //14
    {{glm::vec3(halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.64f, 0.64f, 0.64f)},  {glm::vec3(0, 1, 0)}}, //15

    // Back Face (Different shade)
    {{glm::vec3(halfWidthCornell, halfHeightCornell,  -halfDepthCornell)}, {glm::vec3(0.76f, 0.74f, 0.68f)},  {glm::vec3(0, 0, 1)}}, //16
    {{glm::vec3(-halfWidthCornell, halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.76f, 0.74f, 0.68f)},  {glm::vec3(0, 0, 1)}}, //17
    {{glm::vec3(-halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.76f, 0.74f, 0.68f)},  {glm::vec3(0, 0, 1)}}, //18
    {{glm::vec3(halfWidthCornell, -halfHeightCornell, -halfDepthCornell)}, {glm::vec3(0.76f, 0.74f, 0.68f)},  {glm::vec3(0, 0, 1)}}, //19
};

std::vector<uint32_t> cornellIndices = {
    // left face
    0, 3, 2, 2, 1, 0,

    // right face
    5, 6, 7, 7, 4, 5,

    // top
    8, 11, 10, 10, 9, 8,

    // bottom
    12, 13, 14, 14, 15, 12,


    //back
    17, 18, 19, 19, 16, 17
};

const std::vector<Vertex> planeVertices = {
    {{-50.0f, 1.0f, -55.0f}},
    {{50.0f, 1.0f, -55.0f}},  
    {{50.0f, 1.0f, 55.0f}},   
    {{-50.0f, 1.0f, 55.0f}},  
};

std::vector<uint32_t> planeIndices = {
    1, 0, 3, 3, 2, 1,
};

Geometry createAndUploadIntoGpuMemory(const GeometryData& geometry_data) {
    if (geometry_data.positions.empty()) {
        VKL_EXIT_WITH_ERROR("An empty GeometryData::positions vector has been passed to createAndUploadIntoGpuMemory(...)");
    }
    if (geometry_data.indices.empty()) {
        VKL_EXIT_WITH_ERROR("An empty GeometryData::indices vector has been passed to createAndUploadIntoGpuMemory(...)");
    }

    Geometry result;

    // Create vertex positions buffer and copy data into it:
    size_t positions_buffer_byte_size = geometry_data.positions.size() * sizeof(geometry_data.positions[0]);
    result.positionsBuffer = vklCreateHostCoherentBufferAndUploadData(
        static_cast<const void *>(geometry_data.positions.data()),
        positions_buffer_byte_size,
        VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
    );


    // Create vertex color buffer and copy data into it:
    result.colorsBuffer = VK_NULL_HANDLE;
    if (geometry_data.colors.size() > 0) {
        size_t colors_buffer_byte_size = geometry_data.colors.size() * sizeof(geometry_data.colors[0]);
        result.colorsBuffer = vklCreateHostCoherentBufferAndUploadData(
            geometry_data.colors.data(), colors_buffer_byte_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
    }

    // Create vertex normals buffer and copy data into it:
    size_t normals_buffer_byte_size = geometry_data.normals.size() * sizeof(geometry_data.normals[0]);
    if (geometry_data.normals.size() > 0) {
    result.normalsBuffer = vklCreateHostCoherentBufferAndUploadData(
        geometry_data.normals.data(), normals_buffer_byte_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
    }
    // Create vertex texture coordinates buffer and copy data into it:
    size_t texture_coordinates_buffer_byte_size = geometry_data.textureCoordinates.size() * sizeof(geometry_data.textureCoordinates[0]);
    if (geometry_data.textureCoordinates.size() > 0) {
    result.textureCoordinatesBuffer = vklCreateHostCoherentBufferAndUploadData(
        geometry_data.textureCoordinates.data(),
        static_cast<VkDeviceSize>(texture_coordinates_buffer_byte_size),
        VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
    );
    }

    size_t tangents_buffer_byte_size = geometry_data.tangents.size() * sizeof(geometry_data.tangents[0]);
    if (geometry_data.tangents.size() > 0) {
    result.tangentBuffer = vklCreateHostCoherentBufferAndUploadData(
        geometry_data.tangents.data(),
        static_cast<VkDeviceSize>(tangents_buffer_byte_size),
        VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
    );
    }
    // Create indices buffer and copy data into it:
    size_t indices_buffer_byte_size = geometry_data.indices.size() * sizeof(geometry_data.indices[0]);
    result.indicesBuffer = vklCreateHostCoherentBufferAndUploadData(
        geometry_data.indices.data(),
        static_cast<VkDeviceSize>(indices_buffer_byte_size),
        VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT
    );
    // Also store the number of indices:
    result.numberOfIndices = static_cast<uint32_t>(geometry_data.indices.size());

    return result;
}

void destroyGeometryGpuMemory(const Geometry& geometry) {
    vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.indicesBuffer);
    vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.tangentBuffer);
    vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.textureCoordinatesBuffer);
    vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.normalsBuffer);
    if (geometry.colorsBuffer != VK_NULL_HANDLE) {
        vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.colorsBuffer);
    }
    vklDestroyHostCoherentBufferAndItsBackingMemory(geometry.positionsBuffer);
}




#endif

