/* osgEarth
 * Copyright 2008-2011 Pelican Mapping
 * MIT License
 */

#ifndef OSGEARTH_SHADER_GENERATOR_H
#define OSGEARTH_SHADER_GENERATOR_H 1

#include <osgEarth/Common>
#include <osgEarth/StateSetCache>
#include <osgEarth/VirtualProgram>
#include <osg/NodeVisitor>
#include <osg/State>
#include <osg/Version>
#include <osg/Drawable>
#include <sstream>
#include <set>

// forward declarations
namespace osg
{
    class TexEnv;
    class TexGen;
    class TexMat;
    class Texture1D;
    class Texture2D;
    class Texture3D;
    class TextureRectangle;
    class Texture2DArray;
    class Texture2DMultisample;
    class TextureCubeMap;
    class PointSprite;
    class LightSource;
}

namespace osgSim
{
    class LightPointNode;
}

namespace osgEarth
{
    /**
     * Traverses a scene graph and generates VirtualProgram attributes to
     * render the geometry using GLSL shaders.
     *
     * You can use this class directly, but the osgEarth Registry holds
     * a system-wide implementation that the user can replace. So the best
     * way to use this class is:
     *
     *   osgEarth::Registry::shaderGenerator().run(graph);
     *
     * After generating shaders, the scene graph will have MANY additional
     * StateSets. For performance reasons you should run a StateSet sharing
     * pass afterwards. You can do this by running the StateSetCache 
     * optimization function:
     *
     *   osgEarth::StateSetCache::optimize(graph)
     *
     * Or you can pass a StateSetCache instance into the ShaderGenerator::run()
     * method and it will perform state sharing internally.
     *
     * Implementation Notes:
     *
     * ShaderGenerator WILL NOT modify existing StateSets. Instead, when 
     * a state change is necessary (to inject uniforms or virtual programs)
     * it will clone the existing StateSet and replace it with a
     * modified version. We do this to avoid altering StateSets that might
     * be shared or in the live scene graph.
     */
    class OSGEARTH_EXPORT ShaderGenerator : public osg::NodeVisitor
    {
    public:
        using osg::NodeVisitor::apply;

        /** Constructs a new shader generator */
        ShaderGenerator();

        /** Copy constructor */
        ShaderGenerator(const ShaderGenerator& rhs, const osg::CopyOp& copy);

    public:
        /**
         * returns a copy of the shaderGenerator.
         * need to be reimplemented by any child that wish to be used as the 
         * default implementation in Registry via Registry::setShaderGenerator().
         */
        virtual ShaderGenerator* getCopy(const osg::CopyOp& copy) const { return new ShaderGenerator(*this, copy); };

    public: // ShaderGeneratorInterface

        /**
         * Runs the shader generator on a graph.
         * @param graph Graph for which to generate shader components.
         * @param name Name to give to the top level Virtual Program.
         * @param cache StateSet cache to use for sharing state when finished.
         */
        void run(
            osg::Node* graph,
            const std::string& name = std::string(),
            StateSetCache* cache = nullptr);

        //! Run the shader generator on a single state set.
        osg::ref_ptr<osg::StateSet> run(osg::StateSet* stateSet);

    public: // statics

        /**
         * Marks a node with a hint that the shader generator should ignore it in
         * the future.
         */
        static void setIgnoreHint(osg::Object* object, bool ignore);

        /**
         * Whether an object has been marked for ignore 
         */
        static bool ignore(const osg::Object* object);

    public:

        //! Whether to remove unsupported fixed-function pipeline attributes 
        //! when FFP is not available in OSG. Default is true.
        //! @param value True to remove unsupported FFP attributes, false to leave them in place.
        void setRemoveUnsupportedFFPAttributes(bool value) { _removeUnsupportedFFPAttributes = value; }

        //! Whether to remove unsupported fixed-function pipeline attributes
        //! when FFP is not available in OSG.
        //! @return True to remove unsupported FFP attributes, false to leave them in place.
        bool getRemoveUnsupportedFFPAttributes() const { return _removeUnsupportedFFPAttributes; }

        /**
         * Whether to automatically duplicate (by cloning) subgraphs with
         * more than one parent during the traversal process. Since a
         * shader program is unique based on its traversed NodePath, graphs
         * with multi-parenting can run into problems.
         * Default is false.
         * @untested
         */
        void setDuplicateSharedSubgraphs(bool value);
        bool getDuplicateSharedSubgraphs() const { return _duplicateSharedSubgraphs; }

    public:
        /**
         * User callback that lets you selectly reject shader generation for
         * specific state attributes.
         */
        struct OSGEARTH_EXPORT AcceptCallback : public osg::Referenced
        {
            /** Return true to generate shader code for the SA; false to ignore and skip it */
            virtual bool accept(const osg::StateAttribute* sa) const =0;
            virtual ~AcceptCallback() { }
        };

        /**
         * Adds an acceptor callback that the generator will use to decide
         * whether to ignore certain state attributes.
         */
        void addAcceptCallback(AcceptCallback* cb);


    public:

        /** dtor. */
        virtual ~ShaderGenerator() { }


    public: // osg::NodeVisitor

        virtual void apply( osg::Node& );
        virtual void apply( osg::Group& );
        virtual void apply( osg::Geode& );
        virtual void apply( osg::PagedLOD& );
        virtual void apply( osg::ProxyNode& );
        virtual void apply( osg::ClipNode& );
        virtual void apply( osg::Drawable& );

    public: // types not in osg::NodeVisitor

        virtual void applyNonCoreNodeIfNecessary( osg::Node& );
        
        virtual void apply( osgSim::LightPointNode& );


    protected: // high-level entry points:

        virtual void optimizeStateSharing(osg::Node* graph, StateSetCache* cache);

        virtual void apply( osg::Drawable* );

        virtual bool processGeometry(const osg::Geometry* geom, const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);

        virtual bool processText(const osg::StateSet* stateSet, osg::ref_ptr<osg::StateSet>& replacement);



    protected: // overridable texture handlers:

        struct OSGEARTH_EXPORT GenBuffers
        {
            std::stringstream _modelHead, _modelBody;
            std::stringstream _viewHead, _viewBody;
            std::stringstream _fragHead, _fragBody;
            osg::StateSet*    _stateSet;

            GenBuffers();
        };

        virtual bool apply(osg::Texture* tex, osg::TexGen* texgen, osg::TexEnv* texenv, osg::TexMat* texmat, osg::PointSprite* sprite, int unit, GenBuffers& buf);

        virtual bool apply(osg::TexEnv* texenv, int unit, GenBuffers& buf);

        virtual bool apply(osg::TexGen* texgen, int unit, GenBuffers& buf);

        virtual bool apply(osg::TexMat* texmat, int unit, GenBuffers& buf);

        virtual bool apply(osg::Texture1D* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::Texture2D* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::Texture3D* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::TextureRectangle* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::Texture2DArray* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::TextureCubeMap* tex, int unit, GenBuffers& buf);

        virtual bool apply(osg::PointSprite* sprite, int unit, GenBuffers& buf);
        
        virtual bool apply(osg::StateSet::AttributeList& attrs, GenBuffers& buf);

        virtual bool apply(osg::StateAttribute* attr, GenBuffers& buf);
        
        // This method will check whether setDuplicateSharedNodes has been set,
        // and if so, it will clone a node that has multiple parents such that
        // each parent has a complete separate copy of the child.
        virtual void duplicateSharedNode(osg::Node& child);

        // disables (or removes) attributes that won't work in teh current configuration
        virtual void disableUnsupportedAttributes(osg::StateSet* stateset);

    protected:

        osg::ref_ptr<osg::State> _state;

        std::string _name;
        bool _active = true;
        bool _duplicateSharedSubgraphs = false;
        bool _removeUnsupportedFFPAttributes = true;

        typedef std::vector<osg::ref_ptr<AcceptCallback> > AcceptCallbackVector;
        AcceptCallbackVector _acceptCallbacks;

        std::set<osg::Drawable*> _drawablesVisited;

        bool accept(const osg::StateAttribute* sa) const;
    };

    
    /** Proxy interface for a ShaderGenerator - used by the registry. */
    class OSGEARTH_EXPORT ShaderGeneratorProxy //header only
    {
    public:
        void run(osg::Node* graph, const std::string& name, StateSetCache* cache) {
            _instance->run(graph, name, cache);
        }
        void run(osg::Node* graph) {
            run(graph, "ShaderGenerator", 0L);
        }
        void run(osg::Node* graph, StateSetCache* cache) {
            run(graph, "ShaderGenerator", cache);
        }
        void run(osg::Node* graph, const std::string& name) {
            run(graph, name, 0L);
        }

    public:
        ShaderGeneratorProxy(const ShaderGenerator* temp)
            : _instance( temp->getCopy( osg::CopyOp::SHALLOW_COPY ) ) { }

    private:
        osg::ref_ptr<ShaderGenerator> _instance;
    };

}

#endif // OSGEARTH_SHADER_GENERATOR_H
