/* -*-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 OSG_CULLSTACK
#define OSG_CULLSTACK 1

#include <osg/CullingSet>
#include <osg/Viewport>
#include <osg/fast_back_stack>
#include <osg/Notify>

namespace osg {

/** A CullStack class which accumulates the current project, modelview matrices
and the CullingSet. */
class SG_EXPORT CullStack
{

    public:
    
    
        CullStack();
        
        ~CullStack();
    
        typedef std::vector<ShadowVolumeOccluder>   OccluderList;

        enum CullingModeValues
        {
            NO_CULLING                  = 0x0,
            VIEW_FRUSTUM_SIDES_CULLING  = 0x1,
            NEAR_PLANE_CULLING          = 0x2,
            FAR_PLANE_CULLING           = 0x4,
            VIEW_FRUSTUM_CULLING        = VIEW_FRUSTUM_SIDES_CULLING|
                                          NEAR_PLANE_CULLING|
                                          FAR_PLANE_CULLING,
            SMALL_FEATURE_CULLING       = 0x8,
            SHADOW_OCCLUSION_CULLING    = 0x10,
            DEFAULT_CULLING             = VIEW_FRUSTUM_SIDES_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING,
            ENABLE_ALL_CULLING          = VIEW_FRUSTUM_CULLING|
                                          SMALL_FEATURE_CULLING|
                                          SHADOW_OCCLUSION_CULLING
        };
        
        typedef unsigned int CullingMode;
        

        void reset();
        
        void setOccluderList(const ShadowVolumeOccluderList& svol) { _occluderList = svol; }
        ShadowVolumeOccluderList& getOccluderList() { return _occluderList; }
        const ShadowVolumeOccluderList& getOccluderList() const { return _occluderList; }

        void pushViewport(osg::Viewport* viewport);
        void popViewport();

        void pushProjectionMatrix(osg::RefMatrix* matrix);
        void popProjectionMatrix();

        void pushModelViewMatrix(osg::RefMatrix* matrix);
        void popModelViewMatrix();

        inline float getFrustumVolume() { if (_frustumVolume<0.0f) computeFrustumVolume(); return _frustumVolume; }


        /** Sets the current CullingMode.*/
        void setCullingMode(CullingMode mode) { _cullingMode = mode; }
        
        /** Returns the current CullingMode.*/
        CullingMode getCullingMode() const { return _cullingMode; }

        void setLODScale(float bias) { _LODScale = bias; }
        float getLODScale() const    { return _LODScale; }
        
        void setSmallFeatureCullingPixelSize(float value) { _smallFeatureCullingPixelSize=value; }
        float getSmallFeatureCullingPixelSize() const { return _smallFeatureCullingPixelSize; }
    
    
        /** Compute the pixel of an object at position v, with specified radius.*/
        float pixelSize(const Vec3& v,float radius) const
        {
            return getCurrentCullingSet().pixelSize(v,radius);
        }
        
        /** Compute the pixel of an bounding sphere.*/
        float pixelSize(const BoundingSphere& bs) const
        {
            return pixelSize(bs.center(),bs.radius());
        }

        inline void disableAndPushOccludersCurrentMask(NodePath& nodePath)
        {
            getCurrentCullingSet().disableAndPushOccludersCurrentMask(nodePath);
        }

        inline void popOccludersCurrentMask(NodePath& nodePath)
        {
            getCurrentCullingSet().popOccludersCurrentMask(nodePath);
        }

        inline bool isCulled(const std::vector<Vec3>& vertices)
        {
            return getCurrentCullingSet().isCulled(vertices);
        }

        inline bool isCulled(const BoundingBox& bb)
        {
            return bb.valid() && getCurrentCullingSet().isCulled(bb);
        }
        
        inline bool isCulled(const BoundingSphere& bs)
        {
            return getCurrentCullingSet().isCulled(bs);
        }
        
        inline bool isCulled(const osg::Node& node)
        {
            return node.isCullingActive() && getCurrentCullingSet().isCulled(node.getBound());
        }

        inline void pushCurrentMask()
        {
            getCurrentCullingSet().pushCurrentMask();
        }
        
        inline void popCurrentMask()
        {
            getCurrentCullingSet().popCurrentMask();
        }
        
        
        typedef std::vector< CullingSet > CullingStack;

        inline CullingStack& getClipSpaceCullingStack() { return _clipspaceCullingStack; }
        
        inline CullingStack& getProjectionCullingStack() { return _projectionCullingStack; }
        
        inline CullingStack& getModelViewCullingStack() { return _modelviewCullingStack; }
        
//        inline CullingSet& getCurrentCullingSet() { return _modelviewCullingStack.back(); }
//        inline const CullingSet& getCurrentCullingSet() const { return _modelviewCullingStack.back(); }

        inline CullingSet& getCurrentCullingSet() { return *_back_modelviewCullingStack; }
        inline const CullingSet& getCurrentCullingSet() const { return *_back_modelviewCullingStack; }
        
        inline osg::Viewport* getViewport();
        inline osg::RefMatrix& getModelViewMatrix();
        inline osg::RefMatrix& getProjectionMatrix();
        inline osg::Matrix getWindowMatrix();
        inline const osg::RefMatrix& getMVPW();

        inline const osg::Vec3& getEyeLocal() const { return _eyePointStack.back(); }

        inline const osg::Vec3 getUpLocal() const
        {
            const osg::Matrix& matrix = *_modelviewStack.back();
            return osg::Vec3(matrix(0,1),matrix(1,1),matrix(2,1));
        }

        inline const osg::Vec3 getLookVectorLocal() const
        {
            const osg::Matrix& matrix = *_modelviewStack.back();
            return osg::Vec3(-matrix(0,2),-matrix(1,2),-matrix(2,2));
        }
        
    protected:
    
        void pushCullingSet();
        void popCullingSet();

        CullingMode                                                 _cullingMode;
        float                                                       _LODScale;
        float                                                       _smallFeatureCullingPixelSize;

        // base set of shadow volume occluder to use in culling.
        ShadowVolumeOccluderList                                    _occluderList;

        typedef fast_back_stack< ref_ptr<RefMatrix> >                  MatrixStack;

        MatrixStack                                                 _projectionStack;

        MatrixStack                                                 _modelviewStack;
        MatrixStack                                                 _MVPW_Stack;

        typedef fast_back_stack<ref_ptr<Viewport> >                 ViewportStack;
        ViewportStack                                               _viewportStack;
        
        typedef fast_back_stack<Vec3>                               EyePointStack;
        EyePointStack                                               _eyePointStack;

        CullingStack                                                _clipspaceCullingStack;
        CullingStack                                                _projectionCullingStack;

        CullingStack                                                _modelviewCullingStack;
        unsigned int                                                _index_modelviewCullingStack;
        CullingSet*                                                 _back_modelviewCullingStack;

        void computeFrustumVolume();
        float                                                       _frustumVolume;

        unsigned int                                                _bbCornerNear;
        unsigned int                                                _bbCornerFar;

        ref_ptr<osg::RefMatrix>                                     _identity;
        
        typedef std::vector< osg::ref_ptr<osg::RefMatrix> > MatrixList;
	MatrixList _reuseMatrixList;
	unsigned int _currentReuseMatrixIndex;
	
	inline osg::RefMatrix* createOrReuseMatrix(const osg::Matrix& value);

        
};

inline osg::Viewport* CullStack::getViewport()
{
    if (!_viewportStack.empty())
    {
        return _viewportStack.back().get();
    }
    else
    {
        return 0L;
    }
}

inline osg::RefMatrix& CullStack::getModelViewMatrix()
{
    if (!_modelviewStack.empty())
    {
        return *_modelviewStack.back();
    }
    else
    {
        return *_identity;
    }
}

inline osg::RefMatrix& CullStack::getProjectionMatrix()
{
    if (!_projectionStack.empty())
    {
        return *_projectionStack.back();
    }
    else
    {
        return *_identity;
    }
}

inline osg::Matrix CullStack::getWindowMatrix()
{
    if (!_viewportStack.empty())
    {
        osg::Viewport* viewport = _viewportStack.back().get();
        return viewport->computeWindowMatrix();
    }
    else
    {
        return *_identity;
    }
}

inline const osg::RefMatrix& CullStack::getMVPW()
{
    if (!_MVPW_Stack.empty())
    {
        if (!_MVPW_Stack.back())
        {
            _MVPW_Stack.back() = createOrReuseMatrix(getModelViewMatrix());
            (*_MVPW_Stack.back()) *= getProjectionMatrix();
            (*_MVPW_Stack.back()) *= getWindowMatrix();
        }
        return *_MVPW_Stack.back();
    }
    else
    {
        return *_identity;
    }
}

inline RefMatrix* CullStack::createOrReuseMatrix(const osg::Matrix& value)
{
    // skip of any already reused matrix.
    while (_currentReuseMatrixIndex<_reuseMatrixList.size() && 
           _reuseMatrixList[_currentReuseMatrixIndex]->referenceCount()>1)
    {
        //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())
    {
        RefMatrix* matrix = _reuseMatrixList[_currentReuseMatrixIndex++].get();
        matrix->set(value);
        return matrix;
    }

    // otherwise need to create new matrix.
    osg::RefMatrix* matrix = new RefMatrix(value);
    _reuseMatrixList.push_back(matrix);
    ++_currentReuseMatrixIndex;
    return matrix;
}

}	// end of namespace

#endif 
