/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2023, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#pragma once
#include <mrpt/opengl/Scene.h>

namespace mrpt::opengl
{
class CCamera;
}  // namespace mrpt::opengl

namespace mrpt::gui
{
/** This base class implements a working with opengl::Camera and a OpenGL
 * canvas, and it's used in gui::CWxGLCanvasBase and gui::CQtGlCanvasBase.
 * \ingroup mrpt_gui_grp
 */
class CGlCanvasBase
{
   public:
	struct CamaraParams
	{
		CamaraParams() = default;
		/** Changes elevation, taking care of not going out of the [-90,90]
		 * range */
		void setElevationDeg(float deg);

		/** Converts from a CCamera objects \note [New in MRPT 2.1.5] */
		static CamaraParams FromCamera(const mrpt::opengl::CCamera& c);

		float cameraPointingX = .0f, cameraPointingY = .0f,
			  cameraPointingZ = .0f;
		float cameraZoomDistance = 40.f;
		float cameraElevationDeg = 45.f, cameraAzimuthDeg = 45.f;
		bool cameraIsProjective = true;
		float cameraFOV = 30.f;
	};

	CGlCanvasBase() = default;

	virtual ~CGlCanvasBase();
	/** Sets the minimum of the zoom
	 * See also setMaximumZoom(float) */
	void setMinimumZoom(float zoom);

	/** Sets the maximum of the zoom
	 * See also setMinimumZoom(float) */
	void setMaximumZoom(float zoom);

	/** Saves the click position of the mouse
	 * See also setMouseClicked(bool) */
	void setMousePos(int x, int y);

	/** Sets the property mouseClicked
	 * By default, this property is false.
	 * See also setMousePos(int, int) */
	void setMouseClicked(bool is);

	/** Sets the last mouse position */
	void updateLastPos(int x, int y);
	/** Calls the glViewport function*/
	void resizeViewport(int w, int h);

	/** This function for the mouse event
	 * It gets a reference to CamaraParams, x, y
	 * and updates the zoom of the CameraParams.
	 * See also updateZoom(CamaraParams &, float)*/
	void updateZoom(CamaraParams& params, int x, int y) const;
	/** This function for the wheel event
	 * It gets a reference to CamaraParams, delta
	 * and updates the zoom of the CameraParams.
	 * See also updateZoom(CamaraParams &, int, int)*/
	void updateZoom(CamaraParams& params, float delta) const;
	/** This function for the mouse event
	 * It gets a reference to CamaraParams, x, y
	 * and updates the elevation and azimuth.
	 * See also getElevationDegrees(), getAzimuthDegrees()*/
	void updateRotate(CamaraParams& params, int x, int y) const;
	/** This function for the mouse event
	 * It gets a reference to CamaraParams, x, y
	 * and updates the elevation and azimuth.
	 * See also getElevationDegrees(), getAzimuthDegrees()*/
	void updateOrbitCamera(CamaraParams& params, int x, int y) const;
	/** This function for the mouse event
	 * It gets a reference to CamaraParams, x, y
	 * and updates the pointing of the camera.
	 * See also getCameraPointingX(), getCameraPointingY(),
	 * getCameraPointingZ()*/
	void updatePan(CamaraParams& params, int x, int y) const;

	/** Returns a copy of CamaraParams
	 * See also getRefCameraParams(), setCameraParams(const CamaraParams &)*/
	CamaraParams cameraParams() const;
	/** Returns a reference to CamaraParams
	 * See also cameraParams(), setCameraParams(const CamaraParams &) */
	const CamaraParams& getRefCameraParams() const;
	/** Sets the CamaraParams
	 * See also cameraParams(), getRefCameraParams()*/
	virtual void setCameraParams(const CamaraParams& params);

	/** This function gets a reference to mrpt::opengl::CCamera and
	 * updates the camera parameters(pointing, zoom, azimuth, elevation,
	 * IsProjective, FOV)
	 */
	mrpt::opengl::CCamera& updateCameraParams(mrpt::opengl::CCamera& cam) const;

	/** If set to true (default=false), the cameraPointingX,... parameters are
	 * ignored and the camera stored in the 3D scene is used instead.
	 * See also void bool getUseCameraFromScene()
	 */
	void setUseCameraFromScene(bool is);

	/** See also void setUseCameraFromScene(bool)
	 */
	bool getUseCameraFromScene() const;

	// Visualization params:
	/** Saves the pointing of the camera
	 * See also getCameraPointingX(), getCameraPointingY(), getCameraPointingZ()
	 */
	virtual void setCameraPointing(float pointX, float pointY, float pointZ);

	/** Returns the x pointing of the camera
	 * See also setCameraPointing(float, float, float)
	 */
	float getCameraPointingX() const;

	/** Returns the y pointing of the camera
	 * See also setCameraPointing(float, float, float)
	 */
	float getCameraPointingY() const;

	/** Returns the z pointing of the camera
	 * See also setCameraPointing(float, float, float)
	 */
	float getCameraPointingZ() const;

	/** Saves camera zooming
	 * See also getZoomDistance()
	 */
	virtual void setZoomDistance(float zoom);

	/** Returns a zoom
	 * See also setZoomDistance(float)
	 */
	float getZoomDistance() const;

	/** Saves the degrees of the azimuth camera
	 * See also getAzimuthDegrees()
	 */
	virtual void setAzimuthDegrees(float ang);

	/** Returns a azimuth degrees
	 * See also setAzimuthDegrees(float)
	 */
	float getAzimuthDegrees() const;

	/** Saves the degrees of the elevation camera
	 * See also getElevationDegrees()
	 */
	virtual void setElevationDegrees(float ang);

	/** Returns a elevation degrees
	 * See also setElevationDegrees(float)
	 */
	float getElevationDegrees() const;

	virtual void setCameraProjective(bool is);
	bool isCameraProjective() const;

	virtual void setCameraFOV(float FOV);
	float cameraFOV() const;

	static float SENSIBILITY_DEG_PER_PIXEL;	 // Default = 0.1

	/** Overload this method to limit the capabilities of the user to move the
	 * camera using the mouse.
	 *  For all these variables:
	 *  - cameraPointingX
	 *  - cameraPointingY
	 *  - cameraPointingZ
	 *  - cameraZoomDistance
	 *  - cameraElevationDeg
	 *  - cameraAzimuthDeg
	 *
	 *  A "new_NAME" variable will be passed with the temptative new
	 * value after the user action.
	 *   The default behavior should be to copy all the new variables
	 * to the variables listed above
	 *   but in the middle any find of user-defined filter can be
	 * implemented.
	 */
	virtual void OnUserManuallyMovesCamera(
		float new_cameraPointingX, float new_cameraPointingY,
		float new_cameraPointingZ, float new_cameraZoomDistance,
		float new_cameraElevationDeg, float new_cameraAzimuthDeg)
	{
		m_cameraParams.cameraPointingX = new_cameraPointingX;
		m_cameraParams.cameraPointingY = new_cameraPointingY;
		m_cameraParams.cameraPointingZ = new_cameraPointingZ;
		m_cameraParams.cameraZoomDistance = new_cameraZoomDistance;
		m_cameraParams.cameraElevationDeg = new_cameraElevationDeg;
		m_cameraParams.cameraAzimuthDeg = new_cameraAzimuthDeg;
	}

	inline void getLastMousePosition(int& x, int& y) const
	{
		x = m_mouseLastX;
		y = m_mouseLastY;
	}

	/**  At constructor an empty scene is created. The object is freed at GL
	canvas destructor.
	 * This function returns a smart pointer to the opengl scene
	getOpenGLSceneRef		  */
	mrpt::opengl::Scene::Ptr& getOpenGLSceneRef() { return m_openGLScene; }
	void setOpenGLSceneRef(mrpt::opengl::Scene::Ptr scene);

   protected:
	virtual void swapBuffers() = 0;
	virtual void preRender() = 0;
	virtual void postRender() = 0;
	virtual void renderError(const std::string& err_msg) = 0;

	virtual double renderCanvas(int width = -1, int height = -1);

   private:
	bool useCameraFromScene = false;
	mrpt::opengl::Scene::Ptr m_openGLScene = mrpt::opengl::Scene::Create();
	int m_mouseLastX = 0, m_mouseLastY = 0;
	int m_mouseClickX = 0, m_mouseClickY = 0;
	bool mouseClicked = false;
	float m_minZoom = 1.f;
	float m_maxZoom = 3200.f;
	CamaraParams m_cameraParams;
};	// end of class

/** A headless dummy implementation of CGlCanvasBase: can be used to keep track
 * of user UI mouse events and update the camera parameters, with actual
 * rendering being delegated to someone else. \ingroup mrpt_gui_grp
 */
class CGlCanvasBaseHeadless : public CGlCanvasBase
{
   public:
	CGlCanvasBaseHeadless() = default;
	virtual ~CGlCanvasBaseHeadless() override = default;

   protected:
	virtual void swapBuffers() override {}
	virtual void preRender() override {}
	virtual void postRender() override {}
	virtual void renderError(const std::string& e) override;
};

}  // namespace mrpt::gui
