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

#include <osgEarth/Common>
#include <osgEarth/Profile>
#include <osgEarth/Progress>
#include <osgEarth/Map>
#include <osgEarth/Callbacks>
#include <osgEarth/SceneGraphCallback>
#include <osgEarth/CullingUtils>
#include <osgEarth/PagedNode>
#include <osg/Group>

namespace osgEarth { namespace Util
{
    using namespace osgEarth;

    class OSGEARTH_EXPORT SimplePager : public osg::Group, public PagedNodeManager
    {
    public:
        SimplePager(const osgEarth::Map* map, const osgEarth::Profile* dataProfile);

        //! Initializes the graph. If you override this, be sure to call
        //! buildRootNode and add that as a child of whatever you create.
        virtual void build();

        //! Whether this pager uses additive lods.
        bool getAdditive() const { return _additive; }
        void setAdditive(bool additive) { _additive = additive; }

        //! Tell the pager to stop paging and cancel pending requests
        void setDone();

        unsigned int getMinLevel() const { return _minLevel; }
        void setMinLevel(unsigned int minLevel) { _minLevel = minLevel; }

        unsigned int getMaxLevel() const { return _maxLevel; }
        void setMaxLevel(unsigned int maxLevel) { _maxLevel = maxLevel; }

        float getRangeFactor() const { return _rangeFactor; }
        void setRangeFactor(float value);

        float getMaxRange() const { return _maxRange; }
        void setMaxRange(float value);

        float getMaxPixels() const { return _maxPixels; }
        void setMaxPixels(float value);

        float getMinPixels() const { return _minPixels; }
        void setMinPixels(float value);

        void setLODMethod(const LODMethod& value);
        LODMethod getLODMethod() const { return _lodMethod; }

        float getPriorityScale() const { return _priorityScale; }
        void setPriorityScale(float value) { _priorityScale = value; }

        float getPriorityOffset() const { return _priorityOffset; }
        void setPriorityOffset(float value) { _priorityOffset = value; }

        void setEnableCancelation(bool value);
        bool getEnableCancelation() const;

        void setTimeoutSeconds(double value);
        float getTimeoutSeconds() const { return _timeoutSeconds; }

        //! Whether to install cluster culling for geographic nodes
        void setClusterCullingEnabled(bool value);
        bool getClusterCullingEnabled() const;

        //! Whether to use the bounds of loaded data to compute the paging bounds of that
        //! data's children. Default = false. (By default, it will use the tile key bounds
        //! and the built-in ElevationRanges to compute paging bounds.)
        //! This is optimal when you know every level will be populated by geometry that
        //! fits approximately inside its parent's bounds (e.g., terrain tiles).
        void setUsePayloadBoundsForChildren(bool value);
        bool getUsePayloadBoundsForChildren() const;

        using CreateNodeFunction = std::function<osg::ref_ptr<osg::Node>(const TileKey& key, ProgressCallback* progress)>;
        void setCreateNodeFunction(CreateNodeFunction func) { _createNodeFunction = func; }

        using CreatePagedNodeFunction = std::function<PagedNode2*(const TileKey&)>;
        void setCreatePagedNodeFunction(CreatePagedNodeFunction func) { _createPagedNodeFunction = func; }

        //! Scene graph callbacks for notification of changes. Call before calling build().
        void setSceneGraphCallbacks(SceneGraphCallbacks* value) { _sceneGraphCallbacks = value; }
        SceneGraphCallbacks* getSceneGraphCallbacks() const { return _sceneGraphCallbacks.get(); }

        //! Gets the profile for the SimplePager.
        const osgEarth::Profile* getProfile() const;

        //! Creates a payload for the given TileKey.
        //! Doesn't do anything with paging, just gets the raw data.
        //! Subclasses override this method to provide their data for TileKeys, OR you can
        //! call setCreateNodeFunction and createNode will call that.
        virtual osg::ref_ptr<osg::Node> createNode(const TileKey& key, ProgressCallback* progress);

        //! Callback fired when a node is created.
        Callback<void(const TileKey&, osg::Node*)> onCreateNode;

    public:

        //! (INTERNAL) Creates a group containing 4 PagedNodes,
        //! one for each child of the given TileKey.
        osg::ref_ptr<osg::Node> createPagedChildrenOf(const TileKey& parent, ProgressCallback* progress);

        void traverse(osg::NodeVisitor&) override;

    protected:

        //! Gets the bounding sphere for a given TileKey.
        osg::BoundingSphered getBounds(const TileKey& key) const;

        //! The root node is a special node. If you override this method,
        //! be sure to all this to include it in the scene graph.
        osg::ref_ptr<osg::Node> buildRootNode();

        //! Creates a paging data for the given TileKey
        osg::ref_ptr<osg::Node> createChildNode(const TileKey& key, ProgressCallback* progress);

        bool _additive = false;
        LODMethod _lodMethod = LODMethod::CAMERA_DISTANCE;
        double _rangeFactor = 6.0; // ratio of range to tile size
        float _maxRange = FLT_MAX;
        float _minPixels = 256.0f;
        float _maxPixels = FLT_MAX;
        unsigned int _minLevel = 0;
        unsigned int _maxLevel = 20;
        float _priorityScale = 1.0f;
        float _priorityOffset = 0.0f;
        bool _canCancel = true;
        bool _done = false;
        bool _clusterCullingEnabled = true;
        bool _usePayloadBoundsForChildren = false;
        double _timeoutSeconds = 0.0;

        osg::observer_ptr<const osgEarth::Map> _map;
        osg::ref_ptr<const Profile> _profile;
        osg::ref_ptr<const Profile> _mapProfile;
        osg::ref_ptr<SceneGraphCallbacks> _sceneGraphCallbacks;
        CreateNodeFunction _createNodeFunction;
        CreatePagedNodeFunction _createPagedNodeFunction;

        void fire_onCreateNode(const TileKey& key, osg::Node* node);
    };

} } // namespace osgEarth::Util
