#pragma once

#include <vulkan/vulkan.h>
#include <vector>
#include <memory>
#include "SceneObject.h"
#include "geometry.h"


/**
 * Represents a street object within a Vulkan-rendered scene.
 * Inherits from SceneObject to leverage common object properties and functionalities.
 */
class StreetNode : public SceneObjectWithPhysics {
public:

    glm::vec3 dimensions; // Width and height of the street plane
    float gridSize; // Calculated optimal grid size

    /**
     * Constructor for the StreetNode.
     * Initializes the street geometry and invokes the base SceneObject constructor.
     * 
     * @param dev The Vulkan device.
     * @param phyDev The physical Vulkan device.
     * @param descPool The descriptor pool for allocating descriptor sets.
     * @param descSetLayout The layout of the descriptor set.
     * @param queue The Vulkan queue to which rendering commands will be submitted.
     */
    StreetNode(VkDevice dev, VkPhysicalDevice phyDev,
    VkDescriptorPool descPool, 
    VkDescriptorSetLayout descSetLayout,
    VkQueue queue,
    Physics::GamePhysicsManager& inputPhysicsManager,
    VkSampler sampler,
    VkSampler normalSampler,
    VkSampler normalheightSampler,
    VkCommandPool cmdPool,
    bool sync,
    PFN_vkCmdPipelineBarrier2KHR pfn,
    ShadowMap& shadowMap,
    const glm::vec3& initialCollisionBoxPosition = glm::vec3(0,-1,0),
    const glm::vec3& initialCollisionBoxHalfSidelengths = glm::vec3(60, 1.0, 65),
    const float& inputMass = 0.f
    )
    : SceneObjectWithPhysics(dev, phyDev, descPool, descSetLayout, queue, planeVertices, planeIndices,
    
        inputPhysicsManager, initialCollisionBoxPosition, initialCollisionBoxHalfSidelengths, shadowMap, inputMass)
    {
        float tilingFactor = 1.0f; // Set this to 1.0f to start with, then adjust if needed

        
            data.positions = 
            {
            {-60.0f, 1.0f, -65.0f},
            {60.0f, 1.0f, -65.0f},  
            {60.0f, 1.0f, 65.0f},   
            {-60.0f, 1.0f, 65.0f}
            };


            data.indices =
            {
                planeIndices
            };


            data.normals = {
                // Normals for the plane (facing upwards)
                {0.0f, 1.0f, 0.0f},
                {0.0f, 1.0f, 0.0f},
                {0.0f, 1.0f, 0.0f},
                {0.0f, 1.0f, 0.0f}
            };


            data.textureCoordinates = 
            {
                {0.0f, 0.0f},   // Bottom-left corner
                {10.0f, 0.0f},   // Bottom-right corner
                {10.0f, 10.0f},   // Top-right corner
                {0.0f, 10.0f}    // Top-left corner

            };

            glm::vec3 tangent = glm::normalize(glm::vec3(1.0f, 0.0f, 0.0f)); // Tangent points along the x-axis
            data.tangents = 
            {
                tangent,
                tangent,
                tangent,
                tangent
            };
            
            this->sceneObjectGeometry = createAndUploadIntoGpuMemory(this->data);

            uniformBuffer = vklCreateHostCoherentBufferWithBackingMemory(
                sizeof(UniformBuffer), VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
            );

            ubo.ambientConstant = 0.2f;
            ubo.diffuseConstant = 0.8f;
            ubo.specularConstant = 0.3f;
            ubo.shininessConstant = 32.0f;

            std::string texture_path = "assets/textures/GrassGravel01_1K_BaseColor.dds";
            ImageAndView texture = loadImage(dev, queue, cmdPool, texture_path, sync, pfn);

            ImageAndView normalTexture = loadImage(dev, queue, cmdPool, "assets/textures/GrassGravel01_1K_Normal.dds", sync, pfn);

            ImageAndView heightTexture = loadImage(dev, queue, cmdPool, "assets/textures/GrassGravel01_1K_Height.dds", sync, pfn);

            descriptorSet = allocDescriptorSet(dev, descPool, descriptorSetLayout);
            //writeDescriptorSet(device, descriptorSet, uniformBuffer, texture.view, sampler, normalTexture.view, normalSampler, heightTexture.view, normalheightSampler);
            writeDescriptorSet(device, descriptorSet, uniformBuffer, texture.view, sampler, normalTexture.view, normalSampler);
            
            // write shadowmap to correct binding in the descriptorset (default binding = 4)
            writeDescriptorSet(device, descriptorSet, shadowMap.m_image_view, shadowMap.m_image_sampler, shadowMap.m_descriptorBinding);

            calculateDimensions(planeVertices);
            gridSize = 1.0f;
           
    }

    void calculateDimensions(const std::vector<Vertex>& vertices) {
        float minX = std::numeric_limits<float>::max();
        float maxX = std::numeric_limits<float>::lowest();
        float minZ = std::numeric_limits<float>::max();
        float maxZ = std::numeric_limits<float>::lowest();

        for (const auto& vertex : vertices) {
            minX = std::min(minX, vertex.position.x);
            maxX = std::max(maxX, vertex.position.x);
            minZ = std::min(minZ, vertex.position.z);
            maxZ = std::max(maxZ, vertex.position.z);
        }

        dimensions = glm::vec3(maxX - minX, 0.0f, maxZ - minZ); // width x height
    }

    float calculateOptimalGridSize(float width, float height) {
        // Grid size as a fraction of the larger dimension
        //return std::max(width, height) / 20.0f; // Adjust??
        return 1.0f; //feasible enough for now
    }

    glm::vec3 getDimensions() const {
        return dimensions;
    }

    // Method to get grid size
    float getGridSize() const {
        return gridSize;
    }


    /**
     * Records the Vulkan rendering commands for the street object.
     * Binds the necessary Vulkan resources and issues the draw call.
     * 
     * @param commandBuffer The command buffer to record the rendering commands.
     * @param pipeline_layout The pipeline layout used for descriptor binding.
     * @param pipeline The graphics pipeline to use for rendering the street.
     */
    void recordRenderingCommands(VkCommandBuffer commandBuffer, VkPipeline hudPipeline, VkPipeline specularPipeline, VkPipeline normalPipeline, VkPipeline normalheightPipeline, const FrameTimer& frameTimer) {
        drawGeometryWithMaterial(normalPipeline, this->sceneObjectGeometry, descriptorSet);
    }

};