/*---------------------------------------------------------------
	FILE: CDisplayWindow3D.h
	USE: See description below.

   Part of the MRPT Library
   ISA - Universidad de Malaga - http://www.isa.uma.es
  ---------------------------------------------------------------*/
#ifndef  CDisplayWindow3D_H
#define  CDisplayWindow3D_H

#include <MRPT/UTILS/utils_defs.h>
#include <MRPT/config.h>
#include <MRPT/UTILS/SYNCH.h>
#include <MRPT/UTILS/COpenGLScene.h>

/*---------------------------------------------------------------
	Class
  ---------------------------------------------------------------*/
namespace UTILS
{
	class CMRPTImage;
	class CMRPTImageFloat;

	/** A graphical user interface (GUI) for efficiently rendering 3D scenes in real-time.
	  * TODO: Add discussion about usage!
	  *
	  *
	  */
	class CDisplayWindow3D
	{
	protected:
		/** The caption of the window:
		  */
		std::string		m_caption;

		/** The window's handle
		  */
		int             m_hwnd;

		/** The window size
		  */
		unsigned int	m_winWidth,m_winHeight;

		/** A list of existing windows (for accessing in the callback function).
		  */
		static std::map<int,CDisplayWindow3D*>		m_windowsList;
		static SYNCH::CCriticalSection				m_csWindowsList;

		/** Internal OpenGL object (see general discussion in about usage of this object)
		  */
		UTILS::COpenGLScene	*m_3Dscene;

		/** Critical section for accesing m_3Dscene
		  */
		SYNCH::CCriticalSection		m_csAccess3DScene;

		/** The thread for processing window messages:
		  */
		unsigned long	m_hThread;

		/** The window message processing thread.
		  */
		static void		threadMsgLoop(void *param);

		/** Will be true if a key has been pushed on the window.
		  * \sa m_keyPushedCode
		  */
		volatile bool	m_keyPushed;

		/** The code of the last pushed key, only if "m_keyPushed" is true.
		  * \sa m_keyPushed
	      */
		volatile int    m_keyPushedCode;

        /** Runs the OpenGL draw commands.
          */
		void  doRender();

		/** This will be signaled in the first call to the window paint:
		  */
		SYNCH::CSemaphore		m_semWindowHasInit;
		bool					m_firstInitialized;

		enum TOperation {
			opRepaint = 0,
			opClose
		};

		struct TInternalMsg
		{
			TInternalMsg( CDisplayWindow3D * o, int hwnd, TOperation opCode ) : obj(o),hWnd(hwnd),op(opCode) {};
			CDisplayWindow3D	*obj;
			int					hWnd;
			TOperation			op;
		};

		/** The critical section for accessing the internal message queue:
		  */
		static SYNCH::CCriticalSection		m_csMessageQueue;
		static SYNCH::CSemaphore			m_semMessages;
		static std::deque<TInternalMsg>		m_msgQueue;

		/** The display callback function
		  */
		static void callback_display();

		/** The resize callback function
		  */
		static void callback_resize (int width, int height);

		/** The idle callback function
		  */
		static void callback_idle();

		/** The keyboard callback function
		  */
		static void callback_keyboard(unsigned char key,int x, int y);

		/** The special keyboard callback function
		  */
		static void callback_specialkeyboard(int key, int x, int y);

		/** The mouse clicks callback function
		  */
		static void callback_mouse(int button, int state,int x, int y);

		/** The mouse clicks callback function
		  */
		static void callback_motionMouse(int x, int y);

		/** The key/mouse states:
		  */
		bool		mouseClickedL,mouseClickedR,mouseClickedShift,mouseClickedCtrl,mouseClickedAlt;
		int			mouseClickX, mouseClickY;

		/** Camera related data:
		  */
		float		m_minRange,m_maxRange,m_FOV;


	public:
		/** Constructor
		 */
		CDisplayWindow3D(
			const std::string	&windowCaption = std::string(),
			unsigned int		initialWindowWidth = 400,
			unsigned int		initialWindowHeight = 300 );

		/** Destructor
		 */
		~CDisplayWindow3D();

		/** Gets a pointer to the internal pointer to the scene (carefully read introduction in UTILS::CDisplayWindow3D before use!)
		  *  This also locks the critical section for accesing the scene, thus the window will not be repainted until it is unlocked.
		  */
		UTILS::COpenGLScene **  get3DSceneAndLock( );

		/** Unlocks the access to the internal 3D scene.
		  *  Typically user will want to call forceRepaint after updating the scene.
		  */
		void  unlockAccess3DScene();

		/** Repaints the window.
		  */
		void  forceRepaint();

		/** Return the camera min range (z) (used for gluPerspective).
		  */
		float getMinRange() const { return m_minRange; };

		/** Return the camera max range (z) (used for gluPerspective).
		  */
		float getMaxRange() const { return m_maxRange; };

		/** Return the camera field of view (in degrees) (used for gluPerspective).
		  */
		float getFOV() const { return m_FOV; };

		/** Changes the camera min range (z) (used for gluPerspective).
		  *  The window is not updated with this method, call "forceRepaint" to update the 3D view.
		  */
		void setMinRange(float v) { m_minRange=v; };

		/** Changes the camera max range (z) (used for gluPerspective).
		  *  The window is not updated with this method, call "forceRepaint" to update the 3D view.
		  */
		void setMaxRange(float v) { m_maxRange=v; };

		/** Changes the camera field of view (in degrees) (used for gluPerspective).
		  *  The window is not updated with this method, call "forceRepaint" to update the 3D view.
		  */
		void setFOV(float v)  { m_FOV=v; };


		/** Returns true if a key has been pushed, without blocking waiting for a new key being pushed.
		  * \sa waitForKey, clearKeyHitFlag, getPushedKey
		  */
		bool  keyHit()
		{
			return m_keyPushed;
		}

		/** This function returns 0 if no key has been pushed on the window, or the GLUT key (or special key) code of the last pushed key.
		  *  Calling this method also clears the "key pushed" flag.
		  * \sa keyHit
		  */
		int getPushedKey()
		{
			if (m_keyPushed)
			{
				m_keyPushed = false;
				return m_keyPushedCode;
			}
			else
				return 0;
		}

		/** Assure that "keyHit" will return false until the next pushed key.
		  * \sa keyHit
		  */
		void  clearKeyHitFlag()
		{
			m_keyPushed = false;
		}

		/** Resizes the window, stretching the image to fit into the display area.
		 */
		void  resize( unsigned int width, unsigned int height );

		/** Changes the position of the window on the screen.
		 */
		void  setPos( int x, int y );

		/** Waits for any key to be pushed on the image
		  * \sa keyHit
		  */
		void  waitForKey( );

		/** Returns false if the user has closed the window.
		  */
		bool  isOpen();

		float		m_backgroundColorR,m_backgroundColorG,m_backgroundColorB;
		float		m_cameraPointingX,m_cameraPointingY,m_cameraPointingZ;
		float		m_cameraZoomDistance,m_cameraAzimuthDeg,m_cameraElevationDeg;

	}; // End of class def.

} // End of namespace

#endif
