////////////////////////////////////////////////////////////////////////////////////////
//
//  Copyright 2026 OVITO GmbH, Germany
//
//  This file is part of OVITO (Open Visualization Tool).
//
//  OVITO is free software; you can redistribute it and/or modify it either under the
//  terms of the GNU General Public License version 3 as published by the Free Software
//  Foundation (the "GPL") or, at your option, under the terms of the MIT License.
//  If you do not alter this notice, a recipient may use your version of this
//  file under either the GPL or the MIT License.
//
//  You should have received a copy of the GPL along with this program in a
//  file LICENSE.GPL.txt.  You should have received a copy of the MIT License along
//  with this program in a file LICENSE.MIT.txt
//
//  This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
//  either express or implied. See the GPL or the MIT License for the specific language
//  governing rights and limitations.
//
////////////////////////////////////////////////////////////////////////////////////////

#pragma once


#include <ovito/core/Core.h>
#include <ovito/core/utilities/Color.h>
#include <ovito/core/oo/RefTarget.h>
#include <ovito/core/dataset/animation/TimeInterval.h>
#include <ovito/core/dataset/animation/controller/Controller.h>
#include <ovito/core/dataset/scene/Pipeline.h>

namespace Ovito {

/**
 * \brief Tree node in the scene hierarchy.
 */
class OVITO_CORE_EXPORT SceneNode : public RefTarget
{
public:
    /// Give this class its own metaclass.
    class OVITO_CORE_EXPORT SceneNodeClass : public RefTarget::OOMetaClass
    {
    public:
        /// Inherit constructor from base class.
        using RefTarget::OOMetaClass::OOMetaClass;

        /// Provides a custom function that takes are of the deserialization of a serialized property field that has been removed or changed in a newer version of OVITO.
        /// This is needed for backward compatibility with OVITO 3.11.
        virtual SerializedPropertyField::CustomDeserializationFunctionPtr overrideFieldDeserialization(LoadStream& stream, const SerializedPropertyField& field) const override;
    };
    OVITO_CLASS_META(SceneNode, SceneNodeClass)

public:

    /// Custom notification event types generated by this class:
    enum {
        /// This event is generated by a SceneNode when its transformation controller has generated a TargetChanged event
        /// or the transformation controller has been replaced with a different controller, or if the transformation of a
        /// parent node has changed.
        TransformationChanged = RefTarget::NEXT_AVAILABLE_EVENT_ID,

        /// End-of-list value indicating the next available event type that can be used by sub-classes for custom notifications.
        NEXT_AVAILABLE_EVENT_ID
    };

    /// Constructor.
    void initializeObject(ObjectInitializationFlags flags);

    /// \brief Returns this node's world transformation matrix.
    /// \param[in] time The animation for which the transformation matrix should be computed.
    /// \param[in,out] validityInterval The validity interval of the returned transformation matrix.
    ///                                 The interval passed to the method is reduced to the time interval during which the transformation stays constant.
    /// \return The matrix that transforms from this node's local space to absolute world space.
    ///         This matrix contains also the transformation of the parent node.
    const AffineTransformation& getWorldTransform(AnimationTime time, TimeInterval& validityInterval) const;

    /// Returns this node's world transformation matrix.
    const AffineTransformation& getWorldTransform(AnimationTime time) const {
        TimeInterval validity;
        return getWorldTransform(time, validity);
    }

    /// \brief Returns this node's local transformation matrix.
    /// \param[in] time The animation for which the transformation matrix should be computed.
    /// \param[in,out] validityInterval The validity interval of the returned transformation matrix.
    ///                                 The interval passed to the method is reduced to the time interval during which the transformation stays constant.
    /// \return The matrix that transforms from this node's local space to the coordinate space of the parent node.
    ///         This matrix does therefore not contain the transformation of the parent node.
    ///
    /// The local transformation does not contain the object transform of this node and
    /// does not contain the transformation of the parent node.
    AffineTransformation getLocalTransform(AnimationTime time, TimeInterval& validityInterval) const;

    /// Returns the parent node of this node in the scene tree graph.
    SceneNode* parentNode() const { return _parentNode; }

    /// Asks this object to delete itself. This will also delete all child nodes.
    virtual void requestObjectDeletion() override;

    /// \brief Inserts a scene node into this node's list of children.
    /// \param index The position at which to insert the child node into the list of children.
    /// \param newChild The node that becomes a child of this node. If \a newChild is already a child
    ///                 of another parent node then it is first removed from that parent.
    ///
    /// This method preserves the world transformation of the new child node by calling
    /// Transformation::changeParents() on the node's local transformation controller.
    void insertChildNode(qsizetype index, OORef<SceneNode> newChild);

    /// \brief Adds a child scene node to this node.
    /// \param newChild The node that becomes a child of this node. If \a newChild is already a child
    ///                 of another parent node then it is first removed from that parent.
    ///
    /// This method preserves the world transformation of the new child node by calling
    /// Transformation::changeParents() on the node's local transformation controller.
    void addChildNode(SceneNode* newChild) {
        insertChildNode(children().size(), newChild);
    }

    /// \brief Removes a child node from this parent node.
    /// \param index An index into this node's list of children.
    ///
    /// This method preserves the world transformation of the child node by calling
    void removeChildNode(qsizetype index);

    /// Deletes all child nodes of the scene node.
    void deleteChildren() {
        while(!children().empty())
            children().back()->requestObjectDeletion();
    }

    /// \brief Recursively visits all nodes below this parent node
    ///        and invokes the given visitor function for every node.
    ///
    /// \param fn A function that takes a SceneNode pointer as argument and returns a boolean value or void.
    /// \return true if all child nodes have been visited; false if the loop has been
    ///         terminated early because the visitor function has returned false.
    ///
    /// The visitor function may return a boolean value to indicate whether
    /// it wants to continue visit more nodes. A return value of false
    /// leads to early termination and no further nodes are visited.
    template<class Function>
    bool visitChildren(Function&& fn) const {

        // Determine the return type of the visitor function (bool or void).
        using ReturnType = std::invoke_result_t<Function, SceneNode*>;
        static_assert(std::is_same_v<ReturnType, void> || std::is_same_v<ReturnType, bool>, "The visitor function must return either void or bool.");

        for(SceneNode* child : children()) {
            if constexpr (std::is_same_v<ReturnType, bool>) {
                if(!fn(child) || !child->visitChildren(fn))
                    return false;
            }
            else {
                fn(child);
                child->visitChildren(fn);
            }
        }
        return true;
    }

    /// \brief Recursively visits all scene nodes with pipelines below this parent scene node
    ///        and invokes the given visitor function for every scene node.
    ///
    /// \param fn A function that takes an SceneNode pointer as argument and returns a boolean value or void.
    /// \return true if all pipeline scene nodes in the scene have been visited; false if the loop has been
    ///         terminated early because the visitor function has returned false.
    ///
    /// The visitor function may return a boolean value to indicate whether
    /// it wants to continue to visit more pipeline scene nodes. A return value of false
    /// leads to early termination and no further pipeline scene nodes are visited.
    template<class Function>
    bool visitPipelines(Function&& fn) const {

        // Determine the return type of the visitor function (bool or void).
        using ReturnType = std::invoke_result_t<Function, SceneNode*>;
        static_assert(std::is_same_v<ReturnType, void> || std::is_same_v<ReturnType, bool>, "The visitor function must return either void or bool.");

        for(SceneNode* child : children()) {
            if constexpr (std::is_same_v<ReturnType, bool>) {
                if(child->pipeline() && !fn(child))
                    return false;
                if(!child->visitPipelines(fn))
                    return false;
            }
            else {
                if(child->pipeline())
                    fn(child);
                child->visitPipelines(fn);
            }
        }
        return true;
    }

    /// Determines whether the given pipeline is part of this scene tree.
    bool containsPipeline(const Pipeline* pipeline) const {
        OVITO_ASSERT(pipeline);
        return visitPipelines([&](const SceneNode* node) { return node->pipeline() != pipeline; }) == false;
    }

    /// \brief Binds this scene node to a target node and creates a LookAtController
    ///        that lets this scene node look at the target.
    /// \param targetNode The target to look at or \c nullptr to unbind the node from its old target.
    /// \return The newly created LookAtController assigned as rotation controller for this node.
    ///
    /// The target node will automatically be deleted if this SceneNode is deleted and vice versa.
    LookAtController* setLookatTargetNode(AnimationTime time, SceneNode* targetNode);

    /// \brief Computes the bounding box of the scene node in local coordinates.
    /// \param time The animation time at which to compute the bounding box.
    /// \return An axis-aligned box in the node's local coordinate system that contains
    ///         the whole node geometry.
    /// \note The returned box does not contains the bounding boxes of the child nodes.
    Box3 localBoundingBox(AnimationTime time) const;

    /// \brief Returns the bounding box of the scene node in world coordinates.
    /// \param time The animation time at which to compute the bounding box.
    /// \param vp The viewport in which to compute the bounding box. If specified, the method takes into account per-viewport visibility of the scene nodes.
    /// \return An axis-aligned box in the world local coordinate system that contains
    ///         the whole node geometry including all child nodes.
    Box3 worldBoundingBox(AnimationTime time, Viewport* vp = nullptr) const;

    /// \brief Returns whether this scene node is currently selected.
    /// \return \c true if this node is part of the current SelectionSet;
    ///         \c false otherwise.
    ///
    /// A node is considered selected if it is in the current SelectionSet of the scene
    /// or if its upper most closed group parent is in the selection set.
    bool isSelected() const;

    /// \brief Returns whether this is the root scene node.
    /// \return \c true if this is the root node of the scene.
    virtual bool isRootNode() const { return false; }

    /// \brief Returns whether this node is part of a scene.
    /// \return \c true if the node has a root node.
    bool isInScene() const { return scene() != nullptr; }

    /// Returns the root node of the scene tree; or \c nullptr if the node is not currently part of a scene.
    Scene* scene() const;

    /// Returns the title of this object.
    virtual QString objectTitle() const override;

    /// Shows/hides this node in the given viewport, i.e. turns rendering on or off.
    void setPerViewportVisibility(Viewport* vp, bool visible);

    /// Returns whether this scene node (or one of its parents in the node hierarchy) has been hidden
    /// specifically in the given viewport.
    bool isHiddenInViewport(const Viewport* vp, bool includeHierarchyParent) const;

protected:

    /// From RefMaker.
    virtual bool referenceEvent(RefTarget* source, const ReferenceEvent& event) override;

    /// From RefMaker.
    virtual void referenceReplaced(const PropertyFieldDescriptor* field, RefTarget* oldTarget, RefTarget* newTarget, int listIndex) override;

    /// From RefMaker.
    virtual void referenceInserted(const PropertyFieldDescriptor* field, RefTarget* newTarget, int listIndex) override;

    /// From RefMaker.
    virtual void referenceRemoved(const PropertyFieldDescriptor* field, RefTarget* oldTarget, int listIndex) override;

    /// Asks the object to register internal object references that will be saved to a data stream.
    virtual void registerObjectReferencesForSerialization(ObjectSaveStream& stream, const RefTarget* deltaReferenceObject) const override;

    /// Saves the class' contents to the given stream.
    virtual void saveToStream(ObjectSaveStream& stream, bool excludeRecomputableData) const override;

    /// Loads the class' contents from the given stream.
    virtual void loadFromStream(ObjectLoadStream& stream) override;

    /// Creates a copy of this object.
    virtual OORef<RefTarget> clone(bool deepCopy, CloneHelper& cloneHelper) const override;

    /// This method marks the world transformation cache as invalid,
    /// so it will be rebuilt during the next call to getWorldTransform().
    void invalidateWorldTransformation();

    /// This method marks the cached world bounding box as invalid,
    /// so it will be rebuilt during the next call to worldBoundingBox().
    void invalidateBoundingBox();

    /// Computes the bounding box of the scene node in local coordinates.
    virtual Box3 localBoundingBoxInternal(AnimationTime time, TimeInterval& validity) const {
        return pipeline() ? pipeline()->localBoundingBox(time, validity) : Box3();
    }

    /// Is called whenever one of the child nodes in the tree has generated a AnimationFramesChanged event.
    virtual void onAnimationFramesChanged();

private:

    /// This node's parent node in the hierarchy.
    SceneNode* _parentNode = nullptr;

    /// Transformation matrix controller.
    DECLARE_MODIFIABLE_REFERENCE_FIELD_FLAGS(OORef<Controller>, transformationController, setTransformationController, PROPERTY_FIELD_ALWAYS_DEEP_COPY);

    /// The name of this scene node.
    DECLARE_MODIFIABLE_PROPERTY_FIELD(QString{}, sceneNodeName, setSceneNodeName);

    /// The display color of the node.
    DECLARE_MODIFIABLE_PROPERTY_FIELD((Color{0,0,0}), displayColor, setDisplayColor);

    /// Stores the target node this scene node is bound to using a look
    /// at controller or null if this scene node is not bound to a target node.
    DECLARE_REFERENCE_FIELD_FLAGS(OORef<SceneNode>, lookatTargetNode, PROPERTY_FIELD_ALWAYS_CLONE | PROPERTY_FIELD_NO_SUB_ANIM | PROPERTY_FIELD_DONT_PROPAGATE_MESSAGES);

    /// The child nodes of this node.
    DECLARE_VECTOR_REFERENCE_FIELD_FLAGS(OORef<SceneNode>, children, PROPERTY_FIELD_ALWAYS_CLONE | PROPERTY_FIELD_NO_SUB_ANIM);

    /// Viewports in which this node should NOT be rendered. Allows to selectively control the visibility of the scene node in different viewports.
    DECLARE_MODIFIABLE_PROPERTY_FIELD_FLAGS(std::vector<OOWeakRef<Viewport>>{}, hiddenInViewports, setHiddenInViewports, PROPERTY_FIELD_DONT_SERIALIZE);

    /// The pipeline displayed by the scene node.
    DECLARE_MODIFIABLE_REFERENCE_FIELD(OORef<Pipeline>, pipeline, setPipeline);

    /// This node's cached world transformation matrix.
    /// It contains the transformation of the parent node.
    mutable AffineTransformation _worldTransform{AffineTransformation::Identity()};

    /// This time interval indicates for which times the cached world transformation matrix
    /// has been computed.
    mutable TimeInterval _worldTransformValidity{TimeInterval::empty()};

    /// The cached local bounding box of this node.
    mutable Box3 _localBoundingBox;

    /// Validity time interval of the cached local bounding box.
    mutable TimeInterval _boundingBoxValidity{TimeInterval::empty()};
};

}   // End of namespace
