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

#ifndef OSGXR_Manager
#define OSGXR_Manager 1

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

#include <osgViewer/View>
#include <osgViewer/ViewerBase>

#include <osgXR/Export>
#include <osgXR/Mirror>
#include <osgXR/Settings>
#include <osgXR/View>

#include <list>

namespace osgXR {

// Internal state class
class XRState;

/**
 * Public VR state manager class.
 * Applications can extend this class to allow tighter integration with osgXR.
 */
class OSGXR_EXPORT Manager : public osgViewer::ViewConfig
{
    public:

        Manager();
        virtual ~Manager();

        /// Use if viewer is a CompositeViewer.
        void setViewer(osgViewer::ViewerBase *viewer)
        {
            _viewer = viewer;
        }

        /// Set the NodeMasks to use for visibility masks.
        void setVisibilityMaskNodeMasks(osg::Node::NodeMask left,
                                        osg::Node::NodeMask right) const;

        void configure(osgViewer::View& view) const override;

        /**
         * Perform a regular update.
         * This will poll for OpenXR events, and handle any pending VR start /
         * stop operations (possibly invoking the Manager's view callbacks).
         * Some of these operations require threading on the viewer to be
         * temporarily stopped, but in all cases it is started again.
         */
        virtual void update();

        /// Find whether state has changed since last call, and reset.
        bool checkAndResetStateChanged();

        /// Find whether VR seems to be present.
        bool getPresent() const;

        /**
         * Get whether VR is currently set to be enabled.
         * When enabled, osgXR will try to keep VR running.
         * @return Whether VR is enabled
         */
        bool getEnabled() const;
        /**
         * Set whether VR is currently set to be enabled.
         * When enabled, osgXR will try to keep VR running.
         * @param enabled Whether VR is enabled.
         */
        void setEnabled(bool enabled);

        /**
         * Start destroying the VR state and wait for safe shutdown.
         */
        void destroyAndWait();

        /**
         * Find whether this manager is in the process of being destroyed.
         */
        bool isDestroying() const;

        /**
         * Get whether a VR session is currently running.
         * @return Whether a VR session is currently running.
         */
        bool isRunning() const;

        /// Arrange reinit as needed for new settings.
        void syncSettings();

        /// Arrange reinit as needed of action setup.
        void syncActionSetup();

        /*
         * OpenXR information.
         */

        /**
         * Find whether OpenXR's validation layer is supported.
         * This looks to see whether the OpenXR validation API layer (i.e.
         * XR_APILAYER_LUNARG_core_validation) is available.
         */
        bool hasValidationLayer() const;

        /**
         * Find whether OpenXR supports the submission of depth information.
         * This looks to see whether the OpenXR instance extension for
         * submitting depth information to help the runtime perform better
         * reprojection (i.e. XR_KHR_composition_layer_depth) is available.
         */
        bool hasDepthInfoExtension() const;

        /**
         * Find whether OpenXR supports the visibility mask extension.
         * This looks to see whether the OpenXR instance extension for getting
         * visibility masks is available, which can be used to reduce fragment
         * load.
         */
        bool hasVisibilityMaskExtension() const;

        /// Find the name of the OpenXR runtime.
        const char *getRuntimeName() const;

        /// Find the name of the OpenXR system in use.
        const char *getSystemName() const;

        /// Get a string describing the state (for user consumption).
        const char *getStateString() const;

        /*
         * For implementation by derived classes.
         */

        /**
         * Callback telling the app to configure a new view.
         * This callback allows osgXR to tell the app to configure a new view of
         * the world. The application should notify osgXR of the addition and
         * removal of slave cameras which osgXR should hook into using the
         * osgXR::View parameter.
         * The implementation may stop threading, and it will be started again
         * before update() returns.
         * @param xrView The new osgXR::View with a public API to allow the
         *               application to retrieve what it needs in relation to
         *               the view and to inform osgXR of changes.
         */
        virtual void doCreateView(View *xrView) = 0;

        /**
         * Callback telling the app to destroy an existing view.
         * This callback allows osgXR to tell the app to remove an existing view
         * of the world that it had requested via doCreateView(). The
         * application should notify osgXR of the removal of any slave cameras
         * which it has already informed osgXR about.
         * The implementation may stop threading, and it will be started again
         * before update() returns.
         */
        virtual void doDestroyView(View *xrView) = 0;

        /**
         * Callback telling the app that the VR session is now running.
         * This happens after the OpenXR session has started running, and views
         * have been configured (see doCreateView()). The app should start
         * rendering the VR views, and may choose to reconfigure the desktop
         * window to make a VR mirror visible.
         */
        virtual void onRunning();

        /**
         * Callback telling the app that the VR session has now stopped.
         * This happens after the OpenXR session has stopped, and views have
         * been removed (see doDestroyView()). The app should stop rendering the
         * VR views, and may choose to reconfigure the desktop window so as to
         * no longer show a VR mirror.
         */
        virtual void onStopped();

        /**
         * Callback telling the app that the VR session is in focus.
         * This happens when the VR session enters focus and can get VR input
         * from the user. The app may choose to resume the experience if it was
         * previously paused due to onUnfocus().
         */
        virtual void onFocus();

        /**
         * Callback telling the app that the VR session is no longer in focus.
         * This happens when the VR session leaves focus and can no longer get
         * VR input from the user. The VR runtime may be presenting a modal
         * pop-up on top of the application's rendered frames. The app may
         * choose to pause the experience.
         */
        virtual void onUnfocus();


        /// Add a custom mirror to the queue of mirrors to configure.
        void addMirror(Mirror *mirror);

        /// Set up a camera to render a VR mirror.
        void setupMirrorCamera(osg::Camera *camera);

        /*
         * Internal
         */

        inline Settings *_getSettings()
        {
            return _settings.get();
        }

        inline XRState *_getXrState()
        {
            return _state;
        }

        void _setupMirrors();

    protected:

        osg::ref_ptr<osgViewer::ViewerBase> _viewer;
        osg::ref_ptr<Settings> _settings;
        bool _destroying;

    private:

        std::list<osg::ref_ptr<Mirror> > _mirrorQueue;
        osg::ref_ptr<XRState> _state;
};

}

#endif
