/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2003 Robert Osfield 
 *
 * This library is open source and may be redistributed and/or modified under  
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or 
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 * 
 * This library 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 
 * OpenSceneGraph Public License for more details.
*/

#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/Impostor>
#include <osg/ClearNode>
#include <osg/Notify>
#include <osg/Notify>

#include <osg/CullStack>

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

#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 osg::CullStack
{
    public:
    
        typedef osg::Matrix::value_type value_type;
    

        CullVisitor();
        virtual ~CullVisitor();

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

        virtual void reset();
        
        virtual osg::Vec3 getEyePoint() const { return getEyeLocal(); }
        virtual float getDistanceToEyePoint(const osg::Vec3& pos, bool withLODScale) const;
        virtual float getDistanceFromEyePoint(const osg::Vec3& pos, bool withLODScale) const;

        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::ClipNode& node);

        virtual void apply(osg::Group& node);
        virtual void apply(osg::Transform& node);
        virtual void apply(osg::Projection& node);
        virtual void apply(osg::Switch& node);
        virtual void apply(osg::LOD& node);
        virtual void apply(osg::ClearNode& node);
        virtual void apply(osg::OccluderNode& node);
        virtual void apply(osg::Impostor& node);

        void setClearNode(const osg::ClearNode* earthSky) { _clearNode = earthSky; }
        const osg::ClearNode* getClearNode() const { return _clearNode.get(); }


        /** 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(bool active) { _impostorActive = active; }
        
        /** Get whether impostors are active or not. */
        bool getImpostorsActive() const { return _impostorActive; }

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

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

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

        /** Get whether ImpsotorSprite's are depth sorted bin for rendering.*/
        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(int numFrames) { _numFramesToKeepImpostorSprites = numFrames; }

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

        enum ComputeNearFarMode
        {
            DO_NOT_COMPUTE_NEAR_FAR = 0,
            COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES,
            COMPUTE_NEAR_FAR_USING_PRIMITIVES
        };

        void setComputeNearFarMode(ComputeNearFarMode cnfm) { _computeNearFar=cnfm; } 
        ComputeNearFarMode getComputeNearFarMode() const { return _computeNearFar;} 
        
        void setNearFarRatio(float ratio) { _nearFarRatio = ratio; }
        float getNearFarRatio() const { return _nearFarRatio; }
        

        /** 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;
        }
        
        inline void setRenderGraph(RenderGraph* rg)
        {
            _rootRenderGraph = rg;
            _currentRenderGraph = rg;
        }

        inline RenderGraph* getRootRenderGraph()
        {
            return _rootRenderGraph.get();
        }

        inline RenderGraph* getCurrentRenderGraph()
        {
            return _currentRenderGraph;
        }

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

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

        inline RenderBin* getCurrentRenderBin()
        {
            return _currentRenderBin;
        }

        inline void setCurrentRenderBin(RenderBin* rb)
        {
            _currentRenderBin = rb;
        }

        inline float getCalculatedNearPlane() const { return _computed_znear; }
        
        inline float getCalculatedFarPlane() const { return _computed_zfar; }

	void updateCalculatedNearFar(const osg::Matrix& matrix,const osg::Drawable& drawable) { updateCalculatedNearFar(matrix,drawable.getBound()); }
	void updateCalculatedNearFar(const osg::Matrix& matrix,const osg::BoundingBox& bb);
    	void updateCalculatedNearFar(const osg::Vec3& pos);
		
        /** Add a drawable to current render graph.*/
        inline void addDrawable(osg::Drawable* drawable,osg::RefMatrix* matrix);

        /** Add a drawable and depth to current render graph.*/
        inline void addDrawableAndDepth(osg::Drawable* drawable,osg::RefMatrix* matrix,float depth);

        /** Add an attribute which is positioned related to the modelview matrix.*/
        inline void addPositionedAttribute(osg::RefMatrix* matrix,const osg::StateAttribute* attr);

        /** reimplement CullStack's popProjectionMatrix() adding clamping of the projection matrix to the computed near and far.*/
        virtual void popProjectionMatrix();

        void setState(osg::State* state) { _state = state; }
        osg::State* getState() { return _state.get(); }
        const osg::State* getState() const { return _state.get(); }

    protected:

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

        /** prevent unwanted copy operator.*/
        CullVisitor& operator = (const CullVisitor&) { return *this; }
        
        inline void handle_cull_callbacks_and_traverse(osg::Node& node)
        {
            osg::NodeCallback* callback = node.getCullCallback();
            if (callback) (*callback)(&node,this);
            else traverse(node);
        }

        inline void handle_cull_callbacks_and_accept(osg::Node& node,osg::Node* acceptNode)
        {
            osg::NodeCallback* callback = node.getCullCallback();
            if (callback) (*callback)(&node,this);
            else acceptNode->accept(*this);
        }


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

        osg::ref_ptr<RenderGraph>                                   _rootRenderGraph;
        RenderGraph*                                                _currentRenderGraph;

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

        ComputeNearFarMode  _computeNearFar;

        value_type               _nearFarRatio;
        value_type               _computed_znear;
        value_type               _computed_zfar;
        
        osg::ref_ptr<const osg::ClearNode> _clearNode;
	
        bool    _impostorActive;
        bool    _depthSortImpostorSprites;
        float   _impostorPixelErrorThreshold;
        int     _numFramesToKeepImpostorSprites;
	
        
	typedef std::vector< osg::ref_ptr<RenderLeaf> > RenderLeafList;
	RenderLeafList _reuseRenderLeafList;
	unsigned int _currentReuseRenderLeafIndex;
	
	inline RenderLeaf* createOrReuseRenderLeaf(osg::Drawable* drawable,osg::RefMatrix* projection,osg::RefMatrix* matrix, float depth=0.0f);
        
        osg::ref_ptr<osg::ImpostorSpriteManager>    _impostorSpriteManager;

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

inline void CullVisitor::addDrawable(osg::Drawable* drawable,osg::RefMatrix* 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,_projectionStack.back().get(),matrix));
}

/** Add a drawable and depth to current render graph.*/
inline void CullVisitor::addDrawableAndDepth(osg::Drawable* drawable,osg::RefMatrix* matrix,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,_projectionStack.back().get(),matrix,depth));
}

/** Add an attribute which is positioned related to the modelview matrix.*/
inline void CullVisitor::addPositionedAttribute(osg::RefMatrix* matrix,const osg::StateAttribute* attr)
{
    _currentRenderBin->_stage->addPositionedAttribute(matrix,attr);
}

inline RenderLeaf* CullVisitor::createOrReuseRenderLeaf(osg::Drawable* drawable,osg::RefMatrix* projection,osg::RefMatrix* matrix, float depth)
{
    // 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,projection,matrix,depth);
        return renderleaf;
    }

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


}

#endif

