/* -*-c++-*- */
/* osgEarth - Dynamic map generation toolkit for OpenSceneGraph
* Copyright 2008-2012 Pelican Mapping
* http://osgearth.org
*
* osgEarth is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
*/
#ifndef OSGEARTH_OVERLAY_DECORATOR
#define OSGEARTH_OVERLAY_DECORATOR

#include <osgEarth/Common>
#include <osgEarth/ThreadingUtils>
#include <osgEarth/TerrainEngineNode>
#include <osg/Camera>
#include <osg/CoordinateSystemNode>
#include <osg/TexGenNode>
#include <osg/Uniform>
#include <osgUtil/CullVisitor>

namespace osgEarth
{
    class TerrainEngineNode;

    /**
     * Projects an overlay scene graph onto the main model.
     *
     * The OverlayDecorator is automatically installed in the MapNode and is used
     * to display ModelLayer's whose "overlay" property is set to true.
     *
     * This class is similar in scope to osgSim::OverlayNode, but is optimized
     * for use with osgEarth and geocentric terrains.
     */
    class OSGEARTH_EXPORT OverlayDecorator : public TerrainDecorator
    {
    public:
        OverlayDecorator();

        /** dtor */
        virtual ~OverlayDecorator() { }

        /**
         * The scene graph to be sampled an overlaid on the terrain.
         */
        void setOverlayGraph( osg::Node* node );
        osg::Node* getOverlayGraph() const { return _overlayGraph.get(); }

        /**
         * Explicity sets the texture unit to use. Note! When you add this class
         * to a MapNode, it will automatically allocate a free texture unit; so you
         * usually do NOT need to call this method.
         */
        void setTextureUnit( int unit );
        int getTextureUnit() const { return *_textureUnit; }

        /**
         * The size (resolution in both directions) of the overlay texture. By
         * default, this defaults to 4096 or your hardware's maximum supported
         * texture size, whichever is less.
         */
        void setTextureSize( int texSize );
        int getTextureSize() const { return *_textureSize; }

        /**
         * Whether mipmapping is enabled on the projected overlay texture. Mapmapping
         * will improve the visual appearance, but will use more memory, and will affect
         * performance for overlays that are dynamic. Mipmapping can slow things down
         * a LOT on some GPUs (e.g. Intel GMA)
         */
        void setMipMapping( bool value );
        bool getMipMapping() const { return _mipmapping; }

        /**
         * Gets the maximum horizon distance
         */
        double getMaxHorizonDistance() const;

        /**
         * Sets the maximum horizon distance.  The computed far distance will be no longer than this value.
         * If you use overlay data that spans large distances it can become pixelated when you tilt your camera to look out over
         * the horizon b/c the view frustum can extend over long distances.  If you set this to a smaller value you can control
         * how far out you want to go and reduce the pixelation.  However, if you set this value to be too small, features at a distance may
         * be clipped out of the scene.
         */
        void setMaxHorizonDistance( double maxHorizonDistance );

        /**
         * Whether to enable blending on the RTT camera graph. Default = true. You might
         * want to disable this is you are draping polygons that cover a very large area
         * and curve around the globe; sometimes they suffer from tessellation artifacts
         * when draped.
         */
        void setOverlayBlending( bool value );
        bool getOverlayBlending() const { return _rttBlending; }


        /**
         * Whether to attach the RTT to camera to the stencil buffer.  Default = true.
         * Some older cards don't have very good support 
         */
        void setAttachStencil( bool value );
        bool getAttachStencil() const;

        void setOverlayGraphTraversalMask( unsigned mask );

    public: // TerrainDecorator
        virtual void onInstall( TerrainEngineNode* engine );
        virtual void onUninstall( TerrainEngineNode* engine );

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

    private:
        osg::ref_ptr<osg::Node>       _overlayGraph;
        osg::ref_ptr<osg::Uniform>    _warpUniform;
        optional<int>                 _explicitTextureUnit;
        optional<int>                 _textureUnit;
        optional<int>                 _textureSize;
        bool                          _useShaders;
        bool                          _isGeocentric;
        double                        _maxProjectedMapExtent;
        bool                          _mipmapping;
        bool                          _rttBlending;
        bool                          _updatePending;
        unsigned                      _rttTraversalMask;
        
        double                        _maxHorizonDistance;
        bool                          _attachStencil;

        osg::ref_ptr<const SpatialReference>    _srs;
        osg::ref_ptr<const osg::EllipsoidModel> _ellipsoid;
        osg::observer_ptr<TerrainEngineNode>    _engine;
        
        struct PerViewData
        {
            osg::ref_ptr<osg::Camera>     _rttCamera;
            osg::ref_ptr<osg::Uniform>    _texGenUniform;
            osg::ref_ptr<osg::TexGen>     _texGen;
            osg::Matrixd                  _rttViewMatrix;
            osg::Matrixd                  _rttProjMatrix;
            osg::ref_ptr<osg::StateSet>   _subgraphStateSet;
        };

        typedef std::map<osg::Camera*, PerViewData> PerViewDataMap;
        //typedef std::map<osg::NodeVisitor*, PerViewData> PerViewDataMap;
        PerViewDataMap _perViewData;
        Threading::ReadWriteMutex _perViewDataMutex;


    private:
        void updateRTTCamera(PerViewData& pvd);
        void cull( osgUtil::CullVisitor* cv, PerViewData& pvd );
        void initializeForOverlayGraph();
        void initializePerViewData( PerViewData& );
        void initSubgraphShaders( PerViewData& );
        bool checkNeedsUpdate( PerViewData& );
        PerViewData& getPerViewData( osg::Camera* key );
        //PerViewData& getPerViewData( osg::NodeVisitor* 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;
    };

} // namespace osgEarth

#endif //OSGEARTH_OVERLAY_DECORATOR
