#pragma once
#ifndef SCENENODE_H
#define SCENENODE_H

#include <vulkan/vulkan.h>
#include <vector>
#include <memory>
#include <array>
#include <sstream>
#include <glm/glm.hpp>
#include "PathUtils.h"
#include "Utils.h"
#include "descriptorSetHandling.h"
#include "VulkanLaunchpad.h"
#include "playerCamera.h"
#include "physics.h"
#include <glm/gtx/string_cast.hpp>
#include "GameLogic/SceneGraph/ObjectState.h"
#include "shadowMap.h"

#include "frameTimer.h"


/**
 * Base class for all nodes in the scene graph.
 * Supports hierarchical transformations by maintaining a list of child nodes.
 */
class SceneNode : public std::enable_shared_from_this<SceneNode>{
public:
    std::shared_ptr<SceneNode> parent;  // Parent
    std::shared_ptr<Physics::PhysicsObject> physicsObject; //PhysicsObject 
    glm::mat4 worldTransform = glm::mat4(1.0f);  // World transformation matrix
    glm::mat4 localTransform = glm::mat4(1.0f); // Local transformation matrix of this node, initialized to identity matrix.
    std::vector<std::shared_ptr<SceneNode>> children; // Children of this node in the scene graph.

    ObjectState objectState;


    SceneNode() : physicsObject(nullptr), parent(nullptr) {
    }
    virtual ~SceneNode() = default; // Virtual destructor for proper cleanup of derived types.

    virtual glm::mat4 getLocalTransform()
    {
        return localTransform;
    }

    void setLocalTransform(glm::mat4 transform){
        localTransform = transform;
    }

    virtual glm::mat4 getWorldTransform()
    {
        return worldTransform;
    }

    void attachPhysicsObject(std::shared_ptr<Physics::PhysicsObject> obj) {
        physicsObject = obj;
    }

    bool hasPhysics() const {
        return physicsObject != nullptr;
    }

    virtual void setObjectState(ObjectState state){
        if (state == ObjectState::PhysicsControlled){

        } else if (state == ObjectState::Pathing){
            physicsObject->makeKinematic(physicsObject->mRigidBody);
        }
        objectState = state;
    }

    ObjectState getObjectState(){
        return objectState;
    }


    
    
    virtual void updateTransforms(const FrameTimer& frameTimer) 
    {

    
        if (hasPhysics()){
            if (this->getObjectState() == ObjectState::PhysicsControlled){
                physicsObject->makeDynamic(physicsObject->mRigidBody);
                worldTransform = physicsObject->getModelMatrix();
            } else 
            if (this->getObjectState() == ObjectState::Pathing) {
                worldTransform = physicsObject->getModelMatrix();
            }
        } else if (parent) 
        {                      
            // Combine parent's world transform with local transform.
            worldTransform = parent->getWorldTransform() * getLocalTransform() ;
            
        }

            
        // Propagate the transform update to children.
        for (auto& child : children) {
            child->updateTransforms(frameTimer);
        }
    }


    /**
     * Adds a child node to this node's list of children.
     * @param child A shared pointer to the child node to be added.
     */
    virtual void addChild(std::shared_ptr<SceneNode> child) {
        // Always ensure that that child has same physicsManager as parent!
        std::shared_ptr<SceneNode> thisAsSharedPtr = shared_from_this();
        child->setParent(thisAsSharedPtr);
        children.push_back(child);
    }

    void setParent(std::shared_ptr<SceneNode> theParent) {
        parent = theParent;
    }

    /**
     * Updates uniform buffers of this node and all its descendants.
     * Derived classes should override this method to update their specific uniform data.
     * @param camera The camera from which the scene is viewed, used for calculating view-dependent transformations.
     */
    virtual void updateUniformBuffer(Student::CameraForGLFW& camera, const glm::vec4& windowSize, const FrameTimer& frameTimer) {
        for (auto& child : children) {
            child->updateUniformBuffer(camera, windowSize, frameTimer);
        }
    }

    virtual void updateUniformBufferForShadow(ShadowMap shadowMap, Spotlight spotlight) {
        for (auto& child : children)
        {
            child->updateUniformBufferForShadow(shadowMap, spotlight);
        }
    }

    // /**
    //  * Records rendering commands for this node and all its descendants.
    //  * Derived classes (such as renderable objects) should override this method to bind necessary resources and issue draw calls.
    //  * @param commandBuffer The command buffer into which the rendering commands are recorded.
    //  * @param pipelineLayout The pipeline layout used for rendering.
    //  * @param pipeline The graphics pipeline used for rendering.
    //  */
    virtual void recordRenderingCommands(VkCommandBuffer commandBuffer, VkPipeline hudPipeline, VkPipeline specularPipeline,
            VkPipeline normalPipeline, VkPipeline normalheightPipeline, const FrameTimer& frameTimer) {
        for (auto& child : children) {
            child->recordRenderingCommands(commandBuffer, hudPipeline, specularPipeline, normalPipeline, normalheightPipeline, frameTimer);
        }
    }

    virtual void recordRenderingCommandsForShadowMap(ShadowMap& shadowMap, const Spotlight& spotlight)
    {
        for (auto& child : children)
        {
            child->recordRenderingCommandsForShadowMap(shadowMap, spotlight);
        }
    }

    virtual void updatePosition(){
        for (auto& child : children) {
            child->updatePosition();
        }
    }
    




};

#endif // SCENENODE_H
