//
//  KRNode.h
//  Kraken Engine
//
//  Copyright 2026 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice, this list
//  of conditions and the following disclaimer in the documentation and/or other materials
//  provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//

#pragma once

#include "resources/KRResource.h"
#include "resources/KRResourceRequest.h"
#include "resources/KRResourceBinding.h"
#include "KRViewport.h"
#include "KROctreeNode.h"
#include "KRBehavior.h"
#include "KRShaderReflection.h"
#include "KRNodeProperty.h"
#include <type_traits>

using namespace kraken;

namespace hydra {
class Matrix4;
class AABB;
class Vector3;
} // namespace kraken
class KRCamera;
class KRPipelineManager;
class KRMeshManager;
class KRMaterialManager;
class KRTextureManager;
class KRContext;
class KRScene;
class KRSurface;
class KRResourceBinding;

class KRNode;
class KRPointLight;
class KRSpotLight;
class KRDirectionalLight;
class KRRenderPass;
class KRPipeline;
namespace tinyxml2 {
class XMLNode;
class XMLAttribute;
}



#define KRNODE_PROPERTY(PROP_TYPE, VAR, PROP_DEFAULT, PROP_NAME) \
   struct VAR ## _config { \
    static constexpr decltype(PROP_DEFAULT) defaultVal = PROP_DEFAULT; \
    static constexpr const char* name = PROP_NAME; \
  }; \
  KRNodeProperty<PROP_TYPE, VAR ## _config> VAR;

#define KRNODE_PROPERTY_ARRAY(PROP_TYPE, VAR, PROP_DEFAULT, PROP_NAME, ARRAY_SIZE) \
   struct VAR ## _config { \
    static constexpr decltype(PROP_DEFAULT) defaultVal = PROP_DEFAULT; \
    static constexpr const char* name = PROP_NAME; \
  }; \
  std::array<KRNodeProperty<PROP_TYPE, VAR ## _config>, ARRAY_SIZE> VAR;

class KRNode
  : public KRContextObject
  , public KRReflectedObject
{
public:

  enum LodVisibility
  {
    LOD_VISIBILITY_HIDDEN,
    LOD_VISIBILITY_PRESTREAM,
    LOD_VISIBILITY_VISIBLE
  };

  class RenderInfo
  {
  public:
    RenderInfo(VkCommandBuffer& cb)
      : commandBuffer(cb)
    {

    }

    RenderInfo(const RenderInfo&) = delete;
    RenderInfo& operator=(const RenderInfo&) = delete;

    VkCommandBuffer& commandBuffer;
    KRCamera* camera;
    KRSurface* surface;
    std::vector<KRPointLight*> point_lights;
    std::vector<KRDirectionalLight*> directional_lights;
    std::vector<KRSpotLight*> spot_lights;
    const KRViewport* viewport;
    KRRenderPass* renderPass;
    KRPipeline* pipeline;

    std::vector<const KRReflectedObject*> reflectedObjects;
  };

  static void InitNodeInfo(KrNodeInfo* nodeInfo);
  virtual KrResult update(const KrNodeInfo* nodeInfo);

  KRNode(KRScene& scene, std::string name);
  virtual ~KRNode();

  virtual tinyxml2::XMLElement* saveXML(tinyxml2::XMLNode* parent);

  static KRNode* LoadXML(KRScene& scene, tinyxml2::XMLElement* e);
  static KrResult createNode(const KrCreateNodeInfo* pCreateNodeInfo, KRScene* scene, KRNode** node);
  virtual void loadXML(tinyxml2::XMLElement* e);

  virtual std::string getElementName();
  const std::string& getName() const;

  void appendChild(KRNode* child);
  void prependChild(KRNode* child);
  void insertBefore(KRNode* child);
  void insertAfter(KRNode* child);
  KRNode* getParent() const;

  void setLocalTranslation(const hydra::Vector3& v, bool set_original = false);
  void setLocalScale(const hydra::Vector3& v, bool set_original = false);
  void setLocalRotation(const hydra::Vector3& v, bool set_original = false);


  void setRotationOffset(const hydra::Vector3& v, bool set_original = false);
  void setScalingOffset(const hydra::Vector3& v, bool set_original = false);
  void setRotationPivot(const hydra::Vector3& v, bool set_original = false);
  void setScalingPivot(const hydra::Vector3& v, bool set_original = false);
  void setPreRotation(const hydra::Vector3& v, bool set_original = false);
  void setPostRotation(const hydra::Vector3& v, bool set_original = false);

  const hydra::Vector3& getRotationOffset() const;
  const hydra::Vector3& getScalingOffset() const;
  const hydra::Vector3& getRotationPivot() const;
  const hydra::Vector3& getScalingPivot() const;
  const hydra::Vector3& getPreRotation() const;
  const hydra::Vector3& getPostRotation() const;

  const hydra::Vector3& getInitialRotationOffset() const;
  const hydra::Vector3& getInitialScalingOffset() const;
  const hydra::Vector3& getInitialRotationPivot() const;
  const hydra::Vector3& getInitialScalingPivot() const;
  const hydra::Vector3& getInitialPreRotation() const;
  const hydra::Vector3& getInitialPostRotation() const;


  const hydra::Vector3& getLocalTranslation() const;
  const hydra::Vector3& getLocalScale() const;
  const hydra::Vector3& getLocalRotation() const;

  const hydra::Vector3& getInitialLocalTranslation() const;
  const hydra::Vector3& getInitialLocalScale() const;
  const hydra::Vector3& getInitialLocalRotation() const;

  const hydra::Vector3 getWorldTranslation() const;
  const hydra::Vector3 getWorldScale() const;
  const hydra::Quaternion getWorldRotation() const;

  const hydra::Quaternion getBindPoseWorldRotation() const;
  const hydra::Quaternion getActivePoseWorldRotation() const;

  const hydra::Vector3 localToWorld(const hydra::Vector3& local_point) const;
  const hydra::Vector3 worldToLocal(const hydra::Vector3& world_point) const;

  void setWorldTranslation(const hydra::Vector3& v);
  void setWorldScale(const hydra::Vector3& v);
  void setWorldRotation(const hydra::Vector3& v);

  virtual hydra::AABB getBounds();
  void invalidateBounds();
  const hydra::Matrix4& getModelMatrix() const;
  const hydra::Matrix4& getInverseModelMatrix() const;
  const hydra::Matrix4& getBindPoseMatrix() const;
  const hydra::Matrix4& getActivePoseMatrix() const;
  const hydra::Matrix4& getInverseBindPoseMatrix() const;

  enum node_attribute_type
  {
    KRENGINE_NODE_ATTRIBUTE_NONE,
    KRENGINE_NODE_ATTRIBUTE_TRANSLATE_X,
    KRENGINE_NODE_ATTRIBUTE_TRANSLATE_Y,
    KRENGINE_NODE_ATTRIBUTE_TRANSLATE_Z,
    KRENGINE_NODE_ATTRIBUTE_SCALE_X,
    KRENGINE_NODE_ATTRIBUTE_SCALE_Y,
    KRENGINE_NODE_ATTRIBUTE_SCALE_Z,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_X,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_Y,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_Z,
    KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_X,
    KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_Y,
    KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_Z,
    KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_X,
    KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_Y,
    KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_Z,
    KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_X,
    KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_Y,
    KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_Z,
    KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_X,
    KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_Y,
    KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_Z,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_X,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_Y,
    KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_Z,
    KRENGINE_NODE_SCALE_OFFSET_X,
    KRENGINE_NODE_SCALE_OFFSET_Y,
    KRENGINE_NODE_SCALE_OFFSET_Z,
    KRENGINE_NODE_ATTRIBUTE_COUNT
  };

  void SetAttribute(node_attribute_type attrib, float v);

  KRScene& getScene();

  virtual void preStream(const KRViewport& viewport, std::list<KRResourceRequest>& resourceRequests);
  virtual void getResourceBindings(std::list<KRResourceBinding*>& bindings);
  virtual bool alwaysStreamResources();
  virtual void render(RenderInfo& ri);

  virtual void physicsUpdate(float deltaTime);
  virtual bool hasPhysics();

  virtual void updateLODVisibility(const KRViewport& viewport);
  LodVisibility getLODVisibility();

  void setScaleCompensation(bool scale_compensation);
  bool getScaleCompensation() const;
  void setAnimationEnabled(node_attribute_type attrib, bool enable);
  bool getAnimationEnabled(node_attribute_type attrib) const;


  virtual kraken_stream_level getStreamLevel(const KRViewport& viewport);

  virtual void setLODVisibility(LodVisibility lod_visibility);

public:
  bool isFirstSibling() const;
  bool isLastSibling() const;
  KRNode* m_parentNode;
  KRNode* m_previousNode;
  KRNode* m_nextNode;
  KRNode* m_firstChildNode;
  KRNode* m_lastChildNode;

protected:
  KRNODE_PROPERTY(hydra::Vector3, m_localTranslation, hydra::Vector3({ 0.f, 0.f, 0.f }), "translate");
  KRNODE_PROPERTY(hydra::Vector3, m_localScale, hydra::Vector3({ 1.f, 1.f, 1.f }), "scale");
  KRNODE_PROPERTY(hydra::Vector3, m_localRotation, hydra::Vector3({ 0.f, 0.f, 0.f }), "rotate");
  KRNODE_PROPERTY(hydra::Vector3, m_rotationOffset, hydra::Vector3({ 0.f, 0.f, 0.f }), "rotate_offset");
  KRNODE_PROPERTY(hydra::Vector3, m_scalingOffset, hydra::Vector3({ 0.f, 0.f, 0.f }), "scale_offset");
  KRNODE_PROPERTY(hydra::Vector3, m_rotationPivot, hydra::Vector3({ 0.f, 0.f, 0.f }), "rotate_pivot");
  KRNODE_PROPERTY(hydra::Vector3, m_scalingPivot, hydra::Vector3({ 0.f, 0.f, 0.f }), "scale_pivot");
  KRNODE_PROPERTY(hydra::Vector3, m_preRotation, hydra::Vector3({ 0.f, 0.f, 0.f }), "pre_rotate");
  KRNODE_PROPERTY(hydra::Vector3, m_postRotation, hydra::Vector3({ 0.f, 0.f, 0.f }), "post_rotate");

  hydra::Vector3 m_initialLocalTranslation;
  hydra::Vector3 m_initialLocalScale;
  hydra::Vector3 m_initialLocalRotation;

  hydra::Vector3 m_initialRotationOffset;
  hydra::Vector3 m_initialScalingOffset;
  hydra::Vector3 m_initialRotationPivot;
  hydra::Vector3 m_initialScalingPivot;
  hydra::Vector3 m_initialPreRotation;
  hydra::Vector3 m_initialPostRotation;

  LodVisibility m_lod_visible;

  bool m_animation_mask[KRENGINE_NODE_ATTRIBUTE_COUNT];

private:
  void makeOrphan();
  long m_lastRenderFrame;
  void invalidateModelMatrix();
  void invalidateBindPoseMatrix();
  // Members are mutable to enable lazy calculation from const accessors
  mutable hydra::Matrix4 m_modelMatrix;
  mutable hydra::Matrix4 m_inverseModelMatrix;
  mutable hydra::Matrix4 m_bindPoseMatrix;
  mutable hydra::Matrix4 m_activePoseMatrix;
  mutable hydra::Matrix4 m_inverseBindPoseMatrix;
  mutable bool m_modelMatrixValid;
  mutable bool m_inverseModelMatrixValid;
  mutable bool m_bindPoseMatrixValid;
  mutable bool m_activePoseMatrixValid;
  mutable bool m_inverseBindPoseMatrixValid;

  hydra::AABB m_bounds;
  bool m_boundsValid;

  std::string m_name;

  KRScene* m_pScene;

  std::set<KROctreeNode*> m_octree_nodes;
  bool m_scale_compensation;

  std::set<KRBehavior*> m_behaviors;

public:
  void addBehavior(KRBehavior* behavior);
  std::set<KRBehavior*>& getBehaviors();
  template <class T> T* getBehavior()
  {
    for (std::set<KRBehavior*>::iterator itr = m_behaviors.begin(); itr != m_behaviors.end(); itr++) {
      T* behavior = dynamic_cast<T*>(*itr);
      if (behavior) {
        return behavior;
      }
    }
    return NULL;
  }
  void removeFromOctreeNodes();
  void addToOctreeNode(KROctreeNode* octree_node);
  void childRemoved(KRNode* child_node);

  template <class T> T* find()
  {
    T* match = dynamic_cast<T*>(this);
    if (match) {
      return match;
    }

    for (KRNode* child = m_firstChildNode; child != nullptr; child = child->m_nextNode) {
      match = child->find<T>();
      if (match) {
        return match;
      }
    }

    return NULL;
  }

  template <class T> T* find(const std::string& name)
  {
    // TODO - KRScene should maintain a global node-name map for rapid searching
    T* match = dynamic_cast<T*>(this);
    if (match) {
      if (name.compare(match->getName()) == 0) {
        return match;
      }
    }

    for (KRNode* child = m_firstChildNode; child != nullptr; child = child->m_nextNode) {
      match = child->find<T>(name);
      if (match) {
        return match;
      }
    }

    return NULL;
  }
};
