// -*-c++-*-
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2021 James Hogan <james@albanarts.com>

#ifndef OSGXR_View
#define OSGXR_View 1

#include <osgXR/Export>

#include <osg/Camera>
#include <osg/Matrix>
#include <osg/ref_ptr>

#include <osgViewer/GraphicsWindow>
#include <osgViewer/View>

namespace osgXR {

/**
 * Representation of a view from an osgXR app point of view.
 * This represents a render view that osgXR expects the application to set up.
 * This may not directly correspond to OpenXR views, for example if using stereo
 * SceneView mode there will be a single view set up for stereo rendering.
 */
class OSGXR_EXPORT View : public osg::Referenced
{
    public:

        /*
         * Application -> osgXR notifications.
         */

        enum eFlags {
            /**
             * The camera should use Multi-View Rendering to render the scene.
             * This indicates that the camera should render the scene using
             * Multi-View rendering, and that osgXR should set it up as
             * appropriate depending on the VR mode, including adjusting the
             * view and projection transformations.
             */
            CAM_MVR_SCENE_BIT = 1,
            /**
             * The camera does shading of MVR scene.
             * This indicates that the camera should render using Multi-View
             * rendering, but does not imply adjustment of the view and
             * projection transformations, so is useful for purely shading
             * passes. It also makes shader definitions available relating to
             * texture coordinate transformations, so can be used in combination
             * with CAM_MVR_SCENE_BIT when those macros are required.
             */
            CAM_MVR_SHADING_BIT = 2,
            /**
             * The camera should render to OpenXR.
             * This indicates that the camera should render to the OpenXR
             * swapchain image rather than to the screen.
             */
            CAM_TOXR_BIT = 4,
            /**
             * The render buffer has fixed width per view.
             * This indicates that viewports should be configured with equal
             * widths.
             */
            CAM_MVR_FIXED_WIDTH_BIT = 8,
            /**
             * The render buffer has fixed height per view.
             * This indicates that viewports should be configured with equal
             * heights.
             */
            CAM_MVR_FIXED_HEIGHT_BIT = 16,

            CAM_NO_BITS = 0,
            CAM_DEFAULT_BITS = CAM_MVR_SCENE_BIT | CAM_TOXR_BIT,
        };
        typedef unsigned int Flags;

        /**
         * Notify osgXR that a new slave camera has been added to the view.
         * This tells osgXR that a new slave camera has been added to the view
         * which it should hook into so that it renders to the appropriate
         * texture and submits for XR display.
         */
        virtual void addSlave(osg::Camera *slaveCamera,
                              Flags flags = CAM_DEFAULT_BITS) = 0;

        /**
         * Notify osgXR that a slave camera is being removed from the view.
         * This tells osgXR when a slave camera previously notified with
         * addSlave() is being removed.
         */
        virtual void removeSlave(osg::Camera *slaveCamera) = 0;

        /*
         * osgXR -> Application Notifications.
         */

        /**
         * Interface for application to access individual subviews of an osgXR View.
         * Each of these directly corresponds to a single OpenXR view.
         */
        class OSGXR_EXPORT SubView
        {
            public:
                struct Viewport
                {
                    double x, y, w, h;
                };
                /// Get the array index / layer this MVR view uses in the FBO.
                virtual unsigned int getArrayIndex() const = 0;
                /// Get the viewport in pixels this MVR view uses in the FBO.
                virtual Viewport getViewport() const = 0;
                /// Get the local view matrix.
                virtual const osg::Matrix &getViewMatrix() const = 0;
                /// Get the projection matrix.
                virtual const osg::Matrix &getProjectionMatrix() const = 0;
        };

        /// Interface for application to be notified of view changes.
        class OSGXR_EXPORT Callback : public osg::Referenced
        {
            public:
                /**
                 * Notification that a subview has been determined.
                 * The application may use this to update uniforms to reflect
                 * the latest subview position.
                 */
                virtual void updateSubView(View *view,
                                           unsigned int subviewIndex,
                                           const SubView &subview);
        };

        /// Set the callback.
        void setCallback(Callback *cb)
        {
            _callback = cb;
        }

        // Get the callback.
        Callback *getCallback()
        {
            return _callback.get();
        }

        // Get the const callback.
        const Callback *getCallback() const
        {
            return _callback.get();
        }

        /*
         * Accessors.
         */

        /// Get the OSG GraphicsWindow associated with this osgXR view.
        const osgViewer::GraphicsWindow *getWindow() const
        {
            return _window;
        }

        /// Get the OSG View associated with this osgXR view.
        const osgViewer::View *getView() const
        {
            return _osgView;
        }

        /// Get the width in pixels screen width MVR framebuffer should be.
        virtual unsigned int getMVRWidth() const = 0;
        /// Get the height in pixels screen height MVR framebuffer should be.
        virtual unsigned int getMVRHeight() const = 0;

        /// Get the number of MVR views for view-specific app uniform arrays.
        virtual unsigned int getMVRViews() const = 0;
        /// Get the string to declare MVR view uniforms in global scope.
        virtual std::string getMVRViewIdGlobalStr() const = 0;
        /**
         * Get the string to index MVR view uniform arrays in shader stage.
         * @param[in]  stage    OpenGL shading stage (GL_VERTEX_SHADER,
         *                      GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER).
         * @returns GLSL string, possibly with GLSL extension enabler suffix,
         *          possibly depending on use of getMVRViewIdGlobalStr().
         */
        virtual std::string getMVRViewIdStr(GLenum stage) const = 0;

        /**
         * Get the number of 2D cells fixed size MVR framebuffers should have.
         * If > 1:
         * CellID = ViewID
         * LayerID = 0
         */
        virtual unsigned int getMVRCells() const = 0;

        /**
         * Get the number of layers MVR framebuffer textures should have.
         * If > 1:
         * CellID = 0
         * LayerID = ViewID
         */
        virtual unsigned int getMVRLayers() const = 0;
        /// Get the OSG face number to use for MVR attachments.
        virtual unsigned int getMVRAttachmentFace() const = 0;
        /**
         * Get the string to specify MVR layer number in shader stage.
         * @param[in]  stage    OpenGL shading stage (GL_VERTEX_SHADER,
         *                      GL_GEOMETRY_SHADER or GL_FRAGMENT_SHADER).
         * @returns GLSL string, possibly with GLSL extension enabler suffix.
         */
        virtual std::string getMVRLayerStr(GLenum stage) const = 0;

    protected:

        /*
         * Internal
         */

        View(osgViewer::GraphicsWindow *window, osgViewer::View *osgView);
        virtual ~View();

        osg::ref_ptr<osgViewer::GraphicsWindow> _window;
        osg::ref_ptr<osgViewer::View> _osgView;

        osg::ref_ptr<Callback> _callback;
};

}

#endif
