#pragma once

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

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

        // Gun is attache dto player. So need camera trafo;
        std::shared_ptr<const Student::CameraForGLFW> mCamera;
        
        // Constructor
        GunObject(
        VkDevice dev, 
        VkPhysicalDevice phyDev, 
        VkDescriptorPool descPool, 
        VkDescriptorSetLayout descSetLayout, 
        VkQueue queue,
        VkSampler sampler,
        VkSampler normalSampler,
        VkCommandPool cmdPool,
        bool sync,
        PFN_vkCmdPipelineBarrier2KHR pfn,
        std::shared_ptr<const Student::CameraForGLFW> inputCamera,
        ShadowMap& shadowMap):
        SceneObject(dev, phyDev, descPool, descSetLayout, queue, 
            {
                // Vertices for the top and bottom face of the pizza box
                {{-0.1f, 0.1f, 0.251f}, {1.0f, 0.0f, 0.0f}},  // Top face top-left corner
                {{0.1f, 0.1f, 0.251f}, {0.0f, 1.0f, 0.0f}},   // Top face top-right corner
                {{0.1f, 0.1f, -0.251f}, {0.0f, 0.0f, 1.0f}},  // Top face bottom-right corner
                {{-0.1f, 0.1f, -0.251f}, {1.0f, 1.0f, 1.0f}}, // Top face bottom-left corner
                {{-0.1f, -0.1f, 0.251f}, {1.0f, 1.0f, 0.0f}}, // Bottom face top-left corner
                {{0.1f, -0.1f, 0.251f}, {0.0f, 0.0f, 0.0f}},  // Bottom face top-right corner
                {{0.1f, -0.1f, -0.251f}, {1.0f, 0.0f, 1.0f}}, // Bottom face bottom-right corner
                {{-0.1f, -0.1f, -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
        ),
        mCamera(inputCamera)
        {
            // gun is blue
            GeometryData data;
            data.positions = {{-0.1f, 0.1f, 0.251f},  // Top face top-left corner
                {0.1f, 0.1f, 0.251f},   // Top face top-right corner
                {0.1f, 0.1f, -0.251f},  // Top face bottom-right corner
                {-0.1f, 0.1f, -0.251f}, // Top face bottom-left corner
                {-0.1f, -0.1f, 0.251f}, // Bottom face top-left corner
                {0.1f, -0.1f, 0.251f},  // Bottom face top-right corner
                {0.1f, -0.1f, -0.251f}, // Bottom face bottom-right corner
                {-0.1f, -0.1f, -0.251f}} // Bottom face bottom-left corner
                ;
            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.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.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
        };


            glm::mat4 rotationMatrix = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f));
            glm::mat4 scalingMatrix = glm::scale(glm::mat4(1.0f), glm::vec3(3.0f, 3.0f, 3.0f));
            glm::mat4 translationMatrix = glm::translate(glm::mat4(1.0f), glm::vec3(-0.1f, 0.1f, 0.0f));
        
            uniformBuffer = vklCreateHostCoherentBufferWithBackingMemory(
            sizeof(UniformBuffer), VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT
            );
            if (!load_from_obj("assets/meshes/waterGun.obj")) {
                throw std::runtime_error("Failed to load gun.obj");
            }

            applyTransformations(this->data.positions, rotationMatrix, scalingMatrix, translationMatrix);
            this->sceneObjectGeometry = createAndUploadIntoGpuMemory(this->data);
            

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

                ubo.ambientConstant = material.ambient[0];
                ubo.diffuseConstant = material.diffuse[0];
                ubo.specularConstant = material.specular[0];
                ubo.shininessConstant = material.shininess;


                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/WaterGun_Normal.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;
                }
            }
        }

    // Update Trafos
    glm::vec3 mDirection = glm::vec3(0, 0, 0);
    virtual void updateTransforms(const FrameTimer& frameTimer)
    {
        // Calculate local Tranformation
        glm::mat4 localTranslation = glm::translate(glm::mat4(1) , glm::vec3(2,0,-5));
        localTransform = localTranslation;
        
        // 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));
        mDirection = -cameraZ;

        glm::vec3 offset = 0.5f * (cameraX-cameraY) - 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;
        for (auto& child : children) 
        {
            child->updateTransforms(frameTimer);
        }
    }
    
    int mlastStateLeftMouseButton = GLFW_RELEASE;
    std::shared_ptr<RobotObject> updateShot(GLFWwindow* window, std::vector<std::shared_ptr<RobotObject>> robots)
    {
       std::shared_ptr<RobotObject> hitRobot(nullptr);
       // state Can be  GLFW_PRESS or GLFW_RELEASE
       int currentStateLeftMouseButton = glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_LEFT);
       if(GLFW_RELEASE == mlastStateLeftMouseButton  && GLFW_PRESS == currentStateLeftMouseButton)
       {
            std::map<float, std::shared_ptr<RobotObject>> mapOfRobots;
           
            // go through all robots and check if they are aimed at. Save the closest one as the one that is hit
            float distanceOfClosestRobotHit = FLT_MAX; // this is largest possible float
            for(std::shared_ptr<RobotObject> robot : robots)
            {
                // Get Extents of Collision Cylinder of Robot
                float robotRadius = robot->mCollisionShapeExtents[0];
                float halfRobotHeight = robot->mCollisionShapeExtents[1];
                glm::vec4 robotCenterInViewSpace = mCamera->GetViewMatrix() * glm::vec4(robot->physicsObject->getPosition(),1);

                // Check if robot is aimed at
                if(abs(robotCenterInViewSpace.x) < robotRadius && abs(robotCenterInViewSpace.y) < halfRobotHeight && robotCenterInViewSpace.z < 0)
                {
                    // check if robot is the closest
                    if(robotCenterInViewSpace.z < distanceOfClosestRobotHit)
                    {
                        // robot is closest so far >> mark is as candiadate for being hit
                        distanceOfClosestRobotHit = robotCenterInViewSpace.z;
                        hitRobot = robot;
                    }
                }
            }
       }

       // if a robot was hit make robot black, if already black make it white. Also mark it as hit
       if(hitRobot)
       {
           hitRobot->mIsStunned = true;
           if (glm::length(hitRobot->ubo.color - glm::vec4(0,0,0,1)) < 0.001) hitRobot->ubo.color = glm::vec4(1, 1, 1, 1);
           else hitRobot->ubo.color = glm::vec4(0, 0, 0, 1);
       }
       mlastStateLeftMouseButton = currentStateLeftMouseButton;
       return hitRobot;
    }


    /**
     * 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.
     */
    void recordRenderingCommands(VkCommandBuffer commandBuffer, VkPipeline hudPipeline, VkPipeline specularPipeline, VkPipeline normalPipeline, VkPipeline normalheightPipeline, const FrameTimer& frameTimer){        
        drawObject(normalPipeline);
    }

    virtual void recordRenderingCommandsForShadowMap(ShadowMap& shadowMap, const Spotlight& spotlight)
    {
        // we do not draw the gun shadow because the gun moves for some reason when turnin around (bug) in the shadow renderer.
    }

    virtual void updateUniformBufferForShadow(ShadowMap shadowMap, Spotlight spotlight, Student::CameraForGLFW camera)
    {
        // we do not draw the gun shadow because the gun moves for some reason when turnin around (bug) in the shadow renderer.
    }


};

