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

#include <osgEarth/Common>
#include <osgEarth/Containers>
#include <osgEarth/DrapingCullSet>
#include <osg/Group>

namespace osg {
    class Camera;
    class Plane;
}
namespace osgUtil {
    class CullVisitor;
}
namespace osgEarth {
    class Ellipsoid;
    class SpatialReference;
    class TerrainResources;
}

namespace osgEarth { namespace Contrib
{
    using namespace osgEarth;
    using namespace osgEarth::Util;

    /**
     * Projects geometry from a separate scene graph onto the terrain.
     */
    class OSGEARTH_EXPORT CascadeDrapingDecorator : public osg::Group
    {
    public:
        //! Constructs a decorator
        CascadeDrapingDecorator(const SpatialReference* srs, TerrainResources*);

        //! Sets the maximum number of texture cascases to use (default=4, max=4)
        void setMaxNumCascades(unsigned value);

        //! Number of multisamples to use to improve image quality (default=4)
        void setNumMultiSamples(unsigned value);

        //! Texture size for draping target (default=1024)
        void setTextureSize(unsigned value);

        //! Whether to mipmap projected textures (default=false)
        void setUseMipMaps(bool value);

        //! Whether to use projection matrix fitting
        void setUseProjectionFitting(bool value);
        bool getUseProjectionFitting() const { return _useProjectionFitting; }

        //! Sets the minimum n/f ratio for projection fitting
        void setMinimumNearFarRatio(double value);

        //! Debugging method (used by osgearth_overlayviewer to visualize cascades)
        osg::Node* getDump();

        //! Underlying object to track draped scene graphs
        std::shared_ptr<DrapingManager>& getDrapingManager() { return _manager; }

    public: // osg::Node

        void traverse( osg::NodeVisitor& nv );

    protected:

        virtual ~CascadeDrapingDecorator() { }

        // Data for one RTT cascade
        struct Cascade
        {
            double _minClipY, _maxClipY;
            //double _minX, _minY, _maxX, _maxY;
            osg::BoundingBoxd _box;
            double _widthNDC, _heightNDC;

            void expandToInclude(const osg::Vec3d&);
            void computeProjection(const osg::Matrix&, const osg::Matrix&, const Ellipsoid&, const osg::Plane&, double, const osg::BoundingBoxd&);
            void computeClipCoverage(const osg::Matrix&, const osg::Matrix&);
            void makeProj(double dp);

            osg::ref_ptr<osg::Camera> _rtt;
            osg::Matrix _rttProj;
            osg::ref_ptr<osg::StateSet> _stateSet;
        };

        // RTT configuration for a single master camera.
        // (i.e. there will be one of these for each Camera that traverses DrapingDecorator)
        struct CameraLocal : public osg::Observer
        {
            Cascade _cascades[8];
            unsigned _numCascades;
            unsigned _maxCascades;
            osg::ref_ptr<osg::StateSet> _rttSS;
            osg::ref_ptr<osg::StateSet> _terrainSS;
            osg::Matrix _projMatrixLastFrame;

            void initialize(osg::Camera* camera, CascadeDrapingDecorator&);
            void traverse(osgUtil::CullVisitor*, CascadeDrapingDecorator&, const osg::BoundingSphere&);
            void clear();
            void constrainRttBoxToFrustum(const osg::Matrix&, const osg::Matrix&, const Ellipsoid&, bool, osg::BoundingBoxd& rttBox);
            void constrainRttBoxToBounds(const osg::Matrix&, const osg::BoundingSphere&, osg::BoundingBoxd& rttBox);
            bool intersectTerrain(CascadeDrapingDecorator& dec, const osg::Vec3d& startWorld, const osg::Vec3d& endWorld, osg::Vec3d& output);

            void dump(const osg::Camera* cam, CascadeDrapingDecorator&);

            osg::observer_ptr<osg::Camera> _token;            
            void objectDeleted(void*);
            ~CameraLocal();
        };
        
        int _unit;
        unsigned _multisamples;
        unsigned _maxCascades;
        unsigned _texSize;
        bool _mipmapping;
        double _maxHorizonDistance;
        PerObjectFastMap<const osg::Camera*, CameraLocal> _data;
        osg::ref_ptr<osg::Group> _dump;
        bool _debug;
        osg::ref_ptr<const SpatialReference> _srs;
        bool _constrainMaxYToFrustum;
        bool _constrainRttBoxToDrapingSetBounds;
        bool _useProjectionFitting;
        double _minNearFarRatio;

        // tracks drapable objects in the scene graph
        mutable std::shared_ptr<DrapingManager> _manager;

        osg::observer_ptr<TerrainResources> _resources;
        void reserveTextureImageUnit();
    };

} }

#endif //OSGEARTH_CASCADE_DRAPING_DECORATOR
