#pragma once

#include <vulkan/vulkan.h>
#include "SceneObject.h"
#include "ScooterObject.h"


/**
 * Represents a pizza object within a Vulkan-rendered scene.
 * Inherits from SceneObject to leverage common object properties and functionalities.
 */
class PizzaObject : public SceneObject
{
    public:
        PizzaObject(VkDevice dev, VkPhysicalDevice phyDev, VkDescriptorPool descPool, VkDescriptorSetLayout descSetLayout, VkQueue queue,
            VkSampler sampler,
            VkSampler normalSampler,
            VkCommandPool cmdPool,
            bool sync,
            PFN_vkCmdPipelineBarrier2KHR pfn,
            ShadowMap& shadowMap,
            const glm::vec3& initialCollisionBoxHalfSidelengths = glm::vec3(1, 1, 1)) // Pizza does not have its own physics but its collison box is used to extend the parents colllison box
        :SceneObject(dev, phyDev, descPool, descSetLayout, queue, 
            {
                // Vertices for the top and bottom face of the pizza box
                {{-0.251f, 0.025f, 0.251f}, {1.0f, 0.0f, 0.0f}},  // Top face top-left corner
                {{0.251f, 0.025f, 0.251f}, {0.0f, 1.0f, 0.0f}},   // Top face top-right corner
                {{0.251f, 0.025f, -0.251f}, {0.0f, 0.0f, 1.0f}},  // Top face bottom-right corner
                {{-0.251f, 0.025f, -0.251f}, {1.0f, 1.0f, 1.0f}}, // Top face bottom-left corner
                {{-0.251f, -0.025f, 0.251f}, {1.0f, 1.0f, 0.0f}}, // Bottom face top-left corner
                {{0.251f, -0.025f, 0.251f}, {0.0f, 0.0f, 0.0f}},  // Bottom face top-right corner
                {{0.251f, -0.025f, -0.251f}, {1.0f, 0.0f, 1.0f}}, // Bottom face bottom-right corner
                {{-0.251f, -0.025f, -0.251f}, {0.0f, 1.0f, 1.0f}} // Bottom face bottom-left corner
            },
            // Indices for drawing the box with triangles
            {
                0, 1, 2, 0, 2, 3, // Top face
                4, 5, 6, 4, 6, 7, // Bottom face
                0, 4, 5, 0, 5, 1, // Side faces
                1, 5, 6, 1, 6, 2,
                2, 6, 7, 2, 7, 3,
                3, 7, 4, 3, 4, 0
            }, sampler, cmdPool, sync, pfn, shadowMap
        )
        {
            GeometryData data;
            data.positions =
              
                // Vertices for the top and bottom face of the pizza box
                {{-0.251f, 0.025f, 0.251f},   // Top face top-left corner
                {0.251f, 0.025f, 0.251f},    // Top face top-right corner
                {0.251f, 0.025f, -0.251f},   // Top face bottom-right corner
                {-0.251f, 0.025f, -0.251f},  // Top face bottom-left corner
                {-0.251f, -0.025f, 0.251f}, // Bottom face top-left corner
                {0.251f, -0.025f, 0.251f},   // Bottom face top-right corner
                {0.251f, -0.025f, -0.251f}, // Bottom face bottom-right corner
                {-0.251f, -0.025f, -0.251f} // Bottom face bottom-left corner
                };

            data.normals = {
                {0.0f, 1.0f, 0.0f}, // Top face
                {0.0f, 1.0f, 0.0f},
                {0.0f, 1.0f, 0.0f},
                {0.0f, 1.0f, 0.0f},
                {0.0f, -1.0f, 0.0f}, // Bottom face
                {0.0f, -1.0f, 0.0f},
                {0.0f, -1.0f, 0.0f},
                {0.0f, -1.0f, 0.0f},
                {-1.0f, 0.0f, 0.0f}, // Left face
                {-1.0f, 0.0f, 0.0f},
                {-1.0f, 0.0f, 0.0f},
                {-1.0f, 0.0f, 0.0f},
                {1.0f, 0.0f, 0.0f}, // Right face
                {1.0f, 0.0f, 0.0f},
                {1.0f, 0.0f, 0.0f},
                {1.0f, 0.0f, 0.0f},
                {0.0f, 0.0f, 1.0f}, // Front face
                {0.0f, 0.0f, 1.0f},
                {0.0f, 0.0f, 1.0f},
                {0.0f, 0.0f, 1.0f},
                {0.0f, 0.0f, -1.0f}, // Back face
                {0.0f, 0.0f, -1.0f},
                {0.0f, 0.0f, -1.0f},
                {0.0f, 0.0f, -1.0f}
            };
            data.indices =
               {
                0, 1, 2, 0, 2, 3, // Top face
                4, 5, 6, 4, 6, 7, // Bottom face
                0, 4, 5, 0, 5, 1, // Side faces
                1, 5, 6, 1, 6, 2,
                2, 6, 7, 2, 7, 3,
                3, 7, 4, 3, 4, 0
                };
            data.colors = {
            {1.0f, 0.0f, 0.0f, 1.0f}, // Red for bottom-left back corner
            {0.0f, 1.0f, 0.0f, 1.0f}, // Green for bottom-right back corner
            {0.0f, 0.0f, 1.0f, 1.0f}, // Blue for bottom-right front corner
            {1.0f, 1.0f, 0.0f, 1.0f}, // Yellow for bottom-left front corner
            {1.0f, 0.0f, 1.0f, 1.0f}, // Magenta for top-left back corner
            {0.0f, 1.0f, 1.0f, 1.0f}, // Cyan for top-right back corner
            {1.0f, 1.0f, 1.0f, 1.0f}, // White for top-right front corner
            {0.0f, 0.0f, 0.0f, 1.0f}  // Black for top-left front corner
        };
            uniformBuffer = vklCreateHostCoherentBufferWithBackingMemory(
            sizeof(UniformBuffer), VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
        );

        // Ensure the object is loaded
        if (!load_from_obj("assets/meshes/pizza.obj")) {
            throw std::runtime_error("Failed to load pizza.obj");
        }


        // Create and upload geometry to GPU
        this->sceneObjectGeometry = createAndUploadIntoGpuMemory(this->data);



        ubo.ambientConstant = 0.2f;  // Moderate ambient reflection
        ubo.diffuseConstant = 0.8f;  // High diffuse reflection
        ubo.specularConstant = 0.1f;  // Very low specular reflection
        ubo.shininessConstant = 2.0f;  // Very low shininess


        // Load textures if available
        for (size_t i = 0; i < materials.size(); ++i) {
            const auto& material = materials[i];

 
            if (!material.diffuse_texname.empty()) {
                std::string texture_path = "assets/textures/" + material.diffuse_texname;
                ImageAndView texture = loadImage(dev, queue, cmdPool, texture_path, sync, pfn);

                VkDescriptorSet descriptorSet = allocDescriptorSet(dev, descPool, descriptorSetLayout);          


                ImageAndView normalImageAndView = loadImage(device, queue, cmdPool, "assets/textures/paper_0025_normal_opengl_4k.dds", sync, pfn);
                writeDescriptorSet(device, descriptorSet, uniformBuffer, texture.view, sampler, normalImageAndView.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);

                materialDescriptorSets[i] = descriptorSet;

            }
         }
      
            setObjectState(ObjectState::PhysicsControlled);
        }

    /**
     * Records the Vulkan rendering commands for the house 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 house.
     */
    virtual void recordRenderingCommands(VkCommandBuffer commandBuffer, VkPipeline hudPipeline, VkPipeline specularPipeline, VkPipeline normalPipeline, VkPipeline normalheightPipeline, const FrameTimer& frameTimer){
        drawObject(normalPipeline);
    }


};

class PizzaInHandObject : public PizzaObject
{
public:
    std::shared_ptr<const Student::CameraForGLFW> mCamera; // This pizza is attache dto player. So need camera trafo;
    bool renderThisPizza = true;

    PizzaInHandObject(VkDevice dev, VkPhysicalDevice phyDev, VkDescriptorPool descPool, VkDescriptorSetLayout descSetLayout, VkQueue queue,
        VkSampler sampler,
        VkSampler normalSampler,
        VkCommandPool cmdPool,
        bool sync,
        PFN_vkCmdPipelineBarrier2KHR pfn,
        ShadowMap& shadowMap,
        std::shared_ptr<const Student::CameraForGLFW> inputCamera,
        const glm::vec3& initialCollisionBoxHalfSidelengths = glm::vec3(1, 1, 1)
    ): // Pizza does not have its own physics but its collison box is used to extend the parents colllison box
    PizzaObject(dev, phyDev, descPool, descSetLayout, queue, sampler, normalSampler, cmdPool, sync, pfn, shadowMap),
    mCamera(inputCamera)
    {}

    virtual void updateTransforms(const FrameTimer& frameTimer)
    {
        // Calculate Global Transformation
        glm::vec3 cameraX = glm::normalize(glm::cross(mCamera->Front, mCamera->WorldUp));
        glm::vec3 cameraZ = glm::normalize(-mCamera->Front);
        glm::vec3 cameraY = glm::normalize(-glm::cross(cameraX, cameraZ));

        glm::vec3 offset = -cameraX - cameraY - 2.0f * cameraZ;
        glm::mat4 translation = glm::translate(glm::mat4(1), mCamera->getPosition() + offset);
        glm::mat4 rotation(glm::vec4(cameraX, 0), glm::vec4(cameraY, 0), glm::vec4(cameraZ, 0), glm::vec4(0, 0, 0, 1));
        worldTransform = translation * rotation * glm::scale(glm::mat4(1), glm::vec3(0.5, 0.5, 0.5));
        for (auto& child : children)
        {
            child->updateTransforms(frameTimer);
        }
    }
    
    virtual void recordRenderingCommands(VkCommandBuffer commandBuffer, VkPipeline hudPipeline, VkPipeline specularPipeline, VkPipeline normalPipeline, VkPipeline normalheightPipeline, const FrameTimer& frameTimer) {
        if (renderThisPizza) drawObject(normalPipeline);
    }



};


class PizzaPile : public PizzaObject
{
public:
    PizzaPile(VkDevice dev, VkPhysicalDevice phyDev, VkDescriptorPool descPool, VkDescriptorSetLayout descSetLayout, VkQueue queue,
              VkSampler sampler, VkSampler normalSampler, VkCommandPool cmdPool, bool sync, PFN_vkCmdPipelineBarrier2KHR pfn, ShadowMap& shadowMap)
        : PizzaObject(dev, phyDev, descPool, descSetLayout, queue, sampler, normalSampler, cmdPool, sync, pfn, shadowMap)
    {
    }

    void createPizzaStack(int pizzaCount, 
    VkDevice dev, VkPhysicalDevice phyDev, VkDescriptorPool descPool, VkDescriptorSetLayout descSetLayout, VkQueue queue,
              VkSampler sampler, VkSampler normalSampler, VkCommandPool cmdPool, bool sync, PFN_vkCmdPipelineBarrier2KHR pfn, ShadowMap& shadowMap,
              std::shared_ptr<ScooterObject> scooter)
    {
        const float pizzaHeight = 0.1f;
        const float pizzaScale = 0.5f; // Scale down factor
        const float rotationAmplitude = 5.0f; // Amplitude for the sine wave rotation
        const glm::vec3 pizzaOffset = glm::vec3(0.0f, 0.0f, -0.5f); // Offset to move pizzas to the end of the scooter

        for (int i = 0; i < pizzaCount; ++i) {
            auto pizza = std::make_shared<PizzaObject>(dev, phyDev, descPool, descSetLayout, queue, sampler, normalSampler, cmdPool, sync, pfn, shadowMap);
            
            // Set the position for each pizza based on its index
            float offsetY = 1.0f + i * pizzaHeight; 
            
            // Calculate the rotation angle using sine function
            float rotationAngle = rotationAmplitude * sin(i); // Rotation angle in degrees
            
            // Scale and rotate each pizza
            glm::mat4 transform = glm::mat4(1.0f);
            transform = glm::translate(transform, pizzaOffset);
            transform = glm::translate(transform, glm::vec3(0.0f, offsetY, 0.0f));
            transform = glm::scale(transform, glm::vec3(pizzaScale, pizzaScale, pizzaScale));
            transform = glm::rotate(transform, glm::radians(rotationAngle), glm::vec3(0.0f, 1.0f, 0.0f)); // Rotate around the y-axis
            
            pizza->localTransform = transform;
            
            scooter->addChild(pizza);
            pizzaStack.push_back(pizza);
        }
    }

    void removePizza()
    {
        if (!pizzaStack.empty()) {
            auto pizza = pizzaStack.back();
            pizzaStack.pop_back();
        }
    }

private:
    std::vector<std::shared_ptr<PizzaObject>> pizzaStack;
};
