/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#pragma once

#include <osgEarth/Common>
#include <osg/View>
#include <osgEarth/LoadableNode>
#include <osgEarth/PagedNode>
#include <osgGA/GUIActionAdapter>
#include <osgUtil/LineSegmentIntersector>
#include <osgEarth/Threading>
#include <osgEarth/Math>
#include <set>
#include <vector>

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    /**
     * Visitor that finds all the parental Camera Views, and calls an operator
     * on each one.
     */
    template<typename T>
    class ViewVisitor : public osg::NodeVisitor, public T
    {
    public:
        ViewVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_PARENTS) { }
        virtual ~ViewVisitor() { }
        void apply(osg::Camera& cam) {
            osg::View* view = cam.getView();
            if ( view ) this->operator()( view );
            traverse(cam);
        }
    };

    /**
     * Visitor that locates a node by its type
     */
    template<typename T>
    class FindTopMostNodeOfTypeVisitor : public osg::NodeVisitor
    {
    public:
        FindTopMostNodeOfTypeVisitor():
          osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
              _foundNode(0)
          {}

          void apply(osg::Node& node)
          {
              T* result = dynamic_cast<T*>(&node);
              if (result)
              {
                  _foundNode = result;
              }
              else
              {
                  traverse(node);
              }
          }

          T* _foundNode;
    };

    /**
     * Collects all the nodes of type "T"
     */
    template<typename T>
    struct FindNodesVisitor : public osg::NodeVisitor
    {
        FindNodesVisitor() : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) { }

        void apply(osg::Node& node)
        {
            T* result = dynamic_cast<T*>( &node );
            if ( result )
                _results.push_back( result );
            traverse(node);
        }

        std::vector<T*> _results;
    };

    template<typename T, typename FUNC>
    struct NodeFunctorVisitor : public osg::NodeVisitor
    {
        NodeFunctorVisitor(const FUNC& func) :
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _func(func) { }

        NodeFunctorVisitor(bool recurse, const FUNC& func) :
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN), _recurse(recurse), _func(func) { }

        void apply(osg::Node& node) override
        {
            T* result = dynamic_cast<T*>( &node );
            if (result) {
                _func(result);
                if (_recurse) {
                    traverse(node);
                }
            }                
            else {
                traverse(node);
            }
        }

        bool _recurse = true;
        FUNC _func;
    };  

    template<typename T, typename FUNC>
    inline void forEachNodeOfType(osg::Node* root, const FUNC& functor)
    {
        NodeFunctorVisitor<T, FUNC> visitor(functor);
        if (root)
            root->accept( visitor );
    }

    template<typename T, typename FUNC>
    inline void forEachUnnestedNodeOfType(osg::Node* root, const FUNC& functor)
    {
        NodeFunctorVisitor<T, FUNC> visitor(false, functor);
        if (root)
            root->accept(visitor);
    }

    /**
     * Searchs the scene graph downward starting at [node] and returns the first node found
     * that matches the template parameter type.
     */
    template<typename T>
    T* findTopMostNodeOfType(osg::Node* node, unsigned traversalMask =~0)
    {
        if (!node) return 0;

        FindTopMostNodeOfTypeVisitor<T> fnotv;
        fnotv.setTraversalMode(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN);
        fnotv.setTraversalMask(traversalMask);
        node->accept(fnotv);

        return fnotv._foundNode;
    }

    //! Synonym for findTopMostNodeOfType.
    template<typename T>
    T* findNode(osg::Node* node, unsigned traversalMask = ~0) {
        return findTopMostNodeOfType<T>(node, traversalMask);
    }

    /**
     * Searchs the scene graph upward starting at [node] and returns the first node found
     * that matches the template parameter type.
     */
    template<typename T>
    T* findFirstParentOfType(osg::Node* node, unsigned traversalMask =~0)
    {
        if (!node) return 0;

        FindTopMostNodeOfTypeVisitor<T> fnotv;
        fnotv.setTraversalMode(osg::NodeVisitor::TRAVERSE_PARENTS);
        fnotv.setTraversalMask(traversalMask);
        node->accept(fnotv);

        return fnotv._foundNode;
    }

    /**
     * Searchs the scene graph starting at [node] and returns the first node found
     * that matches the template parameter type. First searched upward, then downward.
     */
    template<typename T>
    T* findRelativeNodeOfType(osg::Node* node, unsigned traversalMask =~0)
    {
        if ( !node ) return 0;
        T* result = findFirstParentOfType<T>( node, traversalMask );
        if ( !result )
            result = findTopMostNodeOfType<T>( node, traversalMask );
        return result;
    }

    /** Find the top of the scene graph through parent 0 */
    inline osg::Node* findTopOfGraph(osg::Node* node)
    {
        return node && node->getNumParents() > 0 ? findTopOfGraph(node->getParent(0)) : node;
    }

    /** Finds a typed node in a node visitor's node path */
    template<typename T>
    T* findInNodePath(osg::NodeVisitor& nv) {
        for (osg::NodePath::iterator i = nv.getNodePath().begin(); i != nv.getNodePath().end(); ++i) {
            T* node = dynamic_cast<T*>(*i);
            if (node) return node;
        }
        return 0L;
    }

    /** Finds all the siblings of a node */
    inline osg::NodeList findSiblings(osg::Node* node)
    {
        osg::NodeList output;
        if (node && node->getNumParents() > 0)
        {
            auto parent = node->getParent(0);
            for(unsigned i=0; i<parent->getNumChildren(); ++i)
                if (parent->getChild(i) != node)
                    output.push_back(parent->getChild(i));
        }
        return output;
    }

    class FindNamedNodeVisitor : public osg::NodeVisitor
    {
    public:
        FindNamedNodeVisitor(const std::string& name) :
            osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN),
            _name(name)
        {
            setTraversalMask(~0);
        }

        virtual void apply(osg::Node& node)
        {
            if (node.getName() == _name)
            {
                _foundNodes.push_back(&node);
            }
            traverse(node);
        }

        typedef std::vector< osg::ref_ptr<osg::Node> > NodeList;

        std::string _name;
        NodeList _foundNodes;
    };

    inline osg::Node* findNamedNode(osg::Node* node, const std::string& name)
    {
        FindNamedNodeVisitor v(name);
        node->accept(v);
        if (!v._foundNodes.empty())
        {
            return v._foundNodes[0].get();
        }
        return 0;
    }

    /**
     * Replace one group with another
     */
    inline void replaceGroup( osg::Group* oldGroup, osg::Group* newGroup )
    {
        if ( oldGroup && newGroup && oldGroup->getNumParents() > 0 )
        {
            for(unsigned i=0; i<oldGroup->getNumChildren(); ++i)
            {
                newGroup->addChild( oldGroup->getChild(i) );
            }

            osg::Node::ParentList parents = oldGroup->getParents();
            for(osg::Node::ParentList::iterator i = parents.begin(); i != parents.end(); ++i )
            {
                (*i)->replaceChild( oldGroup, newGroup );
            }
        }
    }

    /** Insert a group between a parent and its children. */
    inline void insertGroup(osg::Group* newGroup, osg::Group* parent)
    {
        if (parent && newGroup)
        {
            for(unsigned i=0; i<parent->getNumChildren(); ++i)
            {
                newGroup->addChild( parent->getChild(i) );
            }
            parent->removeChildren(0, parent->getNumChildren());
            parent->addChild( newGroup );
        }
    }

    /** Insert a group above a node. */
    inline void insertParent(osg::Group* newParent, osg::Node* existingChild)
    {
        if ( newParent && existingChild )
        {
            newParent->addChild(existingChild);

            for (unsigned i = 0; i < existingChild->getNumParents(); ++i)
            {
                osg::Group* parent = existingChild->getParent(i);
                if ( parent != newParent )
                {
                    parent->removeChild( existingChild );
                    parent->addChild( newParent );
                }
            }
        }
    }

    /** Remove a group from the middle of a scene graph */
    inline void removeGroup(osg::Group* group)
    {
        if (group)
        {
            osg::ref_ptr<osg::Group> g = group;
            while (g->getNumParents() > 0)
            {
                osg::Group* parent = group->getParent(group->getNumParents()-1);
                for (unsigned c = 0; c < group->getNumChildren(); ++c)
                {
                    parent->addChild(group->getChild(c));
                }
                parent->removeChild(group);
            }
        }
    }

    /**
     * Remove all a group's children.
     */
    inline void clearChildren( osg::Group* group )
    {
        if ( group )
            group->removeChildren( 0, group->getNumChildren() );
    }

    /**
     * OSG Group that keeps its children as observer_ptrs instead of ref_ptrs, and
     * removes them when they deref.
     */
    class OSGEARTH_EXPORT ObserverGroup : public osg::Group
    {
    public:
        ObserverGroup();
        virtual void traverse( osg::NodeVisitor& nv );
        std::set<osg::Node*> _orphans;
    };


    /**
     * Group that acts like a normal group but also notifies another
     * object when a change occurs.
     */
    template<typename T>
    class NotifierGroup : public osg::Group
    {
    public:
        NotifierGroup(T* listener) : _listener(listener) { }

        virtual bool addChild( osg::Node* child ) {
            bool ok = osg::Group::addChild(child);
            if ( ok && _listener.valid() ) _listener->onGroupChanged(this);
            return ok;
        }
        virtual bool insertChild( unsigned index, osg::Node* child ) {
            bool ok = osg::Group::insertChild(index, child);
            if ( ok && _listener.valid() ) _listener->onGroupChanged(this);
            return ok;
        }
        virtual bool removeChild( osg::Node* child ) {
            bool ok = osg::Group::removeChild( child );
            if ( ok && _listener.valid() ) _listener->onGroupChanged(this);
            return ok;
        }
        virtual bool replaceChild( osg::Node* origChild, osg::Node* newChild ) {
            bool ok = osg::Group::replaceChild(origChild, newChild);
            if ( ok && _listener.valid() ) _listener->onGroupChanged(this);
            return ok;
        }

    protected:
        virtual ~NotifierGroup() { }
        osg::observer_ptr<T> _listener;
    };


    /**
     * Adjusts a node's update traversal count by a delta.
     * Only safe to call from the UPDATE thread
     */
#define ADJUST_UPDATE_TRAV_COUNT( NODE, DELTA ) \
    { \
        unsigned oldCount = NODE ->getNumChildrenRequiringUpdateTraversal(); \
        unsigned newCount = (unsigned)(((int)oldCount)+(DELTA)); \
        if ( ((DELTA) < 0 && newCount < oldCount) || ((DELTA) >= 0 && newCount >= oldCount) ) { \
            NODE ->setNumChildrenRequiringUpdateTraversal( newCount ); \
        } else { \
            OE_WARN << "**INTERNAL: ADJUST_UPDATE_TRAV_COUNT wrapped around" << std::endl; \
        } \
    }

    /**
     * Adjusts a node's event traversal count by a delta.
     * Only safe to call from the main/event/update threads
     */
#define ADJUST_EVENT_TRAV_COUNT( NODE, DELTA ) \
    { \
        unsigned oldCount = NODE ->getNumChildrenRequiringEventTraversal(); \
        unsigned newCount = (unsigned)(((int)oldCount)+(DELTA)); \
        if ( ((DELTA) < 0 && newCount < oldCount) || ((DELTA) >= 0 && newCount >= oldCount) ) { \
            NODE ->setNumChildrenRequiringEventTraversal( newCount ); \
        } else { \
            OE_WARN << "**INTERNAL: ADJUST_EVENT_TRAV_COUNT wrapped around" << std::endl; \
        } \
    }


     /**
      * Enables auto unloading on any PagedNode2 in the scene graph
      * This is useful if you've used the LoadDataVisitor to
      * preload an area and you no longer care about it.
      */
    struct OSGEARTH_EXPORT EnableAutoUnloadVisitor : public osg::NodeVisitor
    {
        EnableAutoUnloadVisitor();

        void apply(osg::Node& node);
    };

    /**
      * Preloads data within a list of bounding spheres so that the data is available without
      * actually viewing it first.
      * Once the visitor has been sent down a scene graph, you can check the isFullyLoaded
      * property to determine if all of the data in that area has been loaded.  If isFullyLoaded is false
      * then send the visitor down again until isFullyLoaded returns true.
      */
    struct OSGEARTH_EXPORT LoadDataVisitor : public osg::NodeVisitor
    {
        LoadDataVisitor();
        bool getLoadHighestResolutionOnly() const;
        void setLoadHighestResolutionOnly(bool value);

        //! Whether all potential data in the areas are loaded
        bool isFullyLoaded() const;

        //! Resets isFullyLoaded so you can reuse the same visitor multiple times.
        void reset();

        bool intersects(osg::Node& node);

        //! A list of bounding spheres in world coordinates to intersect the scene graph against.
        std::vector<osg::BoundingSphered>& getAreasToLoad();

        void apply(osg::Node& node);

        void apply(LoadableNode& loadableNode);

        void apply(osg::Transform& transform);

        /**
        * Most async nodes will require an update traversal to be called.  If you calling this visitor outside
        * of a normal frame loop you will need to call manualUpdate after you run the visitor in order to update the scene graph properly
        */
        void manualUpdate();

        inline void pushMatrix(osg::Matrix& matrix) { _matrixStack.push_back(matrix); }
        inline void popMatrix() { _matrixStack.pop_back(); }

        typedef std::vector<osg::Matrix> MatrixStack;
        MatrixStack _matrixStack;
        bool _fullyLoaded = true;

        std::set < osg::ref_ptr < PagingManager> > _pagingManagers;

        std::vector< osg::BoundingSphered > _areasToLoad;

        bool _loadHighestResolutionOnly = false;
    };

    /**
    * Loads async data within the given areas and sets the nodes to not expire.
    */
    extern OSGEARTH_EXPORT void loadData(osg::Node* node, std::vector<osg::BoundingSphered>& areasToLoad);


    inline Result<osgUtil::LineSegmentIntersector::Intersection> intersectMouse(osg::View* view, float x, float y,osg::Node* graph)
    {
        osgViewer::View* view2 = dynamic_cast<osgViewer::View*>(view);
        if (!view2 || !graph)
            return {};

        float local_x, local_y = 0.0;
        const osg::Camera* camera = view2->getCameraContainingPosition(x, y, local_x, local_y);
        if (!camera)
            camera = view2->getCamera();

        // Build a matrix that transforms from the terrain/world space
        // to either clip or window space, depending on whether we have
        // a viewport. Is it even possible to not have a viewport? -gw
        osg::Matrixd matrix;

        // compensate for any transforms applied between terrain and camera:
        osg::Matrix terrainRefFrame = osg::computeLocalToWorld(graph->getParentalNodePaths()[0]);
        matrix.postMult(terrainRefFrame);

        osg::Matrixd proj = camera->getProjectionMatrix();
        if (proj(3, 3) == 0) //persp
        {
            // persp camera: adjust the near plane to 1.0
            double V, A, N, F;
            ProjectionMatrix::getPerspective(camera->getProjectionMatrix(), V, A, N, F);
            ProjectionMatrix::setPerspective(proj, V, A, 1.0, F);
            proj.makePerspective(V, A, 1.0, F);
        }

        matrix.postMult(camera->getViewMatrix());
        matrix.postMult(proj);

        double zNear = -1.0;
        double zFar = 1.0;
        if (camera->getViewport())
        {
            matrix.postMult(camera->getViewport()->computeWindowMatrix());
            zNear = 0.0, zFar = 1.0;
        }

        osg::Matrixd inverse;
        inverse.invert(matrix);

        osg::Vec3d startVertex = osg::Vec3d(local_x, local_y, zNear) * inverse;
        osg::Vec3d endVertex = osg::Vec3d(local_x, local_y, zFar) * inverse;

        auto picker = new osgUtil::LineSegmentIntersector(
            osgUtil::Intersector::MODEL,
            startVertex,
            endVertex);

        // Limit it to one intersection; we only care about the nearest.
        picker->setIntersectionLimit(osgUtil::Intersector::LIMIT_NEAREST);

        osgUtil::IntersectionVisitor iv(picker);
        graph->accept(iv);

        if (picker->containsIntersections())
        {
            return(*picker->getIntersections().begin());
        }
        else return {};
    }
} }
