/*
 * Copyright (C) 2019 Open Source Robotics Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
*/

#ifndef IGNITION_GAZEBO_GUI_SCENE3D_HH_
#define IGNITION_GAZEBO_GUI_SCENE3D_HH_

#include <ignition/msgs/boolean.pb.h>
#include <ignition/msgs/gui_camera.pb.h>
#include <ignition/msgs/stringmsg.pb.h>
#include <ignition/msgs/vector3d.pb.h>
#include <ignition/msgs/video_record.pb.h>

#include <string>
#include <memory>
#include <mutex>

#include <sdf/Root.hh>

#include <ignition/math/Color.hh>
#include <ignition/math/Pose3.hh>
#include <ignition/math/Vector2.hh>
#include <ignition/math/Vector3.hh>

#include <ignition/common/MouseEvent.hh>
#include <ignition/common/KeyEvent.hh>

#include <ignition/rendering/Camera.hh>

#include <ignition/gazebo/gui/GuiSystem.hh>

#include "ignition/gui/qt.h"


namespace ignition
{
namespace gazebo
{
// Inline bracket to help doxygen filtering.
inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE {
  class IgnRendererPrivate;
  class RenderWindowItemPrivate;
  class Scene3DPrivate;
  class RenderUtil;

  /// \brief Creates a new ignition rendering scene or adds a user-camera to an
  /// existing scene. It is possible to orbit the camera around the scene with
  /// the mouse. Use other plugins to manage objects in the scene.
  ///
  /// ## Configuration
  ///
  /// * \<engine\> : Optional render engine name, defaults to 'ogre'.
  /// * \<scene\> : Optional scene name, defaults to 'scene'. The plugin will
  ///               create a scene with this name if there isn't one yet. If
  ///               there is already one, a new camera is added to it.
  /// * \<ambient_light\> : Optional color for ambient light, defaults to
  ///                       (0.3, 0.3, 0.3, 1.0)
  /// * \<background_color\> : Optional background color, defaults to
  ///                          (0.3, 0.3, 0.3, 1.0)
  /// * \<camera_pose\> : Optional starting pose for the camera, defaults to
  ///                     (0, 0, 5, 0, 0, 0)
  /// * \<camera_follow\> :
  ///     * \<p_gain\>    : Camera follow movement p gain.
  ///     * \<target\>    : Target to follow.
  /// * \<fullscreen\> : Optional starting the window in fullscreen.
  class Scene3D : public ignition::gazebo::GuiSystem
  {
    Q_OBJECT

    /// \brief Constructor
    public: Scene3D();

    /// \brief Destructor
    public: ~Scene3D() override;

    // Documentation inherited
    public: void LoadConfig(const tinyxml2::XMLElement *_pluginElem) override;

    // Documentation inherited
    public: void Update(const UpdateInfo &_info,
        EntityComponentManager &_ecm) override;

    /// \brief Callback when receives a drop event.
    /// \param[in] _drop Dropped string.
    /// \param[in] _mouseX x coordinate of mouse position.
    /// \param[in] _mouseY y coordinate of mouse position.
    public slots: void OnDropped(const QString &_drop,
        int _mouseX, int _mouseY);

    /// \brief Callback when the mouse hovers to a new position.
    /// \param[in] _mouseX x coordinate of the hovered mouse position.
    /// \param[in] _mouseY y coordinate of the hovered mouse position.
    public slots: void OnHovered(int _mouseX, int _mouseY);

    /// \brief Callback when the mouse enters the render window to
    /// focus the window for mouse/key events
    public slots: void OnFocusWindow();

    // Documentation inherited
    protected: bool eventFilter(QObject *_obj, QEvent *_event) override;

    /// \brief Callback for a transform mode request
    /// \param[in] _msg Request message to set a new transform mode
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnTransformMode(const msgs::StringMsg &_msg,
        msgs::Boolean &_res);

    /// \brief Callback for a record video request
    /// \param[in] _msg Request message to enable/disable video recording.
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnRecordVideo(const msgs::VideoRecord &_msg,
        msgs::Boolean &_res);

    /// \brief Callback for a move to request
    /// \param[in] _msg Request message to set the target to move to.
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnMoveTo(const msgs::StringMsg &_msg,
        msgs::Boolean &_res);

    /// \brief Callback for a follow request
    /// \param[in] _msg Request message to set the target to follow.
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnFollow(const msgs::StringMsg &_msg,
        msgs::Boolean &_res);

    /// \brief Callback for a view angle request
    /// \param[in] _msg Request message to set the camera to.
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnViewAngle(const msgs::Vector3d &_msg,
        msgs::Boolean &_res);

    /// \brief Callback for a move to pose request.
    /// \param[in] _msg GUICamera request message.
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnMoveToPose(const msgs::GUICamera &_msg,
                 msgs::Boolean &_res);

    /// \brief Callback for view collisions request
    /// \param[in] _msg Request message to set the target to view collisions
    /// \param[in] _res Response data
    /// \return True if the request is received
    private: bool OnViewCollisions(const msgs::StringMsg &_msg,
        msgs::Boolean &_res);

    /// \internal
    /// \brief Pointer to private data.
    private: std::unique_ptr<Scene3DPrivate> dataPtr;
  };

  /// \brief Ign-rendering renderer.
  /// All ign-rendering calls should be performed inside this class as it makes
  /// sure that opengl calls in the underlying render engine do not interfere
  /// with QtQuick's opengl render operations. The main Render function will
  /// render to an offscreen texture and notify via signal and slots when it's
  /// ready to be displayed.
  class IgnRenderer : public QObject
  {
    Q_OBJECT

    ///  \brief Constructor
    public: IgnRenderer();

    ///  \brief Destructor
    public: ~IgnRenderer() override;

    ///  \brief Main render function
    public: void Render();

    /// \brief Initialize the render engine
    public: void Initialize();

    /// \brief Destroy camera associated with this renderer
    public: void Destroy();

    /// \brief Set the renderer
    public: class RenderUtil *RenderUtil() const;

    /// \brief Set the transform mode
    /// \param[in] _mode New transform mode to set to
    public: void SetTransformMode(const std::string &_mode);

    /// \brief Set the model to hover over the scene.
    /// \param[in] _model Sdf string of the model to load in for the user.
    public: void SetModel(const std::string &_model);

    /// \brief Set the path to the model to hover over the scene.
    /// \param[in] _filePath Sdf path of the model to load in for the user.
    public: void SetModelPath(const std::string &_filePath);

    /// \brief Set if the dropdown menu is enabled or disabled.
    /// \param[in] _enableDropdownMenu The boolean to enable or disable
    /// the dropdown menu
    public: void SetDropdownMenuEnabled(bool _enableDropdownMenu);

    /// \brief Set whether to record video
    /// \param[in] _record True to start video recording, false to stop.
    /// \param[in] _format Video encoding format: "mp4", "ogv"
    /// \param[in] _savePath Path to save the recorded video.
    public: void SetRecordVideo(bool _record, const std::string &_format,
        const std::string &_savePath);

    /// \brief Set whether to record video using sim time as timestamp
    /// \param[in] _true True record video using sim time
    public: void SetRecordVideoUseSimTime(bool _useSimTime);

    /// \brief Set whether to record video in lockstep mode
    /// \param[in] _true True to record video in lockstep mode
    public: void SetRecordVideoLockstep(bool _lockstep);

    /// \brief Set video recorder bitrate in bps
    /// \param[in] _bitrate Bit rate to set to
    public: void SetRecordVideoBitrate(unsigned int _bitrate);

    /// \brief Move the user camera to move to the speficied target
    /// \param[in] _target Target to move the camera to
    public: void SetMoveTo(const std::string &_target);

    /// \brief Move the user camera to follow the speficied target
    /// \param[in] _target Target to follow
    /// \param[in] _waitForTarget True to continuously look for the target
    /// to follow. A typical use case is when following a target that is not
    ///  present on startup but spawned later into simulation
    public: void SetFollowTarget(const std::string &_target,
        bool _waitForTarget = false);

    /// \brief Set the viewing angle of the camera
    /// \param[in] _direction The pose to assume relative to the entit(y/ies).
    /// (0, 0, 0) indicates to return the camera back to the home pose
    /// originally loaded in from the sdf
    public: void SetViewAngle(const math::Vector3d &_direction);

    /// \brief Set the world pose of the camera
    /// \param[in] _pose The world pose to set the camera to.
    public: void SetMoveToPose(const math::Pose3d &_pose);

    /// \brief View collisions of the specified target
    /// \param[in] _target Target to view collisions
    public: void SetViewCollisionsTarget(const std::string &_target);

    /// \brief Set the p gain for the camera follow movement
    /// \param[in] _gain Camera follow p gain.
    public: void SetFollowPGain(double _gain);

    /// \brief True to set the camera to follow the target in world frame,
    /// false to follow in target's local frame
    /// \param[in] _gain Camera follow p gain.
    public: void SetFollowWorldFrame(bool _worldFrame);

    /// \brief Set the camera follow offset position
    /// \param[in] _offset Camera follow offset position.
    public: void SetFollowOffset(const math::Vector3d &_offset);

    /// \brief Get the target which the user camera is following
    /// \return Target being followed
    public: std::string FollowTarget() const;

    /// \brief Get whether the camera is following the entity in world frame.
    /// \return True if follow mode is in world frame, false if local frame
    public: bool FollowWorldFrame() const;

    /// \brief Get the camera follow offset position
    /// \return Camera follow offset position.
    public: math::Vector3d FollowOffset() const;

    /// \brief Set the initial user camera pose
    /// \param[in] _pose Pose to set the camera to
    public: void SetInitCameraPose(const math::Pose3d &_pose);

    /// \brief New mouse event triggered
    /// \param[in] _e New mouse event
    /// \param[in] _drag Mouse move distance
    public: void NewMouseEvent(const common::MouseEvent &_e,
        const math::Vector2d &_drag = math::Vector2d::Zero);

    /// \brief New hover event triggered.
    /// \param[in] _hoverPos Mouse hover screen position
    public: void NewHoverEvent(const math::Vector2i &_hoverPos);

    /// \brief Handle key press event for snapping
    /// \param[in] _e The key event to process.
    public: void HandleKeyPress(QKeyEvent *_e);

    /// \brief Handle key release event for snapping
    /// \param[in] _e The key event to process.
    public: void HandleKeyRelease(QKeyEvent *_e);

    /// \brief Set the XYZ snap values.
    /// \param[in] _xyz The XYZ snap values
    public: void SetXYZSnap(const math::Vector3d &_xyz);

    /// \brief Get the XYZ snap values.
    /// \return XYZ snapping values as a Vector3d
    public: math::Vector3d XYZSnap() const;

    /// \brief Set the RPY snap values.
    /// \param[in] _rpy The RPY snap values
    public: void SetRPYSnap(const math::Vector3d &_rpy);

    /// \brief Get the RPY snap values.
    /// \return RPY snapping values as a Vector3d
    public: math::Vector3d RPYSnap() const;

    /// \brief Set the scale snap values.
    /// \param[in] _scale The scale snap values
    public: void SetScaleSnap(const math::Vector3d &_scale);

    /// \brief Get the scale snap values.
    /// \return Scale snapping values as a Vector3d
    public: math::Vector3d ScaleSnap() const;

    /// \brief Snaps a point at intervals of a fixed distance. Currently used
    /// to give a snapping behavior when moving models with a mouse.
    /// \param[in] _point Input point to snap.
    /// \param[in] _snapVals The snapping values to use for each corresponding
    /// coordinate in _point
    /// \param[in] _sensitivity Sensitivity of a point snapping, in terms of a
    /// percentage of the interval.
    public: void SnapPoint(
                math::Vector3d &_point,
                math::Vector3d &_snapVals, double _sensitivity = 0.4) const;

    /// \brief Request entity selection. This queues the selection to be handled
    /// later in the render thread.
    /// \param[in] _selectedEntity Entity to be selected, or `kNullEntity`.
    /// \param[in] _deselectAll True if all entities should be deselected.
    /// \param[in] _sendEvent True to emit an event to other widgets.
    public: void RequestSelectionChange(Entity _selectedEntity,
        bool _deselectAll, bool _sendEvent);

    /// \brief Snaps a value at intervals of a fixed distance. Currently used
    /// to give a snapping behavior when moving models with a mouse.
    /// \param[in] _coord Input coordinate point.
    /// \param[in] _interval Fixed distance interval at which the point is
    /// snapped.
    /// \param[in] _sensitivity Sensitivity of a point snapping, in terms of a
    /// percentage of the interval.
    /// \return Snapped coordinate point.
    private: double SnapValue(
                 double _coord, double _interval, double _sensitivity) const;

    /// \brief Constraints the passed in axis to the currently selected axes.
    /// \param[in] _axis The axis to constrain.
    private: void XYZConstraint(math::Vector3d &_axis);

    /// \brief Handle mouse events
    private: void HandleMouseEvent();

    /// \brief Handle mouse event for context menu
    private: void HandleMouseContextMenu();

    /// \brief Handle mouse event for view control
    private: void HandleMouseViewControl();

    /// \brief Handle mouse event for transform control
    private: void HandleMouseTransformControl();

    /// \brief Handle entity selection requests
    private: void HandleEntitySelection();

    /// \brief Handle model placement requests
    private: void HandleModelPlacement();

    /// \brief Broadcasts the currently hovered 3d scene location.
    private: void BroadcastHoverPos();

    /// \brief Broadcasts a left click within the scene
    private: void BroadcastLeftClick();

    /// \brief Broadcasts a right click within the scene
    private: void BroadcastRightClick();

    /// \brief Generate a unique entity id.
    /// \return The unique entity id
    private: Entity UniqueId();

    /// \brief Generate a preview of a resource.
    /// \param[in] _sdf The SDF to be previewed.
    /// \return True on success, false if failure
    public: bool GeneratePreview(const sdf::Root &_sdf);

    /// \brief Delete the visuals generated while an entity is being spawned.
    public: void TerminateSpawnPreview();

    /// \brief Retrieve the point on a plane at z = 0 in the 3D scene hit by a
    /// ray cast from the given 2D screen coordinates.
    /// \param[in] _screenPod 2D coordinates on the screen, in pixels.
    /// \return 3D coordinates of a point in the 3D scene.
    public: math::Vector3d ScreenToPlane(const math::Vector2i &_screenPos)
        const;

    /// \brief Retrieve the first point on a surface in the 3D scene hit by a
    /// ray cast from the given 2D screen coordinates.
    /// \param[in] _screenPos 2D coordinates on the screen, in pixels.
    /// \return 3D coordinates of a point in the 3D scene.
    public: math::Vector3d ScreenToScene(const math::Vector2i &_screenPos)
        const;

    /// \brief Get the current camera pose.
    /// \return Pose of the camera.
    public: math::Pose3d CameraPose() const;

    /// \brief Callback when a move to animation is complete
    private: void OnMoveToComplete();

    /// \brief Callback when a view angle animation is complete
    private: void OnViewAngleComplete();

    /// \brief Callback when a move to  pose animation is complete
    private: void OnMoveToPoseComplete();

    /// \brief Process a node's selection
    /// \param[in] _node The node to be selected.
    /// \param[in] _sendEvent True to notify other widgets. This should be true
    /// when the selection is initiated from this plugin.
    private: void UpdateSelectedEntity(const rendering::NodePtr &_node,
        bool _sendEvent);

    /// \brief Deselect all entities.
    /// \param[in] _sendEvent True to notify other widgets. This should be true
    /// when the deselection is initiated from this plugin.
    private: void DeselectAllEntities(bool _sendEvent);

    /// \brief Signal fired when context menu event is triggered
    signals: void ContextMenuRequested(QString _entity);

    /// \brief When fired, the follow target changed. May not be fired for
    /// every target change.
    /// \param[in] _target Target to follow
    /// \param[in] _waitForTarget True to continuously look for the target
    signals: void FollowTargetChanged(const std::string &_target,
        bool _waitForTarget);

    /// \brief Render texture id
    public: GLuint textureId = 0u;

    /// \brief Initial Camera pose
    public: math::Pose3d cameraPose = math::Pose3d(0, 0, 2, 0, 0.4, 0);

    /// \brief Name of the world
    public: std::string worldName;

    /// \brief Camera visibility mask
    public: uint32_t visibilityMask = 0xFFFFFFFFu;

    /// \brief True if engine has been initialized;
    public: bool initialized = false;

    /// \brief Render texture size
    public: QSize textureSize = QSize(1024, 1024);

    /// \brief Flag to indicate texture size has changed.
    public: bool textureDirty = false;

    /// \internal
    /// \brief Pointer to private data.
    private: std::unique_ptr<IgnRendererPrivate> dataPtr;
  };

  /// \brief Rendering thread
  class RenderThread : public QThread
  {
    Q_OBJECT

    /// \brief Constructor
    public: RenderThread();

    /// \brief Render the next frame
    public slots: void RenderNext();

    /// \brief Shutdown the thread and the render engine
    public slots: void ShutDown();

    /// \brief Slot called to update render texture size
    public slots: void SizeChanged();

    /// \brief Signal to indicate that a frame has been rendered and ready
    /// to be displayed
    /// \param[in] _id GLuid of the opengl texture
    /// \param[in] _size Size of the texture
    signals: void TextureReady(int _id, const QSize &_size);

    /// \brief Offscreen surface to render to
    public: QOffscreenSurface *surface = nullptr;

    /// \brief OpenGL context to be passed to the render engine
    public: QOpenGLContext *context = nullptr;

    /// \brief Ign-rendering renderer
    public: IgnRenderer ignRenderer;
  };


  /// \brief A QQUickItem that manages the render window
  class RenderWindowItem : public QQuickItem
  {
    Q_OBJECT

    /// \brief Constructor
    /// \param[in] _parent Parent item
    public: explicit RenderWindowItem(QQuickItem *_parent = nullptr);

    /// \brief Destructor
    public: ~RenderWindowItem() override;

    /// \brief Set the renderer
    public: class RenderUtil *RenderUtil() const;

    /// \brief Set the initial user camera pose
    /// \param[in] _pose Pose to set the camera to
    public: void SetCameraPose(const math::Pose3d &_pose);

    /// \brief Get the user camera pose.
    /// \return Pose of the user camera.
    public: math::Pose3d CameraPose() const;

    /// \brief Set the initial user camera pose
    /// \param[in] _pose Pose to set the camera to
    public: void SetInitCameraPose(const math::Pose3d &_pose);

    /// \brief Set the user camera visibility mask
    /// \param[in] _mask Visibility mask to set to
    public: void SetVisibilityMask(uint32_t _mask);

    /// \brief Set the transform mode
    /// \param[in] _mode New transform mode to set to
    public: void SetTransformMode(const std::string &_mode);

    /// \brief Set the model to hover.
    /// \param[in] _model Sdf string of the model to load in for the user.
    public: void SetModel(const std::string &_model);

    /// \brief Set the path of the model to hover.
    /// \param[in] _filePath File path of the model to load in for the user.
    public: void SetModelPath(const std::string &_filePath);

    /// \brief Set if the dropdown menu is enabled or disabled.
    /// \param[in] _enableDropdownMenu The boolean to enable or disable
    /// the menu
    public: void SetDropdownMenuEnabled(bool _enableDropdownMenu);

    /// \brief Set whether to record video
    /// \param[in] _record True to start video recording, false to stop.
    /// \param[in] _format Video encoding format: "mp4", "ogv"
    /// \param[in] _savePath Path to save the recorded video.
    public: void SetRecordVideo(bool _record, const std::string &_format,
        const std::string &_savePath);

    /// \brief Set whether to record video using sim time as timestamp
    /// \param[in] _true True record video using sim time
    public: void SetRecordVideoUseSimTime(bool _useSimTime);

    /// \brief Set whether to record video in lockstep mode
    /// \param[in] _true True to record video in lockstep mode
    public: void SetRecordVideoLockstep(bool _lockstep);

    /// \brief Set video recorder bitrate in bps
    /// \param[in] _bitrate Bit rate to set to
    public: void SetRecordVideoBitrate(unsigned int _bitrate);

    /// \brief Move the user camera to move to the specified target
    /// \param[in] _target Target to move the camera to
    public: void SetMoveTo(const std::string &_target);

    /// \brief Move the user camera to follow the specified target
    /// \param[in] _target Target to follow
    /// \param[in] _waitForTarget True to continuously look for the target
    /// to follow. A typical use case is follow a target that is not present
    /// on startup but spawned later into simulation
    public Q_SLOTS: void SetFollowTarget(const std::string &_target,
        bool _waitForTarget = false);

    /// \brief Set the viewing angle of the camera
    /// \param[in] _direction The pose to assume relative to the entit(y/ies).
    /// (0, 0, 0) indicates to return the camera back to the home pose
    /// originally loaded in from the sdf
    public: void SetViewAngle(const math::Vector3d &_direction);

    /// \brief Set the pose of the camera
    /// \param[in] _pose The new camera pose in the world frame.
    public: void SetMoveToPose(const math::Pose3d &_pose);

    /// \brief View collisions of the specified target
    /// \param[in] _target Target to view collisions
    public: void SetViewCollisionsTarget(const std::string &_target);

    /// \brief Set the p gain for the camera follow movement
    /// \param[in] _gain Camera follow p gain.
    public: void SetFollowPGain(double _gain);

    /// \brief True to set the camera to follow the target in world frame,
    /// false to follow in target's local frame
    /// \param[in] _gain Camera follow p gain.
    public: void SetFollowWorldFrame(bool _worldFrame);

    /// \brief Set the camera follow offset position
    /// \param[in] _offset Camera follow offset position.
    public: void SetFollowOffset(const math::Vector3d &_offset);

    /// \brief Set the world name
    /// \param[in] _name Name of the world to set to.
    public: void SetWorldName(const std::string &_name);

    /// \brief An update function to apply the rules of selection to the
    /// passed in node. The rules are as follows:
    /// - If control is held, append the node to the selected entity list.
    /// - If control is not held, deselect all other entities.
    /// - If the user is currently in a transform mode, attach the control
    ///   to the latest selected node and deselect all others.
    /// Note that this function emits events to update other widgets.
    /// \param[in] _entity The entity to select.
    /// \param[in] _sendEvent True to notify other widgets. This should be true
    /// when the selection is initiated from this plugin.
    public: void UpdateSelectedEntity(Entity _entity,
        bool _sendEvent);

    /// \brief Deselect all the currently selected entities within
    /// the RenderUtil class.
    /// \param[in] _sendEvent True to notify other widgets. This should be true
    /// when the deselection is initiated from this plugin.
    public: void DeselectAllEntities(bool _sendEvent);

    /// \brief Set the XYZ snap values from the user input.
    /// \param[in] _xyz The XYZ snap values
    public: void SetXYZSnap(const math::Vector3d &_xyz);

    /// \brief Set the RPY snap values from the user input.
    /// \param[in] _rpy The RPY snap values
    public: void SetRPYSnap(const math::Vector3d &_rpy);

    /// \brief Set the scale snap values from the user input.
    /// \param[in] _scale The scale snap values
    public: void SetScaleSnap(const math::Vector3d &_scale);

    /// \brief Retrieve the first point on a surface in the 3D scene hit by a
    /// ray cast from the given 2D screen coordinates.
    /// \param[in] _screenPos 2D coordinates on the screen, in pixels.
    /// \return 3D coordinates of a point in the 3D scene.
    public: math::Vector3d ScreenToScene(const math::Vector2i &_screenPos);

    /// \brief Called when the mouse hovers to a new position.
    /// \param[in] _hoverPos 2D coordinates of the hovered mouse position on
    /// the render window.
    public: void OnHovered(const ignition::math::Vector2i &_hoverPos);

    /// \brief Get whether the renderer is initialized. The renderer is
    /// initialized when the context is created and the render thread is
    /// started.
    /// \return True if the renderer is initialized.
    public: bool RendererInitialized() const;

    /// \brief Slot called when thread is ready to be started
    public Q_SLOTS: void Ready();

    /// \brief Handle key press event for snapping
    /// \param[in] _e The key event to process.
    public: void HandleKeyPress(QKeyEvent *_e);

    /// \brief Handle key release event for snapping
    /// \param[in] _e The key event to process.
    public: void HandleKeyRelease(QKeyEvent *_e);

    // Documentation inherited
    protected: void mousePressEvent(QMouseEvent *_e) override;

    // Documentation inherited
    protected: void mouseReleaseEvent(QMouseEvent *_e) override;

    // Documentation inherited
    protected: void mouseMoveEvent(QMouseEvent *_e) override;

    // Documentation inherited
    protected: void wheelEvent(QWheelEvent *_e) override;

    /// \brief Overrides the paint event to render the render engine
    /// camera view
    /// \param[in] _oldNode The node passed in previous updatePaintNode
    /// function. It represents the visual representation of the item.
    /// \param[in] _data The node transformation data.
    /// \return Updated node.
    private: QSGNode *updatePaintNode(QSGNode *_oldNode,
        QQuickItem::UpdatePaintNodeData *_data) override;

    /// \brief Signal fired to open context menu
    /// Note that the function name needs to start with lowercase in order for
    /// the connection to work on the QML side
    /// \param[in] _entity Scoped name of entity.
    signals: void openContextMenu(QString _entity); // NOLINT

    /// \brief Qt callback when context menu request is received
    /// \param[in] _entity Scoped name of entity.
    public slots: void OnContextMenuRequested(QString _entity);

    /// \internal
    /// \brief Pointer to private data.
    private: std::unique_ptr<RenderWindowItemPrivate> dataPtr;
  };

  /// \brief Texture node for displaying the render texture from ign-renderer
  class TextureNode : public QObject, public QSGSimpleTextureNode
  {
    Q_OBJECT

    /// \brief Constructor
    /// \param[in] _window Parent window
    public: explicit TextureNode(QQuickWindow *_window);

    /// \brief Destructor
    public: ~TextureNode() override;

    /// \brief This function gets called on the FBO rendering thread and will
    ///  store the texture id and size and schedule an update on the window.
    /// \param[in] _id OpenGL render texture Id
    /// \param[in] _size Texture size
    public slots: void NewTexture(int _id, const QSize &_size);

    /// \brief Before the scene graph starts to render, we update to the
    /// pending texture
    public slots: void PrepareNode();

    /// \brief Signal emitted when the texture is being rendered and renderer
    /// can start rendering next frame
    signals: void TextureInUse();

    /// \brief Signal emitted when a new texture is ready to trigger window
    /// update
    signals: void PendingNewTexture();

    /// \brief OpenGL texture id
    public: int id = 0;

    /// \brief Texture size
    public: QSize size = QSize(0, 0);

    /// \brief Mutex to protect the texture variables
    public: QMutex mutex;

    /// \brief Qt's scene graph texture
    public: QSGTexture *texture = nullptr;

    /// \brief Qt quick window
    public: QQuickWindow *window = nullptr;
  };
}
}
}

#endif
