/* osgEarth
* Copyright 2025 Pelican Mapping
* MIT License
*/
#ifndef OSGEARTH_OVERLAY_DECORATOR
#define OSGEARTH_OVERLAY_DECORATOR

#include <osgEarth/Common>
#include <osgEarth/Threading>
#include <osgEarth/TerrainEngineNode>
#include <osg/Camera>
#include <osg/CoordinateSystemNode>
#include <osg/TexGenNode>
#include <osg/Uniform>
#include <osgUtil/CullVisitor>
#include <osgShadow/ConvexPolyhedron>

namespace osgEarth
{
    class TerrainEngineNode;
}

namespace osgEarth { namespace Util
{
    class OverlayDecorator;
    class OverlayTechnique;

    /**
     * Overlays geometry onto the terrain using various techniques.
     */
    class OSGEARTH_EXPORT OverlayDecorator : public osg::Group
    {
    public:
        OverlayDecorator();

        /**
         * Adds a new overlay technique to the decorator.
         */
        void addTechnique( OverlayTechnique* technique );

        /**
         * Gets the group for nodes that are to be overlaid with
         * the specified Technique.
         */
        template<typename T>
        osg::Group* getGroup() {
            for(unsigned i=0; i<_techniques.size(); ++i ) {
                if ( dynamic_cast<T*>(_techniques[i].get()) )
                    return _overlayGroups[i].get();
            }
            return 0L;
        }

        /**
         * The traversal mask to use when traversing the overlay groups.
         */
        void setOverlayGraphTraversalMask( unsigned mask );

        /**
         * Sets the maximum horizon distance to enforce when calculating
         * the draping extents. Constraining the distance will mean that you won't
         * see draped geometry past a certain point, but you will potentially get
         * higher quality for the geometry you do see (with fewer aliasing artifacts).
         */
        double getMaxHorizonDistance() const;
        void setMaxHorizonDistance( double horizonDistance );

        /**
         * Maximum height (above and below) the terrain MSL/ellipsoid at which
         * to drape geometry. Geometry above the max or below the -max will not
         * appear. This is only important to maintain Z buffering precision in the
         * draped geometry; however, it is usually a better idea to draw draped
         * geometry without blending or z-testing anyway.
         * Default is 500,000 meters.
         */
        void setMaximumHeight(double value) { _maxHeight = value; }
        double getMaximumHeight() const { return _maxHeight; }

        /**
         * Initializes the decorator with a terrain engine
         */
        void setTerrainEngine(TerrainEngineNode*);

    public: // osg::Node
        void traverse( osg::NodeVisitor& nv );

    public: // osg::Object
        void resizeGLObjectBuffers(unsigned maxSize);
        void releaseGLObjects(osg::State* state) const;

    public: // NotiferGroup listener interface
        void onGroupChanged(osg::Group* group);

    protected:
        virtual ~OverlayDecorator() { }

    public:

        // RTT camera parameters for an overlay technique. There will be
        // one of these per technique, per view.
        struct TechRTTParams
        {
            osg::Camera*                  _mainCamera;       // Camera to which this per-view data is attached
            osg::ref_ptr<osg::Camera>     _rttCamera;        // RTT camera.
            osg::Matrixd                  _rttViewMatrix;    // View matrix of RTT camera
            osg::Matrixd                  _rttProjMatrix;    // Projection matrix of RTT camera
            osg::Group*                   _group;            // contains the overlay graphics
            osg::StateSet*                _terrainStateSet;  // same object as in PerViewData (shared across techniques)
            osg::ref_ptr<osg::Object>     _techniqueData;    // technique sets this if needed
            const double*                 _horizonDistance;  // points to the PVD horizon distance
            TerrainResources*             _terrainResources; // for accessing image units
            osg::Vec3d                    _eyeWorld;         // eyepoint in world coords
            osgShadow::ConvexPolyhedron   _visibleFrustumPH; // polyhedron representing the frustum
            osg::ref_ptr<osg::Uniform>    _rttToPrimaryMatrixUniform; // xform from RTT cam to primary cam
        };

        // One of these per view (camera). The terrain state set
        // if Shared almong all the techniques under a view.
        struct PerViewData
        {
            osg::Camera*                _camera;                // Camera to which this per-view data is attached
            std::vector<TechRTTParams>  _techParams;            // Pre-view data for each technique
            osg::ref_ptr<osg::StateSet> _sharedTerrainStateSet; // shared state set to apply to the terrain traversal
            double                      _sharedHorizonDistance; // horizon distnace (not used?)
            osg::Matrix                 _prevViewMatrix;        // last frame's view matrix
        };

    private:
        optional<int>                 _explicitTextureUnit;
        optional<int>                 _textureUnit;
        optional<int>                 _textureSize;
        bool                          _isGeocentric;
        unsigned                      _rttTraversalMask;
        double                        _maxHeight;
        double                        _maxHorizonDistance;
        unsigned                      _totalOverlayChildren;

        osg::ref_ptr<const SpatialReference>    _srs;
        Ellipsoid _ellipsoid;
        osg::observer_ptr<TerrainEngineNode>    _engine;

        // Mapping of each RTT camera params vector to a Camera (per view data)
        using PerViewDataMap = std::unordered_map<osg::Camera*, PerViewData>;

        PerViewDataMap _perViewData;
        mutable Threading::ReadWriteMutex _perViewDataMutex;

        typedef std::vector< osg::ref_ptr<OverlayTechnique> > Techniques;
        Techniques _techniques;
        Techniques _unsupportedTechniques;

        typedef std::vector< osg::ref_ptr<osg::Group> > Groups;
        Groups _overlayGroups;


    private:
        void cullTerrainAndCalculateRTTParams( osgUtil::CullVisitor* cv, PerViewData& pvd );
        void initializePerViewData( PerViewData&, osg::Camera* );
        PerViewData& getPerViewData( osg::Camera* key );

    public:
        // marker class for DrapeableNode support.
        struct InternalNodeVisitor : public osg::NodeVisitor {
            InternalNodeVisitor(const osg::NodeVisitor::TraversalMode& mode =osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) : 
                osg::NodeVisitor(mode) { }
        };

        //debugging:
        void requestDump() { _dumpRequested = true; }
        osg::Node* getDump() { osg::Node* r = _dump.release(); _dump = 0L; return r; }
        osg::ref_ptr<osg::Node> _dump;
        bool _dumpRequested;
    };


    /**
     * Abstract interface for an overlay technique implementation
     */
    class OverlayTechnique : public osg::Referenced
    {
    protected:
        bool _supported;
        OverlayTechnique() : _supported(true) { }
        virtual ~OverlayTechnique() { }

    public:
        virtual bool supported() { return _supported; }

        virtual bool constrainOrthoZ() const { return false; }

        virtual bool hasData(
            OverlayDecorator::TechRTTParams& params) const { return true; }

        virtual const osg::BoundingSphere& getBound(
            OverlayDecorator::TechRTTParams& params) const { return params._group->getBound(); }

        virtual bool optimizeToVisibleBound() const { return false; }

        virtual void onInstall( TerrainEngineNode* engine ) { }

        virtual void onUninstall( TerrainEngineNode* engine ) { }

        virtual void reestablish( TerrainEngineNode* engine ) { }

        virtual void preCullTerrain(
            OverlayDecorator::TechRTTParams& params,
            osgUtil::CullVisitor*            cv ) { }

        virtual void cullOverlayGroup(
            OverlayDecorator::TechRTTParams& params,
            osgUtil::CullVisitor*            cv ) { }

        virtual void resizeGLObjectBuffers(unsigned maxSize) { }
        virtual void releaseGLObjects(osg::State* state) const { }
    };

} } // namespace osgEarth

#endif //OSGEARTH_OVERLAY_DECORATOR
