//C++ header - Open Scene Graph - Copyright (C) 1998-2001 Robert Osfield
//Distributed under the terms of the GNU Library General Public License (LGPL)
//as published by the Free Software Foundation.

#ifndef OSGUTIL_CULLVISITOR
#define OSGUTIL_CULLVISITOR 1

#include <osg/NodeVisitor>
#include <osg/BoundingSphere>
#include <osg/BoundingBox>
#include <osg/Matrix>
#include <osg/Drawable>
#include <osg/StateSet>
#include <osg/State>
#include <osg/Camera>
#include <osg/Impostor>
#include <osg/EarthSky>
#include <osg/Notify>

#include <osgUtil/RenderGraph>
#include <osgUtil/RenderStage>
#include <osgUtil/CullViewState>

#include <map>
#include <vector>

#include <osg/Vec3>

namespace osgUtil {

/**
 * Basic NodeVisitor implementation for rendering a scene.
 * This visitor traverses the scene graph, collecting transparent and
 * opaque osg::Drawables into a depth sorted transparent bin and a state
 * sorted opaque bin.  The opaque bin is rendered first, and then the
 * transparent bin in rendered in order from the furthest osg::Drawable
 * from the eye to the one nearest the eye. 
 */
class OSGUTIL_EXPORT CullVisitor : public osg::NodeVisitor
{
    public:

        CullVisitor();
        virtual ~CullVisitor();

        virtual CullVisitor* cloneType() const { return new CullVisitor(); }

        virtual void reset();

        virtual void apply(osg::Node&);
        virtual void apply(osg::Geode& node);
        virtual void apply(osg::Billboard& node);
        virtual void apply(osg::LightSource& node);

        virtual void apply(osg::Group& node);
        virtual void apply(osg::Transform& node);
        virtual void apply(osg::Switch& node);
        virtual void apply(osg::LOD& node);
        virtual void apply(osg::EarthSky& node);
        virtual void apply(osg::Impostor& node);



        void setCamera(const osg::Camera& camera);
        const osg::Camera* getCamera() const { return _camera.get(); }


        void setEarthSky(const osg::EarthSky* earthSky) { _earthSky = earthSky; }
        const osg::EarthSky* getEarthSky() const { return _earthSky.get(); }


        void setLODBias(const float bias) { _LODBias = bias; }
        const float getLODBias() const    { return _LODBias; }
        
        /** Switch the creation of Impostors on or off.
          * Setting active to false forces the CullVisitor to use the Impostor
          * LOD children for rendering. Setting active to true forces the
          * CullVisitor to create the appropriate pre-rendering stages which
          * render to the ImpostorSprite's texture.*/
        void setImpostorsActive(const bool active) { _impostorActive = active; }
        
        /** Get whether impostors are active or not. */
        const bool getImpostorsActive() const { return _impostorActive; }

        /** Set the impostor error threshold.
          * Used in calculation of whether impostors remain valid.*/
        void setImpostorPixelErrorThreshold(const float numPixels) { _impostorPixelErrorThreshold=numPixels; }

        /** Get the impostor error threshold.*/
        const float getImpostorPixelErrorThreshold() const { return _impostorPixelErrorThreshold; }

        /** Set whether ImpsotorSprite's should be placed in a depth sorted bin for rendering.*/
        void setDepthSortImpostorSprites(const bool doDepthSort) { _depthSortImpostorSprites = doDepthSort; }

        /** Get whether ImpsotorSprite's are depth sorted bin for rendering.*/
        const bool setDepthSortImpostorSprites() const { return _depthSortImpostorSprites; }

        /** Set the number of frames that an ImpsotorSprite's is kept whilst not being beyond,
          * before being recycled.*/
        void setNumberOfFrameToKeepImpostorSprites(const int numFrames) { _numFramesToKeepImpostorSprites = numFrames; }

        /** Get the number of frames that an ImpsotorSprite's is kept whilst not being beyond,
          * before being recycled.*/
        const int getNumberOfFrameToKeepImpostorSprites() const { return _numFramesToKeepImpostorSprites; }

        enum TransparencySortMode {
            LOOK_VECTOR_DISTANCE,
            OBJECT_EYE_POINT_DISTANCE
        };

        void setTransparencySortMode(TransparencySortMode tsm) { _tsm = tsm; }
        
        /** Sets the current CullingMode.*/
        void setCullingMode(CullViewState::CullingMode mode);

        /** Returns the current CullingMode.*/
        CullViewState::CullingMode getCullingMode() const;
        

        /** Set the viewport.
          * Used to enable the CullVisitor can make decision 
          * such as based on viewport dimensions.*/
        void setViewport(osg::Viewport* viewport) { _viewport = viewport; }

        /** Get the const viewport. */
        const osg::Viewport* getViewport() const { return _viewport.get(); }

        /** Get the viewport. */
        osg::Viewport* getViewport() { return _viewport.get(); }

        inline void pushCullViewState() { pushCullViewState(NULL,NULL); }
        void pushCullViewState(osg::Matrix* matrix);
        void pushCullViewState(osg::Matrix* matrix,osg::Matrix* inverse);
        
        void popCullViewState();

        /** Push state set on the current state group.
          * If the state exists in a child state group of the current
          * state group then move the current state group to that child.
          * Otherwise, create a new state group for the state set, add
          * it to the current state group then move the current state
          * group pointer to the new state group.
          */
        inline void pushStateSet(const osg::StateSet* ss)
        {
            _currentRenderGraph = _currentRenderGraph->find_or_insert(ss);
            if (ss->useRenderBinDetails())
            {
                _currentRenderBin = _currentRenderBin->find_or_insert(ss->getBinNumber(),ss->getBinName());
            }
        }
        
        /** Pop the top state set and hence associated state group.
          * Move the current state group to the parent of the popped
          * state group.
          */
        inline void popStateSet()
        {
            if (_currentRenderGraph->_stateset->useRenderBinDetails())
            {
                _currentRenderBin = _currentRenderBin->_parent;
            }
            _currentRenderGraph = _currentRenderGraph->_parent;
        }
        
        void setRenderGraph(RenderGraph* rg)
        {
            _rootRenderGraph = rg;
            _currentRenderGraph = rg;
        }

        RenderGraph* getRenderGraph()
        {
            return _rootRenderGraph.get();
        }

        void setRenderStage(RenderStage* rg)
        {
            _rootRenderStage = rg;
            _currentRenderBin = rg;
        }

        RenderStage* getRenderStage()
        {
            return _rootRenderStage.get();
        }

        const float getCalculatedNearPlane() const { return _calculated_znear; }
        
        const float getCalculatedFarPlane() const { return _calculated_zfar; }

	//SandB
	/**sets the flag for detailed culling*/
	void setDetailedCulling(bool detailed) {_detailedCulling = detailed;}

	/**gets the status of detailed culling*/
	const bool getDetailedCulling() const {return _detailedCulling;}

        /**calculates unit directions of vectors that are intersections of cameras' clipping planes*/
        void calcClippingDirections() const;

    protected:

        /** prevent unwanted copy construction.*/
        CullVisitor(const CullVisitor&):osg::NodeVisitor() {}

        /** prevent unwanted copy operator.*/
        CullVisitor& operator = (const CullVisitor&) { return *this; }
        
        inline osg::Matrix* getCurrentMatrix()
        {
            return _cvs->_matrix.get();
        }


        inline osg::Matrix* getInverseCurrentMatrix()
        {
            return _cvs->_inverse.get();
        }

        inline const osg::Vec3& getEyeLocal() const
        {
            return _cvs->_eyePoint;
        }

        inline const osg::Vec3& getUpLocal() const
        {
            return _cvs->_upVector;
        }

        inline const osg::Vec3& getCenterLocal() const
        {
            return _cvs->_centerPoint;
        }


        inline const osg::Vec3& getLookVectorLocal() const
        {
            return _cvs->_lookVector;
        }


        inline bool isCulled(const osg::BoundingSphere& sp,CullViewState::CullingMode& mode) const
        {
            return _cvs->isCulled(sp,mode);
        }

        inline const bool isCulled(const osg::BoundingBox& bb,const CullViewState::CullingMode mode) const
        {
            return _cvs->isCulled(bb,mode);
        }
	
	void updateCalculatedNearFar(const osg::BoundingBox& bb);

    	void updateCalculatedNearFar(const osg::Vec3& pos);
		

        //SandB added: ////////////////////////////////////////////////////////////////////////////////
        /**updates near and far clipping values for case of detailed culling*/
        void updateCalculatedNearFar(osg::Drawable* pDrawable);

        /**calculates near for "global" vertex in scene*/
        double calculateZNear(const osg::Vec3& position, const osg::Vec3& eye, const osg::Vec3& look);
        ///////////////////////////////////////////////////////////////////////////////////////////////

        /**unit vector of direction along intersection of cameras left and top clipping planes in "global" coordinates*/
        mutable osg::Vec3 _LeftUp;
        /**unit vector of direction along intersection of cameras right and top clipping planes in "global" coordinates*/
        mutable osg::Vec3 _RightUp;
        /**unit vector of direction along intersection of cameras left and down clipping planes in "global" coordinates*/
        mutable osg::Vec3 _LeftDown;
        /**unit vector of direction along intersection of cameras right and down clipping planes in "global" coordinates*/
        mutable osg::Vec3 _RightDown;


        /** Add a drawable to current render graph.*/
        inline void addDrawable(osg::Drawable* drawable,osg::Matrix* matrix)
        {
            if (_currentRenderGraph->leaves_empty())
            {
                // this is first leaf to be added to RenderGraph
                // and therefore should not already know to current render bin,
                // so need to add it.
                _currentRenderBin->addRenderGraph(_currentRenderGraph);
            }
            //_currentRenderGraph->addLeaf(new RenderLeaf(drawable,matrix));
    	    _currentRenderGraph->addLeaf(createOrReuseRenderLeaf(drawable,matrix));
        }

        /** Add a drawable and depth to current render graph.*/
        inline void addDrawableAndDepth(osg::Drawable* drawable,osg::Matrix* matrix,const float depth)
        {
            if (_currentRenderGraph->leaves_empty())
            {
                // this is first leaf to be added to RenderGraph
                // and therefore should not already know to current render bin,
                // so need to add it.
                _currentRenderBin->addRenderGraph(_currentRenderGraph);
            }
            //_currentRenderGraph->addLeaf(new RenderLeaf(drawable,matrix,depth));
    	    _currentRenderGraph->addLeaf(createOrReuseRenderLeaf(drawable,matrix,depth));
        }

        /** Add a light to current render graph.*/
        inline void addLight(osg::Light* light,osg::Matrix* matrix)
        {
            _currentRenderBin->_stage->addLight(light,matrix);
        }

        /** create an impostor sprite by setting up a pre-rendering stage
          * to generate the impostor texture. */
        osg::ImpostorSprite* createImpostorSprite(osg::Impostor& node);

		bool _detailedCulling;

        typedef std::vector< osg::ref_ptr<CullViewState> > CullViewStateStack;
        CullViewStateStack          _viewStateStack;
        osg::ref_ptr<CullViewState> _tvs;
        osg::ref_ptr<CullViewState> _cvs;
        
        osg::ref_ptr<RenderGraph>   _rootRenderGraph;
        RenderGraph*                _currentRenderGraph;

        osg::ref_ptr<RenderStage>   _rootRenderStage;        
        RenderBin*                  _currentRenderBin;

        std::vector<CullViewState::CullingMode> _cullingModeStack;

        float   _LODBias;

        float _calculated_znear;
        float _calculated_zfar;
        
        osg::ref_ptr<const osg::Camera> _camera;
        
        osg::ref_ptr<const osg::EarthSky> _earthSky;

        TransparencySortMode _tsm;
	
        // viewport x,y,width,height respectively.
        osg::ref_ptr<osg::Viewport> _viewport;

        bool _impostorActive;
        bool _depthSortImpostorSprites;
        float _impostorPixelErrorThreshold;
        int _numFramesToKeepImpostorSprites;
	
	typedef std::vector< osg::ref_ptr<osg::Matrix> > MatrixList;
	MatrixList _reuseMatrixList;
	unsigned int _currentReuseMatrixIndex;
	
	inline osg::Matrix* createOrReuseMatrix()
	{
            // skip of any already reused matrix.
            while (_currentReuseMatrixIndex<_reuseMatrixList.size() && 
                   _reuseMatrixList[_currentReuseMatrixIndex]->referenceCount()>1)
            {
                osg::notify(osg::NOTICE)<<"Warning:createOrReuseMatrix() skipping multiply refrenced entry."<< std::endl;
                ++_currentReuseMatrixIndex;
            }

            // if still within list, element must be singularly referenced
            // there return it to be reused.
            if (_currentReuseMatrixIndex<_reuseMatrixList.size())
            {
                osg::Matrix* matrix = _reuseMatrixList[_currentReuseMatrixIndex++].get();
                matrix->makeIdentity();
                return matrix;
            }

            // otherwise need to create new matrix.
            osg::Matrix* matrix = new osg::Matrix();
            _reuseMatrixList.push_back(matrix);
            ++_currentReuseMatrixIndex;
            return matrix;
	}
	
	typedef std::vector< osg::ref_ptr<RenderLeaf> > RenderLeafList;
	RenderLeafList _reuseRenderLeafList;
	unsigned int _currentReuseRenderLeafIndex;
	
	inline RenderLeaf* createOrReuseRenderLeaf(osg::Drawable* drawable,osg::Matrix* matrix, float depth=0.0f)
	{
            // skip of any already reused renderleaf.
            while (_currentReuseRenderLeafIndex<_reuseRenderLeafList.size() && 
                   _reuseRenderLeafList[_currentReuseRenderLeafIndex]->referenceCount()>1)
            {
                osg::notify(osg::NOTICE)<<"Warning:createOrReuseRenderLeaf() skipping multiply refrenced entry."<< std::endl;
                ++_currentReuseRenderLeafIndex;
            }

            // if still within list, element must be singularly referenced
            // there return it to be reused.
            if (_currentReuseRenderLeafIndex<_reuseRenderLeafList.size())
            {
                RenderLeaf* renderleaf = _reuseRenderLeafList[_currentReuseRenderLeafIndex++].get();
                renderleaf->set(drawable,matrix,depth);
                return renderleaf;
            }

            // otherwise need to create new renderleaf.
            RenderLeaf* renderleaf = new RenderLeaf(drawable,matrix,depth);
            _reuseRenderLeafList.push_back(renderleaf);
            ++_currentReuseRenderLeafIndex;
            return renderleaf;
	}

        osg::ref_ptr<osg::ImpostorSpriteManager> _impostorSpriteManager;
	
};

}

#endif

