/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 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_CAMERA
#define OSG_CAMERA 1

#include <osg/Transform>
#include <osg/Viewport>
#include <osg/ColorMask>
#include <osg/CullSettings>
#include <osg/Texture>
#include <osg/Image>
#include <osg/GraphicsContext>
#include <osg/Stats>

#include <OpenThreads/Mutex>

namespace osg {

// forward declare View to allow Camera to point back to the View that its within
class View;
class RenderInfo;

/** Camera - is a subclass of Transform which represents encapsulates the settings of a Camera.
*/
class OSG_EXPORT Camera : public Transform, public CullSettings
{
    public :


        Camera();

        /** Copy constructor using CopyOp to manage deep vs shallow copy.*/
        Camera(const Camera&,const CopyOp& copyop=CopyOp::SHALLOW_COPY);

        META_Node(osg, Camera);

        /** Set the View that this Camera is part of. */
        void setView(View* view) { _view = view; }

        /** Get the View that this Camera is part of. */
        View* getView() { return _view; }

        /** Get the const View that this Camera is part of. */
        const View* getView() const { return _view; }


        /** Set the Stats object used for collect various frame related timing and scene graph stats.*/
        void setStats(osg::Stats* stats) { _stats = stats; }

        /** Get the Stats object.*/
        osg::Stats* getStats() { return _stats.get(); }

        /** Get the const Stats object.*/
        const osg::Stats* getStats() const { return _stats.get(); }


        /** Set whether this camera allows events to be generated by the associated graphics window to be associated with this camera.*/
        void setAllowEventFocus(bool focus) { _allowEventFocus = focus; }

        /** Get whether this camera allows events to be generated by the associated graphics window to be associated with this camera.*/
        bool getAllowEventFocus() const { return _allowEventFocus; }


        /** Set the DsplaySettings object associated with this view.*/
        void setDisplaySettings(osg::DisplaySettings* ds) { _displaySettings = ds; }
        
        /** Set the DsplaySettings object associated with this view.*/
        osg::DisplaySettings* getDisplaySettings() { return _displaySettings.get(); }
        
        /** Set the DsplaySettings object associated with this view.*/
        const osg::DisplaySettings* getDisplaySettings() const { return _displaySettings.get(); }


        /** Set the clear mask used in glClear(..).
          * Defaults to GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT. */
        inline void setClearMask(GLbitfield mask) { _clearMask = mask; }

        /** Get the clear mask.*/
        inline GLbitfield getClearMask() const { return _clearMask; }

        /** Set the clear color used in glClearColor(..). 
          * glClearColor is only called if mask & GL_COLOR_BUFFER_BIT is true*/
        void setClearColor(const osg::Vec4& color) { _clearColor=color; applyMaskAction(CLEAR_COLOR); }
        
        /** Get the clear color.*/
        const osg::Vec4& getClearColor() const { return _clearColor; }
        
        /** Set the clear accum used in glClearAccum(..). 
          * glClearAcumm is only called if mask & GL_ACCUM_BUFFER_BIT is true. */
        void setClearAccum(const osg::Vec4& color) { _clearAccum=color; }
        
        /** Get the clear accum value.*/
        const osg::Vec4& getClearAccum() const { return _clearAccum; }
        
        /** Set the clear depth used in glClearDepth(..). Defaults to 1.0
          * glClearDepth is only called if mask & GL_DEPTH_BUFFER_BIT is true. */
        void setClearDepth(double depth) { _clearDepth=depth; }
        
        /** Get the clear depth value.*/
        double getClearDepth() const { return _clearDepth; }

        /** Set the clear stencil value used in glClearStencil(). Defaults to 0;
          * glClearStencil is only called if mask & GL_STENCIL_BUFFER_BIT is true*/
        void setClearStencil(int stencil) { _clearStencil=stencil; }
        
        /** Get the clear stencil value.*/
        int getClearStencil() const { return _clearStencil; }
        
        
        /** Set the color mask of the camera to use specified osg::ColorMask. */
        void setColorMask(osg::ColorMask* colorMask);


        /** Set the color mask of the camera to specified values. */
        void setColorMask(bool red, bool green, bool blue, bool alpha);
        
        /** Get the const ColorMask. */
        const ColorMask* getColorMask() const { return _colorMask.get(); }

        /** Get the ColorMask. */
        ColorMask* getColorMask() { return _colorMask.get(); }


        /** Set the viewport of the camera to use specified osg::Viewport. */
        void setViewport(osg::Viewport* viewport);

        /** Set the viewport of the camera to specified dimensions. */
        void setViewport(int x,int y,int width,int height);
        
        /** Get the const viewport. */
        const Viewport* getViewport() const { return _viewport.get(); }

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


        enum TransformOrder
        {
            PRE_MULTIPLY,
            POST_MULTIPLY  
        };
        
        /** Set the transformation order for world-to-local and local-to-world transformation.*/
        void setTransformOrder(TransformOrder order) { _transformOrder = order; }

        /** Get the transformation order.*/
        TransformOrder getTransformOrder() const { return _transformOrder; }
        
        enum ProjectionResizePolicy
        {
            FIXED, /** Keep the projection matrix fixed, despite window resizes.*/
            HORIZONTAL, /** Adjust the HORIZOTNAL field of view on window resizes.*/
            VERTICAL /** Adjust the VERTICAL field of view on window resizes.*/
        };
        
        /** Set the policy used to determine if and how the projection matrix should be adjusted on window resizes. */
        inline void setProjectionResizePolicy(ProjectionResizePolicy policy) { _projectionResizePolicy = policy; }

        /** Get the policy used to determine if and how the projection matrix should be adjusted on window resizes. */
        inline ProjectionResizePolicy getProjectionResizePolicy() const { return _projectionResizePolicy; }


        /** 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 orthographic 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);  dirtyBound();}
        
        /** 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);  dirtyBound();}

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

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

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

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

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

        /** Get the inverse view matrix.*/
        Matrixd getInverseViewMatrix() const;

        
        enum RenderOrder
        {
            PRE_RENDER,
            NESTED_RENDER,
            POST_RENDER
        };
        
        /** Set the rendering order of this camera's subgraph relative to any camera that this subgraph is nested within.
          * For rendering to a texture, one typically uses PRE_RENDER.
          * For Head Up Displays, one would typically use POST_RENDER.*/
        void setRenderOrder(RenderOrder order, int orderNum = 0) { _renderOrder = order; _renderOrderNum = orderNum; }
        
        /** Get the rendering order of this camera's subgraph relative to any camera that this subgraph is nested within.*/
        RenderOrder getRenderOrder() const { return _renderOrder; }

        /** Get the rendering order number of this camera relative to any sibling cameras in this subgraph.*/
        int getRenderOrderNum() const { return _renderOrderNum; }        

        /** Return true if this Camera is set up as a render to texture camera, i.e. it has textures assigned to it.*/
        bool isRenderToTextureCamera() const;

        enum RenderTargetImplementation
        {
            FRAME_BUFFER_OBJECT,
            PIXEL_BUFFER_RTT,
            PIXEL_BUFFER,
            FRAME_BUFFER,
            SEPERATE_WINDOW
        };

        /** Set the render target.*/
        void setRenderTargetImplementation(RenderTargetImplementation impl);

        /** Set the render target and fall-back that's used if the former isn't available.*/
        void setRenderTargetImplementation(RenderTargetImplementation impl, RenderTargetImplementation fallback);

        /** Get the render target.*/
        RenderTargetImplementation getRenderTargetImplementation() const { return _renderTargetImplementation; }

        /** Get the render target fallback.*/
        RenderTargetImplementation getRenderTargetFallback() const { return _renderTargetFallback; }


        /** Set the draw buffer used at the start of each frame draw. 
          * Note, a buffer value of GL_NONE is used to sepecify that the rendering back-end should choose the most appropriate buffer.*/
        void setDrawBuffer(GLenum buffer) { _drawBuffer = buffer; }

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

        /** Set the read buffer for any required copy operations to use.  
          * Note, a buffer value of GL_NONE is used to sepecify that the rendering back-end should choose the most appropriate buffer.*/
        void setReadBuffer(GLenum buffer) { _readBuffer = buffer; }

        /** Get the read buffer for any required copy operations to use. */
        GLenum getReadBuffer() const { return _readBuffer; }

        enum BufferComponent
        {
            DEPTH_BUFFER,
            STENCIL_BUFFER,
            COLOR_BUFFER,
            COLOR_BUFFER0,
            COLOR_BUFFER1 = COLOR_BUFFER0+1,
            COLOR_BUFFER2 = COLOR_BUFFER0+2,
            COLOR_BUFFER3 = COLOR_BUFFER0+3,
            COLOR_BUFFER4 = COLOR_BUFFER0+4,
            COLOR_BUFFER5 = COLOR_BUFFER0+5,
            COLOR_BUFFER6 = COLOR_BUFFER0+6,
            COLOR_BUFFER7 = COLOR_BUFFER0+7,
            COLOR_BUFFER8 = COLOR_BUFFER0+8,
            COLOR_BUFFER9 = COLOR_BUFFER0+9,
            COLOR_BUFFER10 = COLOR_BUFFER0+10,
            COLOR_BUFFER11 = COLOR_BUFFER0+11,
            COLOR_BUFFER12 = COLOR_BUFFER0+12,
            COLOR_BUFFER13 = COLOR_BUFFER0+13,
            COLOR_BUFFER14 = COLOR_BUFFER0+14,
            COLOR_BUFFER15 = COLOR_BUFFER0+15
        };

        /** Attach a buffer with specified OpenGL internal format.*/
        void attach(BufferComponent buffer, GLenum internalFormat);

        /** Attach a Texture to specified buffer component. 
          * The level parameter controls the mip map level of the texture that is attached. 
          * The face parameter controls the face of texture cube map or z level of 3d texture. 
          * The mipMapGeneration flag controls whether mipmap generation should be done for texture.*/
        void attach(BufferComponent buffer, osg::Texture* texture, unsigned int level = 0, unsigned int face=0, bool mipMapGeneration=false,
            unsigned int multisampleSamples = 0,
            unsigned int multisampleColorSamples = 0);

        /** Attach a Image to specified buffer component.*/
        void attach(BufferComponent buffer, osg::Image* image,
            unsigned int multisampleSamples = 0,
            unsigned int multisampleColorSamples = 0);

        /** Detach specified buffer component.*/
        void detach(BufferComponent buffer);
        
        struct Attachment
        {
            Attachment():
                _internalFormat(GL_NONE),
                _level(0),
                _face(0),
                _mipMapGeneration(false),
                _multisampleSamples(0),
                _multisampleColorSamples(0) {}
        
            int width() const
            {
                if (_texture.valid()) return _texture->getTextureWidth(); 
                if (_image.valid()) return _image->s();
                return 0;
            };

            int height() const
            {
                if (_texture.valid()) return _texture->getTextureHeight(); 
                if (_image.valid()) return _image->t();
                return 0;
            };
        
            int depth() const
            {
                if (_texture.valid()) return _texture->getTextureDepth(); 
                if (_image.valid()) return _image->r();
                return 0;
            };

            GLenum              _internalFormat;
            ref_ptr<Image>      _image;
            ref_ptr<Texture>    _texture;
            unsigned int        _level;
            unsigned int        _face;
            bool                _mipMapGeneration;
            unsigned int        _multisampleSamples;
            unsigned int        _multisampleColorSamples;
        };
        
        typedef std::map< BufferComponent, Attachment> BufferAttachmentMap;
        
        /** Get the BufferAttachmentMap, used to configure frame buffer objects, pbuffers and texture reads.*/
        BufferAttachmentMap& getBufferAttachmentMap() { return _bufferAttachmentMap; }

        /** Get the const BufferAttachmentMap, used to configure frame buffer objects, pbuffers and texture reads.*/
        const BufferAttachmentMap& getBufferAttachmentMap() const { return _bufferAttachmentMap; }


        /** Create a operation thread for this camera.*/
        void createCameraThread();

        /** Assign a operation thread to the camera.*/
        void setCameraThread(OperationThread* gt);

        /** Get the operation thread assigned to this camera.*/
        OperationThread* getCameraThread() { return _cameraThread.get(); }

        /** Get the const operation thread assigned to this camera.*/
        const OperationThread* getCameraThread() const { return _cameraThread.get(); }



        /** Set the GraphicsContext that provides the mechansim for managing the OpenGL graphics context associated with this camera.*/
        void setGraphicsContext(GraphicsContext* context);

        /** Get the GraphicsContext.*/
        GraphicsContext* getGraphicsContext() { return _graphicsContext.get(); }

        /** Get the const GraphicsContext.*/
        const GraphicsContext* getGraphicsContext() const { return _graphicsContext.get(); }
        
        
        /** Set the Rendering object that is used to implement rendering of the subgraph.*/
        void setRenderer(osg::GraphicsOperation* rc) { _renderer = rc; }

        /** Get the Rendering object that is used to implement rendering of the subgraph.*/
        osg::GraphicsOperation* getRenderer() { return _renderer.get(); }

        /** Get the const Rendering object that is used to implement rendering of the subgraph.*/
        const osg::GraphicsOperation* getRenderer() const { return _renderer.get(); }
        

        /** Set the Rendering cache that is used for cached objects associated with rendering of subgraphs.*/
        void setRenderingCache(osg::Object* rc) { _renderingCache = rc; }

        /** Get the Rendering cache that is used for cached objects associated with rendering of subgraphs.*/
        osg::Object* getRenderingCache() { return _renderingCache.get(); }

        /** Get the const Rendering cache that is used for cached objects associated with rendering of subgraphs.*/
        const osg::Object* getRenderingCache() const { return _renderingCache.get(); }
 

        /** Draw callback for custom operations.*/
        struct OSG_EXPORT DrawCallback : virtual public Object
        {
            DrawCallback() {}

            DrawCallback(const DrawCallback&,const CopyOp&) {}

            META_Object(osg, DrawCallback);

            /** Functor method called by rendering thread. Users will typically override this method to carry tasks such as screen capture.*/ 
            virtual void operator () (osg::RenderInfo& renderInfo) const;

            /** Functor method, provided for backwards compatibility, called by operator() (osg::RenderInfo& renderInfo) method.*/ 
            virtual void operator () (const osg::Camera& /*camera*/) const {}
        };
        
        /** Set the initial draw callback for custom operations to be done before the drawing of the camera's subgraph and pre render stages.*/
        void setInitialDrawCallback(DrawCallback* cb) { _initialDrawCallback = cb; }

        /** Get the initial draw callback.*/
        DrawCallback* getInitialDrawCallback() { return _initialDrawCallback.get(); }

        /** Get the const initial draw callback.*/
        const DrawCallback* getInitialDrawCallback() const { return _initialDrawCallback.get(); }
 

        /** Set the pre draw callback for custom operations to be done before the drawing of the camera's subgraph but after any pre render stages have been completed.*/
        void setPreDrawCallback(DrawCallback* cb) { _preDrawCallback = cb; }

        /** Get the pre draw callback.*/
        DrawCallback* getPreDrawCallback() { return _preDrawCallback.get(); }

        /** Get the const pre draw callback.*/
        const DrawCallback* getPreDrawCallback() const { return _preDrawCallback.get(); }
 

        /** Set the post draw callback for custom operations to be done after the drawing of the camera's subgraph but before the any post render stages have been completed.*/
        void setPostDrawCallback(DrawCallback* cb) { _postDrawCallback = cb; }

        /** Get the post draw callback.*/
        DrawCallback* getPostDrawCallback() { return _postDrawCallback.get(); }

        /** Get the const post draw callback.*/
        const DrawCallback* getPostDrawCallback() const { return _postDrawCallback.get(); }
        

        /** Set the final draw callback for custom operations to be done after the drawing of the camera's subgraph and all of the post render stages has been completed.*/
        void setFinalDrawCallback(DrawCallback* cb) { _finalDrawCallback = cb; }

        /** Get the final draw callback.*/
        DrawCallback* getFinalDrawCallback() { return _finalDrawCallback.get(); }

        /** Get the const final draw callback.*/
        const DrawCallback* getFinalDrawCallback() const { return _finalDrawCallback.get(); }

 
        OpenThreads::Mutex* getDataChangeMutex() const { return &_dataChangeMutex; }

        /** Resize any per context GLObject buffers to specified size. */
        virtual void resizeGLObjectBuffers(unsigned int maxSize);

        /** If State is non-zero, this function releases any associated OpenGL objects for
           * the specified graphics context. Otherwise, releases OpenGL objexts
           * for all graphics contexts. */
        virtual void releaseGLObjects(osg::State* = 0) const;

    public:

        /** Transform method that must be defined to provide generic interface for scene graph traversals.*/
        virtual bool computeLocalToWorldMatrix(Matrix& matrix,NodeVisitor*) const;

        /** Transform method that must be defined to provide generic interface for scene graph traversals.*/
        virtual bool computeWorldToLocalMatrix(Matrix& matrix,NodeVisitor*) const;

        /** Inherit the local cull settings variable from specified CullSettings object, according to the inheritance mask.*/
        virtual void inheritCullSettings(const CullSettings& settings, unsigned int inheritanceMask);

    protected :
    
        virtual ~Camera();
        
        mutable OpenThreads::Mutex          _dataChangeMutex;


        View*                               _view;

        osg::ref_ptr<osg::Stats>            _stats;

        bool                                _allowEventFocus;
        
        osg::ref_ptr<osg::DisplaySettings>  _displaySettings;

        GLbitfield                          _clearMask;
        osg::Vec4                           _clearColor;
        osg::Vec4                           _clearAccum;
        double                              _clearDepth;
        int                                 _clearStencil;
        
        ref_ptr<ColorMask>                  _colorMask;
        ref_ptr<Viewport>                   _viewport;

        TransformOrder                      _transformOrder;
        ProjectionResizePolicy              _projectionResizePolicy;

        Matrixd                             _projectionMatrix;
        Matrixd                             _viewMatrix;
        
        RenderOrder                         _renderOrder;
        int                                 _renderOrderNum;
        
        GLenum                              _drawBuffer;
        GLenum                              _readBuffer;
        
        RenderTargetImplementation          _renderTargetImplementation;
        RenderTargetImplementation          _renderTargetFallback;
        BufferAttachmentMap                 _bufferAttachmentMap;

        ref_ptr<OperationThread>            _cameraThread;
        
        ref_ptr<GraphicsContext>            _graphicsContext;
        
        ref_ptr<GraphicsOperation>          _renderer;
        ref_ptr<Object>                     _renderingCache;
        
        ref_ptr<DrawCallback>               _initialDrawCallback;
        ref_ptr<DrawCallback>               _preDrawCallback;
        ref_ptr<DrawCallback>               _postDrawCallback;
        ref_ptr<DrawCallback>               _finalDrawCallback;
};

}

#endif
