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

#include <osgEarth/Common>
#include <osg/Camera>
#include <osg/Texture2DArray>
#include <osg/Matrix>
#include <osg/Uniform>
#include <osg/Light>

namespace osgEarth { namespace Util
{
    /**
     * Static Shadowing methods
     */
    struct OSGEARTH_EXPORT Shadowing
    {
        //! Marks a camera as a shadow map camera
        static void setIsShadowCamera(osg::Camera* camera);

        //! Whether a camera is marked as a shadow map camera
        static bool isShadowCamera(const osg::Camera* camera);
    };

    /**
     * Group that casts shadows on its subgraph.
     *
     * NOTE!! This object is not multi-camera aware yet.
     */
    class OSGEARTH_EXPORT ShadowCaster : public osg::Group
    {
    public:
        //! Construct a shadow caster
        ShadowCaster();

        /** Whether shadows are supported (requires GLSL) */
        bool supported() const { return _supported; }

        //! Whether to enable shadowing
        void setEnabled(bool value) { _enabled = value; }
        bool getEnabled() const { return _enabled; }

        /**
         * The light that will cast shadows.
         * NOTE: Only works for point lights, like the sun.
         */
        void setLight(osg::Light* light) { _light = light; }
        osg::Light* getLight() { return _light.get(); }

        /**
         * Group of geometry that should cast shadows on this node's children.
         * You must add geometry here in order to cast shadows. The geometry
         * you add here will only be used to generate shadows - it must still
         * exist elsewhere in the scene graph for rendering.
         */
        osg::Group* getShadowCastingGroup() { return _castingGroup.get(); }

        /**
         * Sets the traversal mask to use when collecting shadow-casting
         * geometry. Default is 0xFFFFFFFF (everything)
         */
        void setTraversalMask(unsigned mask) { _traversalMask = mask; }
        unsigned getTraversalMask() const { return _traversalMask; }

        /**
         * Slice ranges. Each slice (the space between each value in the list)
         * represents a single shadow map in the Cascading Shadow Maps 
         * implementation.
         */
        const std::vector<float>& getRanges() { return _ranges; }
        void setRanges(const std::vector<float>& ranges);

        /**
         * The GPU texture image unit that will store the shadow map while
         * rendering the subgraph.
         */
        int getTextureImageUnit() const { return _texImageUnit; }
        void setTextureImageUnit(int unit);

        /**
         * The size (in both dimensions) of the shadow depth texture. Bigger
         * is sharper. Default is 1024. The total texture size used will be
         * size * # of range slices * 3 bytes.
         */
        unsigned getTextureSize() const { return _size; }
        void setTextureSize(unsigned size);

        /**
         * The ambient color level of the shadow. 0.0 = black. Default is 0.4
         */
        void setShadowColor(float value);
        float getShadowColor() const { return _color; }

        /**
         * The blurring factor to apply to shadow PCF sampling. Using a blurring
         * factor will incur a GPU performance penalty. Default is 0.0.
         * Decent values are [0.0 -> 0.001].
         */
        void setBlurFactor(float value);
        float getBlurFactor() const { return _blurFactor; }


    public: // osg::Node

        virtual void traverse(osg::NodeVisitor& nv);

    public:

        virtual void resizeGLObjectBuffers(unsigned maxSize);

        virtual void releaseGLObjects(osg::State* state) const;

    protected:
        virtual ~ShadowCaster() { }

        void reinitialize();

        bool                                    _supported;
        bool                                    _enabled;
        osg::ref_ptr<osg::Group>                _castingGroup;
        unsigned                                _size;
        float                                   _blurFactor;
        float                                   _color;
        osg::ref_ptr<osg::Light>                _light;
        osg::ref_ptr<osg::Texture2DArray>       _shadowmap;
        osg::ref_ptr<osg::StateSet>             _rttStateSet;
        std::vector<float>                      _ranges;
        std::vector<osg::ref_ptr<osg::Camera> > _rttCameras;
        osg::Matrix                             _prevProjMatrix;
        unsigned                                _traversalMask;

        int                         _texImageUnit;
        osg::ref_ptr<osg::StateSet> _renderStateSet;
        osg::ref_ptr<osg::Uniform>  _shadowMapTexGenUniform;
        osg::ref_ptr<osg::Uniform>  _shadowBlurUniform;
        osg::ref_ptr<osg::Uniform>  _shadowColorUniform;
        osg::ref_ptr<osg::Uniform>  _shadowToPrimaryMatrix;
        osg::ref_ptr<osg::Uniform>  _primaryProjectionMatrix;
        osg::ref_ptr<osg::Uniform>  _primaryViewportUniform;
    };

} }

#endif // OSGEARTH_UTIL_SHADOWING_H
