/* -*-c++-*- */
/* osgEarth - Geospatial SDK for OpenSceneGraph
* Copyright 2020 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_NODE_UTILS_H
#define OSGEARTH_NODE_UTILS_H 1

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

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    /**
     * General purpose operation for doing something to a node.
     */
    struct NodeOperation : public osg::Referenced
    {
        virtual void operator()( osg::Node* node ) =0;
    };

    typedef std::vector< osg::ref_ptr<NodeOperation> > NodeOperationVector;

    struct RefNodeOperationVector : public osg::Referenced, public NodeOperationVector
    {
    public:
       Threading::ReadWriteMutex& mutex() const { return _mutex; }
    private:
       mutable Threading::ReadWriteMutex _mutex;
    };

    /**
     * A PagedLOD that will fire node operation callbacks when it merges
     * new nodes into the graph.
     */
    class OSGEARTH_EXPORT PagedLODWithNodeOperations : public osg::PagedLOD
    {
    public:
        PagedLODWithNodeOperations( RefNodeOperationVector* postMergeOps );

    public: // osg::Group

        virtual bool addChild( osg::Node* child );
        virtual bool insertChild( unsigned index, Node* child );
        virtual bool replaceChild( Node* origChild, Node* newChild );

    protected:
        osg::observer_ptr<RefNodeOperationVector> _postMergeOps;

        void runPostMerge( osg::Node* node );
    };

    /**
     * Visitor that looks for empty group nodes and removes them from the
     * scene graph. (Note, if the entry node is an empty group, it will
     * not be affected.)
     */
    class OSGEARTH_EXPORT RemoveEmptyGroupsVisitor : public osg::NodeVisitor
    {
    public:
        static void run( osg::Node* entry ) {
            if ( entry ) {
                RemoveEmptyGroupsVisitor v;
                entry->accept( v );
            }
        }

    public:
        RemoveEmptyGroupsVisitor();
        void apply( osg::Group& group ); //override
    };

    /**
     * Visitor that counts the number of point, line, and polygon primitive sets
     * in a scene graph.
     */
    class OSGEARTH_EXPORT PrimitiveSetTypeCounter : public osg::NodeVisitor
    {
    public:
        PrimitiveSetTypeCounter();

        /** dtor */
        virtual ~PrimitiveSetTypeCounter() { }

        void apply(class osg::Drawable&);

        unsigned _point;
        unsigned _line;
        unsigned _polygon;
    };

    /**
     * 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);
        }
    };

    /**
     * Functor (for use with ViewVisitor) that notifies a view that it needs to
     * redraw the scene because something has changed
     * Usage: ViewVisitor<RequestRedraw> vis; node->accept(vis);
     */
    struct RequestRedraw
    {
        void operator()(osg::View* view) {
            osgGA::GUIActionAdapter* aa = dynamic_cast<osgGA::GUIActionAdapter*>(view);
            if ( aa ) aa->requestRedraw();
        }
    };

    /**
     * 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;
    };

    /**
     * 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;
    }

    /**
     * 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;
    }

    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_INFO << "**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_INFO << "**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);

} }

#endif // OSGEARTH_CACHING_H
