/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2005 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_SCENEVIEW
#define OSGUTIL_SCENEVIEW 1

#include <osg/Node>
#include <osg/StateSet>
#include <osg/Light>
#include <osg/FrameStamp>
#include <osg/DisplaySettings>
#include <osg/CollectOccludersVisitor>
#include <osg/CullSettings>

#include <osgUtil/CullVisitor>

namespace osgUtil {

/**
 * SceneView is literally a view of a scene, encapsulating the 'camera' 
 * (not to be confused with Producer::Camera) (modelview+projection matrices),
 * global state, lights and the scene itself.  Provides
 * methods for setting up the view and rendering it.
*/
class OSGUTIL_EXPORT SceneView : public osg::Referenced, public osg::CullSettings
{
    public:

        /** Construct a default scene view.*/
        SceneView(osg::DisplaySettings* ds=NULL);

        enum Options
        {
            NO_SCENEVIEW_LIGHT = 0x0,
            HEADLIGHT = 0x1,
            SKY_LIGHT = 0x2,
            COMPILE_GLOBJECTS_AT_INIT = 0x4,
            STANDARD_SETTINGS = HEADLIGHT |
                                COMPILE_GLOBJECTS_AT_INIT                                
        };

        /** Set scene view to use default global state, light, camera
         *  and render visitor.
         */
        void setDefaults(unsigned int options = STANDARD_SETTINGS);

        /** Set the data to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        void setSceneData(osg::Node* node) { _sceneData = node; }
        /** Get the scene data to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        osg::Node* getSceneData() { return _sceneData.get(); }

        /** Get the const scene data which to view. The data will typically be
         *  an osg::Scene but can be any osg::Node type.
         */
        const osg::Node* getSceneData() const { return _sceneData.get(); }

        /** Set the viewport of the scene view to use specified osg::Viewport. */
        void setViewport(osg::Viewport* viewport)
        {
            if (viewport) _viewport = viewport;
            else
            {
                // ensure that _viewport is always valid.
                _viewport = new osg::Viewport;
            }
        }

        /** Set the viewport of the scene view to specified dimensions. */
        void setViewport(int x,int y,int width,int height)
        {
            _viewport->setViewport(x,y,width,height);
        }
        

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

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

        /** Get the viewport of the scene view. */
        void getViewport(int& x,int& y,int& width,int& height) const
        {
            _viewport->getViewport(x,y,width,height);
        }

        /** Set the DisplaySettings. */
        inline void setDisplaySettings(osg::DisplaySettings* vs) { _displaySettings = vs; }
        
        /** Get the const DisplaySettings */
        inline const osg::DisplaySettings* getDisplaySettings() const { return _displaySettings.get(); }

        /** Get the DisplaySettings */
        inline osg::DisplaySettings* getDisplaySettings() { return _displaySettings.get(); }


        /** Set the color used in glClearColor().
            Defaults to an off blue color.*/
        void setClearColor(const osg::Vec4& color) { _clearColor=color; }
        /** Get the color used in glClearColor.*/
        const osg::Vec4& getClearColor() const { return _clearColor; }


        void setGlobalStateSet(osg::StateSet* state) { _globalStateSet = state; }
        osg::StateSet* getGlobalStateSet() { return _globalStateSet.get(); }
        const osg::StateSet* getGlobalStateSet() const { return _globalStateSet.get(); }

        void setLocalStateSet(osg::StateSet* state) { _localStateSet = state; }
        osg::StateSet* getLocalStateSet() { return _localStateSet.get(); }
        const osg::StateSet* getLocalStateSet() const { return _localStateSet.get(); }
        
        enum ActiveUniforms
        {
            FRAME_NUMBER_UNIFORM            = 0x1,
            FRAME_TIME_UNIFORM              = 0x2,
            DELTA_FRAME_TIME_UNIFORM        = 0x4,
            VIEW_MATRIX_UNIFORM             = 0x8,
            INVERSE_VIEW_MATRIX_UNIFORM     = 0x16,
            DEFAULT_UNIFORMS                = FRAME_NUMBER_UNIFORM |
                                              FRAME_TIME_UNIFORM |
                                              DELTA_FRAME_TIME_UNIFORM |
                                              VIEW_MATRIX_UNIFORM |
                                              INVERSE_VIEW_MATRIX_UNIFORM,
            ALL_UNIFORMS                    = 0xFFFFFFFF
        };

        /** Set the uniforms that SceneView should set set up on each frame.*/        
        void setActiveUniforms(int activeUniforms) { _activeUniforms = activeUniforms; }

        /** Get the uniforms that SceneView should set set up on each frame.*/        
        int getActiveUniforms() const { return _activeUniforms; }

        void updateUniforms();
        

        typedef Options LightingMode;

        void setLightingMode(LightingMode mode) { _lightingMode=mode; }
        LightingMode getLightingMode() const { return _lightingMode; }

        void setLight(osg::Light* light) { _light = light; }
        osg::Light* getLight() { return _light.get(); }
        const osg::Light* getLight() const { return _light.get(); }
        
        void setState(osg::State* state) { _state = state; }
        osg::State* getState() { return _state.get(); }
        const osg::State* getState() const { return _state.get(); }



        /** Set the projection matrix. Can be thought of as setting the lens of a camera. */
        inline void setProjectionMatrix(const osg::Matrixf& matrix) { _projectionMatrix.set(matrix); }

        /** Set the projection matrix. Can be thought of as setting the lens of a camera. */
        inline void setProjectionMatrix(const osg::Matrixd& matrix) { _projectionMatrix.set(matrix); }

        /** Set to an orthographic projection. See OpenGL glOrtho for documentation further details.*/
        void setProjectionMatrixAsOrtho(double left, double right,
                                        double bottom, double top,
                                        double zNear, double zFar);

        /** Set to a 2D orthographic projection. See OpenGL glOrtho2D documentation for further details.*/
        void setProjectionMatrixAsOrtho2D(double left, double right,
                                          double bottom, double top);

        /** Set to a perspective projection. See OpenGL glFrustum documentation for further details.*/
        void setProjectionMatrixAsFrustum(double left, double right,
                                          double bottom, double top,
                                          double zNear, double zFar);

        /** Create a symmetrical perspective projection, See OpenGL gluPerspective documentation for further details.
          * Aspect ratio is defined as width/height.*/
        void setProjectionMatrixAsPerspective(double fovy,double aspectRatio,
                                              double zNear, double zFar);

        /** Get the projection matrix.*/
        osg::Matrixd& getProjectionMatrix() { return _projectionMatrix; }

        /** Get the const projection matrix.*/
        const osg::Matrixd& getProjectionMatrix() const { return _projectionMatrix; }

        /** Get the othographic settings of the orthographic projection matrix. 
          * Returns false if matrix is not an orthographic matrix, where parameter values are undefined.*/
        bool getProjectionMatrixAsOrtho(double& left, double& right,
                                        double& bottom, double& top,
                                        double& zNear, double& zFar);

        /** Get the frustum setting of a perspective projection matrix.
          * Returns false if matrix is not a perspective matrix, where parameter values are undefined.*/
        bool getProjectionMatrixAsFrustum(double& left, double& right,
                                          double& bottom, double& top,
                                          double& zNear, double& zFar);

        /** Get the frustum setting of a symmetric perspective projection matrix.
          * Returns false if matrix is not a perspective matrix, where parameter values are undefined. 
          * Note, if matrix is not a symmetric perspective matrix then the shear will be lost.
          * Asymmetric matrices occur when stereo, power walls, caves and reality center display are used.
          * In these configurations one should use the 'getProjectionMatrixAsFrustum' method instead.*/
        bool getProjectionMatrixAsPerspective(double& fovy,double& aspectRatio,
                                              double& zNear, double& zFar);


        /** Set the view matrix. Can be thought of as setting the position of the world relative to the camera in camera coordinates. */
        inline void setViewMatrix(const osg::Matrixf& matrix) { _viewMatrix.set(matrix); }
        
        /** Set the view matrix. Can be thought of as setting the position of the world relative to the camera in camera coordinates. */
        inline void setViewMatrix(const osg::Matrixd& matrix) { _viewMatrix.set(matrix); }

        /** Set to the position and orientation of view matrix, using the same convention as gluLookAt. */
        void setViewMatrixAsLookAt(const osg::Vec3& eye,const osg::Vec3& center,const osg::Vec3& up);

        /** Get the view matrix. */
        osg::Matrixd& getViewMatrix() { return _viewMatrix; }

        /** Get the const view matrix. */
        const osg::Matrixd& getViewMatrix() const { return _viewMatrix; }

        /** Get to the position and orientation of a modelview matrix, using the same convention as gluLookAt. */
        void getViewMatrixAsLookAt(osg::Vec3& eye,osg::Vec3& center,osg::Vec3& up,float lookDistance=1.0f);



        
        void setInitVisitor(osg::NodeVisitor* av) { _initVisitor = av; }
        osg::NodeVisitor* getInitVisitor() { return _initVisitor.get(); }
        const osg::NodeVisitor* getInitVisitor() const { return _initVisitor.get(); }


        void setUpdateVisitor(osg::NodeVisitor* av) { _updateVisitor = av; }
        osg::NodeVisitor* getUpdateVisitor() { return _updateVisitor.get(); }
        const osg::NodeVisitor* getUpdateVisitor() const { return _updateVisitor.get(); }


        void setCullVisitor(osgUtil::CullVisitor* cv) { _cullVisitor = cv; }
        osgUtil::CullVisitor* getCullVisitor() { return _cullVisitor.get(); }
        const osgUtil::CullVisitor* getCullVisitor() const { return _cullVisitor.get(); }

        void setCullVisitorLeft(osgUtil::CullVisitor* cv) { _cullVisitorLeft = cv; }
        osgUtil::CullVisitor* getCullVisitorLeft() { return _cullVisitorLeft.get(); }
        const osgUtil::CullVisitor* getCullVisitorLeft() const { return _cullVisitorLeft.get(); }

        void setCullVisitorRight(osgUtil::CullVisitor* cv) { _cullVisitorRight = cv; }
        osgUtil::CullVisitor* getCullVisitorRight() { return _cullVisitorRight.get(); }
        const osgUtil::CullVisitor* getCullVisitorRight() const { return _cullVisitorRight.get(); }
        
        void setCollectOccludersVisitor(osg::CollectOccludersVisitor* cov) { _collectOccludersVisistor = cov; }
        osg::CollectOccludersVisitor* getCollectOccludersVisitor() { return _collectOccludersVisistor.get(); }
        const osg::CollectOccludersVisitor* getCollectOccludersVisitor() const { return _collectOccludersVisistor.get(); }


        void setRenderGraph(osgUtil::RenderGraph* rg) { _rendergraph = rg; }
        osgUtil::RenderGraph* getRenderGraph() { return _rendergraph.get(); }
        const osgUtil::RenderGraph* getRenderGraph() const { return _rendergraph.get(); }

        void setRenderGraphLeft(osgUtil::RenderGraph* rg) { _rendergraphLeft = rg; }
        osgUtil::RenderGraph* getRenderGraphLeft() { return _rendergraphLeft.get(); }
        const osgUtil::RenderGraph* getRenderGraphLeft() const { return _rendergraphLeft.get(); }

        void setRenderGraphRight(osgUtil::RenderGraph* rg) { _rendergraphRight = rg; }
        osgUtil::RenderGraph* getRenderGraphRight() { return _rendergraphRight.get(); }
        const osgUtil::RenderGraph* getRenderGraphRight() const { return _rendergraphRight.get(); }


        void setRenderStage(osgUtil::RenderStage* rs) { _renderStage = rs; }
        osgUtil::RenderStage* getRenderStage() { return _renderStage.get(); }
        const osgUtil::RenderStage* getRenderStage() const { return _renderStage.get(); }

        void setRenderStageLeft(osgUtil::RenderStage* rs) { _renderStageLeft = rs; }
        osgUtil::RenderStage* getRenderStageLeft() { return _renderStageLeft.get(); }
        const osgUtil::RenderStage* getRenderStageLeft() const { return _renderStageLeft.get(); }

        void setRenderStageRight(osgUtil::RenderStage* rs) { _renderStageRight = rs; }
        osgUtil::RenderStage* getRenderStageRight() { return _renderStageRight.get(); }
        const osgUtil::RenderStage* getRenderStageRight() const { return _renderStageRight.get(); }


        /** Set the draw buffer value used at the start of each frame draw.  Note, overridden in quad buffer stereo mode */
        void setDrawBufferValue( GLenum drawBufferValue )
        {
            _drawBufferValue = drawBufferValue;
        }

        /** Get the draw buffer value used at the start of each frame draw. */
        GLenum getDrawBufferValue() const
        {
            return _drawBufferValue;
        }


        /** FusionDistanceMode is used only when working in stereo.*/
        enum FusionDistanceMode
        {
            /** Use fusion distance from the value set on the SceneView.*/
            USE_FUSION_DISTANCE_VALUE,
            /** Compute the fusion distance by multiplying the screen distance by the fusion distance value.*/
            PROPORTIONAL_TO_SCREEN_DISTANCE
        };

        /** Set the FusionDistanceMode and Value. Note, is used only when working in stereo.*/
        void setFusionDistance(FusionDistanceMode mode,float value=1.0f)
        {
            _fusionDistanceMode = mode;
            _fusionDistanceValue = value;
        }

        /** Get the FusionDistanceMode.*/
        FusionDistanceMode getFusionDistanceMode() const { return _fusionDistanceMode; }

        /** Get the FusionDistanceValue. Note, only used for USE_FUSION_DISTANCE_VALUE & PROPORTIONAL_TO_SCREEN_DISTANCE modes.*/
        float getFusionDistanceValue() const { return _fusionDistanceValue; }


        /** Set whether the draw method should call renderer->prioritizeTexture.*/
        void setPrioritizeTextures(bool pt) { _prioritizeTextures = pt; }
        
        /** Get whether the draw method should call renderer->prioritizeTexture.*/
        bool getPrioritizeTextures() const { return _prioritizeTextures; }

        /** Callback for overidding the default method for compute the offset projection and view matrices.*/
        struct ComputeStereoMatricesCallback : public osg::Referenced
        {
            virtual osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection) const = 0;
            virtual osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const = 0;

            virtual osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection) const = 0;
            virtual osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const = 0;
        };
        
        void setComputeStereoMatricesCallback(ComputeStereoMatricesCallback* callback) { _computeStereoMatricesCallback=callback; }
        ComputeStereoMatricesCallback* getComputeStereoMatricesCallback() { return _computeStereoMatricesCallback.get(); }
        const ComputeStereoMatricesCallback* getComputeStereoMatricesCallback() const { return _computeStereoMatricesCallback.get(); }

        /** Calculate, via glUnProject, the object coordinates of a window point.
            Note, current implementation requires that SceneView::draw() has been previously called
            for projectWindowIntoObject to produce valid values.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window.
            Returns true on successful projection.
        */
        bool projectWindowIntoObject(const osg::Vec3& window,osg::Vec3& object) const;

        /** Calculate, via glUnProject, the object coordinates of a window x,y
            when projected onto the near and far planes.
            Note, current implementation requires that SceneView::draw() has been previously called
            for projectWindowIntoObject to produce valid values.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window.
            Returns true on successful projection.
        */
        bool projectWindowXYIntoObject(int x,int y,osg::Vec3& near_point,osg::Vec3& far_point) const;

        /** Calculate, via glProject, the object coordinates of a window.
            Note, current implementation requires that SceneView::draw() has been previously called
            for projectWindowIntoObject to produce valid values.  Consistent with OpenGL
            windows coordinates are calculated relative to the bottom left of the window,
            whereas window API's normally have the top left as the origin,
            so you may need to pass in (mouseX,window_height-mouseY,...).
            Returns true on successful projection.
        */
        bool projectObjectIntoWindow(const osg::Vec3& object,osg::Vec3& window) const;


        /** Set the frame stamp for the current frame.*/
        inline void setFrameStamp(osg::FrameStamp* fs) { _frameStamp = fs; }

        /** Get the frame stamp for the current frame.*/
        inline const osg::FrameStamp* getFrameStamp() const { return _frameStamp.get(); }




        inline osg::Matrixd computeLeftEyeProjection(const osg::Matrixd& projection)  const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeLeftEyeProjection(projection);
            else return computeLeftEyeProjectionImplementation(projection);
        }

        inline osg::Matrixd computeLeftEyeView(const osg::Matrixd& view) const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeLeftEyeView(view);
            else return computeLeftEyeViewImplementation(view);
        }
        
        inline osg::Matrixd computeRightEyeProjection(const osg::Matrixd& projection)  const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeRightEyeProjection(projection);
            else return computeRightEyeProjectionImplementation(projection);
        }

        inline osg::Matrixd computeRightEyeView(const osg::Matrixd& view) const
        {
            if (_computeStereoMatricesCallback.valid()) return _computeStereoMatricesCallback->computeRightEyeView(view);
            else return computeRightEyeViewImplementation(view);
        }

        virtual osg::Matrixd computeLeftEyeProjectionImplementation(const osg::Matrixd& projection) const;
        virtual osg::Matrixd computeLeftEyeViewImplementation(const osg::Matrixd& view) const;

        virtual osg::Matrixd computeRightEyeProjectionImplementation(const osg::Matrixd& projection) const;
        virtual osg::Matrixd computeRightEyeViewImplementation(const osg::Matrixd& view) const;


        /** Do init traversal of attached scene graph using Init NodeVisitor.
          * The init traversal is called once for each SceneView, and should
          * be used to compile display list, texture objects intialize data
          * not otherwise intialized during scene graph loading. Note, is
          * called automatically by update & cull if it hasn't already been called
          * elsewhere. Also init() should only ever be called within a valid
          * graphics context.*/
        virtual void init();

        /** Do app traversal of attached scene graph using App NodeVisitor.*/
        virtual void update();

        /** Do cull traversal of attached scene graph using Cull NodeVisitor.*/
        virtual void cull();

        /** Do draw traversal of draw bins generated by cull traversal.*/
        virtual void draw();
        
        /** Release all OpenGL objects from the scene graph, such as texture objects, display lists etc.
          * These released scene graphs placed in the respective delete GLObjects cache, which
          * then need to be deleted in OpenGL by SceneView::flushAllDeleteGLObjects(). */
        virtual void releaseAllGLObjects();

        /** Flush all deleted OpenGL objects, such as texture objects, display lists etc.*/
        virtual void flushAllDeletedGLObjects();

        /** Flush deleted OpenGL objects, such as texture objects, display lists etc within specified available time.*/
        virtual void flushDeletedGLObjects(double& availableTime);
        
    protected:

        virtual ~SceneView();

        /** Do cull traversal of attached scene graph using Cull NodeVisitor.*/
        virtual void cullStage(const osg::Matrixd& projection,const osg::Matrixd& modelview,osgUtil::CullVisitor* cullVisitor, osgUtil::RenderGraph* rendergraph, osgUtil::RenderStage* renderStage);

        const osg::Matrix computeMVPW() const;

        void clearArea(int x,int y,int width,int height,const osg::Vec4& color);

        osg::ref_ptr<osg::StateSet>                 _localStateSet;
        osg::ref_ptr<osg::State>                    _state;
        
        osg::Matrixd                                _projectionMatrix;
        osg::Matrixd                                _viewMatrix;
        
        bool                                        _initCalled;
        osg::ref_ptr<osg::NodeVisitor>              _initVisitor;
        osg::ref_ptr<osg::NodeVisitor>              _updateVisitor;
        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitor;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraph;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStage;

        osg::ref_ptr<ComputeStereoMatricesCallback> _computeStereoMatricesCallback;

        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitorLeft;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraphLeft;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStageLeft;

        osg::ref_ptr<osgUtil::CullVisitor>          _cullVisitorRight;
        osg::ref_ptr<osgUtil::RenderGraph>          _rendergraphRight;
        osg::ref_ptr<osgUtil::RenderStage>          _renderStageRight;

        osg::ref_ptr<osg::CollectOccludersVisitor>  _collectOccludersVisistor;
        
        osg::ref_ptr<osg::FrameStamp>               _frameStamp;
        

        osg::ref_ptr<osg::Node>                     _sceneData;
        osg::ref_ptr<osg::StateSet>                 _globalStateSet;
        osg::ref_ptr<osg::Light>                    _light;
        osg::ref_ptr<osg::DisplaySettings>          _displaySettings;
        
        osg::Vec4                                   _clearColor;


        FusionDistanceMode                          _fusionDistanceMode;
        float                                       _fusionDistanceValue;

        osg::ref_ptr<osg::Viewport>                 _viewport;

        LightingMode                                _lightingMode;
        
        bool                                        _prioritizeTextures;
        
        GLenum                                      _drawBufferValue;
        
        bool                                        _requiresFlush;
        
        int                                         _activeUniforms;        
        double                                      _previousFrameTime;
        
};

}

#endif

