/* +---------------------------------------------------------------------------+
   |          The Mobile Robot Programming Toolkit (MRPT) C++ library          |
   |                                                                           |
   |                   http://mrpt.sourceforge.net/                            |
   |                                                                           |
   |   Copyright (C) 2005-2008  University of Malaga                           |
   |                                                                           |
   |    This software was written by the Perception and Robotics               |
   |      research group, University of Malaga (Spain).                        |
   |    Contact: Jose-Luis Blanco  <jlblanco@ctima.uma.es>                     |
   |                                                                           |
   |  This file is part of the MRPT project.                                   |
   |                                                                           |
   |     MRPT is free software: you can redistribute it and/or modify          |
   |     it under the terms of the GNU General Public License as published by  |
   |     the Free Software Foundation, either version 3 of the License, or     |
   |     (at your option) any later version.                                   |
   |                                                                           |
   |   MRPT is distributed in the hope that it will be useful,                 |
   |     but WITHOUT ANY WARRANTY; without even the implied warranty of        |
   |     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         |
   |     GNU General Public License for more details.                          |
   |                                                                           |
   |     You should have received a copy of the GNU General Public License     |
   |     along with MRPT.  If not, see <http://www.gnu.org/licenses/>.         |
   |                                                                           |
   +---------------------------------------------------------------------------+ */
#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>
#include <MRPT/UTILS/safe_pointers.h>

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

	/** A graphical user interface (GUI) for efficiently rendering 3D scenes in real-time.
	  *  This class always contains internally an instance of UTILS::COpenGLScene, which
	  *   the objects, viewports, etc. to be rendered.
      *
	  *  Since the 3D rendering is performed in a detached thread, especial care must be taken
	  *   when updating the 3D scene to be rendered. The process involves an internal critical section
	  *   and it must always consist of these steps:
	  *
	  \code
        CDisplayWindow3D	win("My window");

        // Adquire the scene:
        COpenGLScene		**theScene = win.get3DSceneAndLock();

        // Modify the scene:
        (*theScene)->...

        // Unlock it, so the window can use it for redraw:
        win.unlockAccess3DScene();

		// Update window, if required
		win.forceRepaint();
	  \endcode
	  *
	  * An alternative way of updating the scene is by creating, before locking the 3D window, a new object
	  *  of class COpenGLScene, then locking the window only for replacing the pointers, freeing next the old
	  *  object. This may be advantageous is generating the 3D scene takes a long time, since while the window
	  *  is locked it will not be responsive to the user input or window redraw. The alternative way is:
	  *
	  \code
        CDisplayWindow3D	win("My window");

        // Create my scene:
        COpenGLScene    *myScene = new COpenGLScene();
        ...

        // Adquire the scene:
        COpenGLScene		**theScene = win.get3DSceneAndLock();
        COpenGLScene        *oldScene = *theScene;  // Keep copy of old object
        *theScene = myScene;    // Replace just the pointer (fast!)
        win.unlockAccess3DScene();  // Unlock it, so the window can use it for redraw:

		// Update window, if required
		win.forceRepaint();

		// Free the old object:
		delete oldScene;
	  \endcode
	  *
      * \sa  The example /samples/UTILS/display3D, the <a href="http://babel.isa.uma.es/mrpt/index.php/Tutorial_3D_Scenes">tutorial on the wiki</a>.
	  */
	class CDisplayWindow3D
	{
    public:
		/** This semaphore will be signaled when the wx window is built and ready.
		  */
		SYNCH::CSemaphore 	m_semThreadReady;

		/** This semaphore will be signaled when the wx window is destroyed.
		  */
		SYNCH::CSemaphore 	m_semWindowDestroyed;


		/** Read-only access to the wxDialog object.
		  */
		void * getWxObject() { return m_hwnd.get(); }

		/** Called by wx main thread to set m_hwnd to NULL.
		  */
		void notifyChildWindowDestruction();

	protected:

		float m_minRange,m_maxRange,m_FOV;


		/** The caption of the window:
		  */
		std::string		m_caption;

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

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

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

		/** Auxiliars
		  */
		volatile bool	m_keyPushed;

		/** Throws an exception on initialization error
		  */
		void  createOpenGLContext();

		void_ptr_noncopy	m_DisplayDeviceContext;
		void_ptr_noncopy	m_GLRenderingContext;

		void  doRender();

	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 1; //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 );

		/** Changes the window title.
		  */
		void  setWindowTitle( const std::string &str );

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

		/** Changes the camera parameters programatically
		  */
		void setCameraElevationDeg( float deg );

		/** Changes the camera parameters programatically
		  */
		void setCameraAzimuthDeg( float deg );

		/** Changes the camera parameters programatically
		  */
		void setCameraPointingToPoint( float x,float y, float z );

		/** Changes the camera parameters programatically
		  */
		void setCameraZoom( float zoom );


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


	}; // End of class def.

} // End of namespace

#endif
