/* -*-c++-*- Producer - Copyright (C) 2001-2004  Don Burns
 *
 * This library is open source and may be redistributed and/or modified under
 * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or
 * (at your option) any later version.  The full license is in LICENSE file
 * included with this distribution, and on the openscenegraph.org website.
 *
 * This library 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
 * OpenSceneGraph Public License for more details.
 */


#ifndef PRODUCER_RENDER_SURFACE
#define PRODUCER_RENDER_SURFACE 1

#include <Producer/Export>

#include <map>
#include <string>
#include <iostream>

#include <Producer/RefOpenThreads>
#include <Producer/Block>

#include <Producer/Types>
#include <Producer/Referenced>
#include <Producer/VisualChooser>

#ifdef _WIN32_IMPLEMENTATION
  #include <vector>
#endif

namespace Producer {

/**
    \class RenderSurface
    \brief A RenderSurface provides a rendering surface for 3D graphics applications.

    A RenderSurface creates a window in a windowing system for the purpose of 3D 
    rendering.  The focus of a RenderSurface differs from a windowing system window
    in that it is not a user input/output device, but rather a context and screen area
    specifically designed for 3D applications.  Consequently, a RenderSurface does not 
    provide or impose a requirement on the caller to structure the application around
    the capturing or handling of events.  Further, RenderSurface provides increased 
    control over the quality of pixel formats.
 */

class PR_EXPORT RenderSurface : public Referenced, public OpenThreads::Thread
{
    public :

        static const unsigned int UnknownDimension;

        class Callback : public Producer::Referenced 
        {
            public:
                Callback() {}
                virtual void operator()( const RenderSurface & ) = 0;

            protected:
                virtual ~Callback() {}
        };

        struct InputRectangle 
        {
            public:
                InputRectangle(): _left(-1.0), _bottom(-1.0), _width(2.0), _height(2.0) {}
                InputRectangle( float left, float right, float bottom, float top ):
                    _left(left), _bottom(bottom), _width(right-left), _height(top-bottom) {}
                InputRectangle(const InputRectangle &ir) 
                {
                     _left = ir._left;
                     _bottom = ir._bottom;
                     _width = ir._width;
                     _height = ir._height;
                }
                virtual ~InputRectangle() {}
        
                void set( float left, float right, float bottom, float top )
                {
                    _left = left;
                    _bottom = bottom;
                    _width = right - left;
                    _height = top - bottom;
                }
                float left() const { return _left; }
                float bottom() const { return _bottom; }
                float width() const { return _width; }
                float height() const { return _height; }
        
            protected:
        
            private:
                float _left, _bottom, _width, _height;
        };

        enum DrawableType {
            DrawableType_Window,
            DrawableType_PBuffer
        };


        RenderSurface( void );

        void setDrawableType( DrawableType );
        void setReadDrawable( RenderSurface *);

        void setInputRectangle( const InputRectangle &ir );
        const InputRectangle &getInputRectangle();
        void bindInputRectangleToWindowSize(bool);


        /** Set the name of the Host the window is to be created on.
            Ignored on Win32*/
        void setHostName( const std::string & );

        /** 
          * Get the name of the Host the window is to be created on. 
          * Ignored on Win32 */
        const std::string& getHostName( void ) const;

        /** 
          * Set the number of the display the render surface is to 
          * be created on.  In XWindows, this is the number of the
          * XServer.  Ignored on Win32   */
        void setDisplayNum( int );

        /** Get the number of the display the render surface is to 
          * be created on.  In XWindows, this is the number of the
          * XServer.  Ignored on Win32   */
        int getDisplayNum( void ) const;
     
        /** Set the number of the screen the render surface is to 
          * be created on.  In XWindows, this is the number of the
          * XServer.  Ignored on Win32   */
        void setScreenNum( int );

        /** Get the number of the screen the render surface is to 
          * be created on.  In XWindows, this is the number of the
          * XServer.  Ignored on Win32   */
        int getScreenNum( void ) const;

        /** Get the size of the screen in pixels the render surface 
          * is to be created on.  */
        void getScreenSize( unsigned int &width, unsigned int &height ) const;
        

        /** Default window name */
        static const std::string defaultWindowName;

        static const std::string &getDefaultWindowName();

        /** Set the Window system Window name of the Render Surface */
        void setWindowName( const std::string & );
        /** Get the Window system Window name of the Render Surface */
        const std::string& getWindowName( void ) const;

        /** Set the windowing system rectangle the RenderSurface will 
          * occupy on the screen.  The parameters are given as integers
          * in screen space.  x and y determine the lower left hand corner
          * of the RenderSurface.  Width and height are given in screen 
          * coordinates */
        void  setWindowRectangle( int x, int y, unsigned int width, unsigned int height, 
                                    bool resize=true );
        /** Get the windowing system rectangle the RenderSurface will 
          * occupy on the screen.  The parameters are given as integers
          * in screen space.  x and y determine the lower left hand corner
          * of the RenderSurface.  Width and height are given in screen 
          * coordinates */
        void getWindowRectangle( int &x, int &y, unsigned int &width, unsigned int &height ) const;
        /** Get the X coordinate of the origin of the RenderSurface's window */
        int getWindowOriginX() const;

        /** Get the Y coordinate of the origin of the RenderSurface's window */
        int getWindowOriginY() const;

        /** Get the width of the RenderSurface in windowing system screen coordinates */
        unsigned int getWindowWidth() const;

        /** Get the height of the RenderSurface in windowing system screen coordinates */
        unsigned int getWindowHeight() const;

        /** Explicitely set the Display variable before realization. (X11 only). */
        void               setDisplay( Display *dpy );
        /** Get the Display. (X11 only). */
        Display*           getDisplay( void );
        /** Get the const Display. (X11 only). */
        const Display*     getDisplay( void ) const;

        /** Explicitely set the Windowing system window before realization. */
        void               setWindow( const Window win );
        /** Returns the Windowing system handle to the window */
        Window getWindow( void ) const;

        /** Returns the OpenGL context */
        GLContext getGLContext( void ) const;

        /** Set the Windowing system's parent window */
        void setParentWindow( Window parent );
        /** Get the Windowing system's parent window */
        Window getParentWindow( void ) const;

        void setVisualChooser( VisualChooser *vc );
        VisualChooser *getVisualChooser( void );
        const VisualChooser *getVisualChooser( void ) const;

        void setVisualInfo( VisualInfo *vi );
        VisualInfo *getVisualInfo( void );
        const VisualInfo *getVisualInfo( void ) const;

        /** Returns true if the RenderSurface has been realized, false if not. */
        bool isRealized( void ) const;

        /** Request the use of a window border.  If flag is false, no border will
          * appear after realization.  If flag is true, the windowing system window
          * will be created in default state. */
        void useBorder( bool flag );
        bool usesBorder();

       /** Request whether the window should have a visible cursor.  If true, the 
         * windowing system's default cursur will be assigned to the window.  If false
         * the window will not have a visible cursor. */
        void useCursor( bool flag );

        /** Specify whether the RenderSurface should use a separate thread for 
          * window configuration events.  If flag is set to true, then the 
          * RenderSurface will spawn a new thread to manage events caused by 
          * resizing the window, mapping or destroying the window. */
        void useConfigEventThread(bool flag);

        /** Realize the RenderSurface.  When realized, all components of the RenderSurface
          * not already configured are configured, a window and a graphics context are 
          * created and made current.  If an already existing graphics context is passed
          * through "shared_context", then the graphics context created will share certain 
          * graphics constructs (such as display lists) with "shared_context". */
        bool realize( VisualChooser *vc=NULL, GLContext shared_context=0 );

        /**
         */ 
        
        void addRealizeCallback( Callback *realizeCB );
        void setRealizeCallback( Callback *realizeCB ) { addRealizeCallback(realizeCB); }

        /** Swaps buffers if RenderSurface quality attribute is DoubleBuffered */
        virtual void swapBuffers(void);

        /** Makes the graphics context and RenderSurface current for rendering */ 
        bool makeCurrent(void) const;

        /** Where supported, sync() will synchronize with the vertical retrace signal of the 
          * graphics display device.  \a divisor specifies the number of vertical retace signals
          * to allow before returning. */
        virtual void sync( int divisor=1 );

        /** Where supported, getRefreshRate() will return the frequency in hz of the vertical
          * retrace signal of the graphics display device.  If getRefreshRate() returns 0, then
          * the underlying support to get the graphics display device's vertical retrace signal
          * is not present. */
        unsigned int getRefreshRate() const;

        /** Where supported, InitThreads will initialize all graphics components for thread
          * safety.  InitThreads() should be called before any other calls to Xlib, or OpenGL
          * are made, and should always be called when multi-threaded environments are intended.          */
        static void InitThreads();

        /** Where supported, InitThreads will initialize all graphics components for thread
         * Puts the calling thread to sleep until the RenderSurface is realized.  Returns true
         * if for success and false for failure.
         * */
        bool waitForRealize();

        /** fullScreen(flag).  If flag is true, RenderSurface resizes its window to fill the 
         * entire screen and turns off the border.  If false, the window is returned to a size
         * previously specified.  If previous state specified a border around the window, a 
         * the border is replaced
         * */
        void fullScreen( bool flag );
        /** setCustomFullScreenRencangle(x,y,width,height).  Programmer may set a customized 
          * rectangle to be interpreted as "fullscreen" when fullscreen(true) is called.  This
          * allows configurations that have large virtual screens that span more than one monitor
          * to define a "local" full screen for each monitor.
          */
        void setCustomFullScreenRectangle( int x, int y, unsigned int width, unsigned int height );
        /** useDefaultFullScreenRetangle().  Sets the application back to using the default
          * screen size as fullscreen rather than the custom full screen rectangle
          */
        void useDefaultFullScreenRectangle(); 

        /** isFullScreen() returns true if the RenderSurface's window fills the entire screen 
         * and has no border. */
        bool isFullScreen() const { return _isFullScreen; }

        /** positionPointer(x,y) places the pointer at window coordinates x, y.
          */
        void positionPointer( int x, int y );

        /** set/getUseDefaultEsc is deprecated */
        void setUseDefaultEsc( bool flag ) {_useDefaultEsc = flag; }
        bool getUseDefaultEsc() { return _useDefaultEsc; }

        /** map and unmap the window */
        void mapWindow();
        void unmapWindow();

        // TEMPORARY PBUFFER RENDER TO TEXTURE SUPPORT
        // Temporary PBuffer support for Windows.  Somebody got really
        // confused and mixed up PBuffers with a renderable texture and
        // caused this messy headache.
        // These have no meaning in GLX world.....
        enum BufferType {
			FrontBuffer,
			BackBuffer
        };
        
        enum RenderToTextureMode {
			RenderToTextureMode_None,
			RenderToRGBTexture,
			RenderToRGBATexture
        };
       
        enum RenderToTextureTarget {
			Texture1D,
			Texture2D,
			TextureCUBE
        };
        
        enum RenderToTextureOptions {
			RenderToTextureOptions_Default = 0,
			RequestSpaceForMipMaps  = 1,
			RequestLargestPBuffer   = 2
        };
        
        enum CubeMapFace {
			PositiveX  = 0,
			NegativeX  = 1,
			PositiveY  = 2,
			NegativeY  = 3,
			PositiveZ  = 4,
			NegativeZ  = 5
        };

        /**
          * Bind PBuffer content to the currently selected texture.
          * This method affects PBuffer surfaces only. */
        void bindPBufferToTexture(BufferType buffer = FrontBuffer) const;
        
        /** 
          * Get the render-to-texture mode (PBuffer drawables only).
          * This method has no effect if it is called after realize() */
        RenderToTextureMode getRenderToTextureMode() const;
        
        /**
          * Set the render-to-texture mode (PBuffer drawables only). You
          * can pass int values different from the constants defined by
          * RenderToTextureMode, in which case they will be applied
          * directly as parameters to the WGL_TEXTURE_FORMAT attribute.
          * This method has no effect if it is called after realize(). */
        void setRenderToTextureMode(RenderToTextureMode mode);
        
        /** 
          * Get the render-to-texture target (PBuffer drawables only).
          * This method has no effect if it is called after realize(). */
        RenderToTextureTarget getRenderToTextureTarget() const;
        
        /** 
          * Set the render-to-texture target (PBuffer drawables only). You
          * can pass int values different from the constants defined by
          * RenderToTextureTarget, in which case they will be applied
          * directly as parameters to the WGL_TEXTURE_TARGET attribute.
          * This method has no effect if it is called after realize(). */
        void setRenderToTextureTarget(RenderToTextureTarget target);
        
        /** 
          * Get the render-to-texture options (PBuffer drawables only).
          * This method has no effect if it is called after realize(). */
        RenderToTextureOptions getRenderToTextureOptions() const;
        
        /** 
          * Set the render-to-texture options (PBuffer drawables only).
          * You can pass any combination of the constants defined in
          * enum RenderToTextureOptions.
          * This method has no effect if it is called after realize(). */
        void setRenderToTextureOptions(RenderToTextureOptions options);
        
        /**
          * Get which mipmap level on the target texture will be
          * affected by rendering. */
        int getRenderToTextureMipMapLevel() const;

        /**
          * Select which mipmap level on the target texture will be
          * affected by rendering. This method can be called after the
          * PBuffer has been realized. */
        void setRenderToTextureMipMapLevel(int level);
        
        /**
          * Get which face on the target cube map texture will be
          * affected by rendering. */
        CubeMapFace getRenderToTextureFace() const;

        /**
          * Select which face on the target cube map texture will be
          * affected by rendering. This method can be called after the
          * PBuffer has been realized. */
        void setRenderToTextureFace(CubeMapFace face);

        // END TEMPORARY PBUFFER RENDER TO TEXTURE SUPPORT
        
        /**
          * Get the (const) vector of user-defined PBuffer attributes. */
        const std::vector<int> &getPBufferUserAttributes() const;

        /**
          * Get the vector of user-defined PBuffer attributes. This
          * vector will be used to initialize the PBuffer's attribute list.*/
        std::vector<int> &getPBufferUserAttributes();

		////////////////////////////////////////////////////////////////////
        enum PixelAttributeName {
            UseGL,
            BufferSize,
            Level,
            RGBA, 
            DoubleBuffer, 
            Stereo, 
            AuxBuffers,
            RedSize,
            GreenSize,
            BlueSize,
            AlphaSize,
            DepthSize,
            StencilSize,
            AccumRedSize,
            AccumGreenSize,
            AccumBlueSize,
            AccumAlphaSize
        };

        //-------------------------------------------------------------------------
        // Generic method for adding an attribute without a parameter 
        // (e.g DoubleBuffer )
        void addPixelAttribute( PixelAttributeName attribute );

        //-------------------------------------------------------------------------
        // Generic method for adding an attribute with a parameter 
        // (e.g DepthSize, 1 )
        void addPixelAttribute( PixelAttributeName attribute, int parameter );

        //-------------------------------------------------------------------------
        // Generic method for adding an attribute without a parameter 
        // (e.g DoubleBuffer )
        void addExtendedPixelAttribute( unsigned int attribute );

        //-------------------------------------------------------------------------
        // Generic method for adding an extended attribute with a parameter 
        // (e.g DepthSize, 1 )
        void addExtendedPixelAttribute( unsigned int attribute, int parameter );

		void setSimplePixelConfiguration();

    private :
        unsigned int _refreshRate;
#ifdef _X11_IMPLEMENTATION
        int (*__glxGetRefreshRateSGI)(unsigned int *);
        int (*__glxGetVideoSyncSGI)(unsigned int *);
        int (*__glxWaitVideoSyncSGI)(int,int,unsigned int *);
        void testVSync( void );
#endif
        bool _checkEvents( Display *);
        void _setBorder( bool flag );
        void _setCursor();
        void _positionPointer(int x, int y);
        void _resizeWindow();

    protected :

        virtual ~RenderSurface( void );

        DrawableType       _drawableType;
        std::string        _hostname;
        int                _displayNum;
        float              _windowLeft, _windowRight, 
                           _windowBottom, _windowTop;
        int                _windowX, _windowY;
        unsigned int       _windowWidth, _windowHeight;
        unsigned int       _screenWidth, _screenHeight;
        bool               _useCustomFullScreen;
        int                _customFullScreenOriginX;
        int                _customFullScreenOriginY;
        unsigned int       _customFullScreenWidth;
        unsigned int       _customFullScreenHeight;
        Display *          _dpy;
        int                _screen;
        Window             _win;
        Window             _parent;
        RenderSurface      *_readDrawableRenderSurface;
        unsigned int       _parentWindowHeight;
        bool               _realized;
        ref_ptr< VisualChooser >  _visualChooser;
        VisualInfo *       _visualInfo;
        GLContext          _glcontext;
        GLContext          _sharedGlcontext;
        Cursor             _nullCursor;
        bool               _decorations;
        bool               _useCursor;
        std::string        _windowName;
        unsigned int       _frameCount;
        bool               _mayFullScreen;
        bool               _isFullScreen;
        bool               _bindInputRectangleToWindowSize;

        // Temporary "Pbuffer support for Win32
        RenderToTextureMode     _rtt_mode;
        RenderToTextureTarget   _rtt_target;
        RenderToTextureOptions  _rtt_options;
        int                     _rtt_mipmap;
        CubeMapFace             _rtt_face;
        bool                    _rtt_dirty_mipmap;
        bool                    _rtt_dirty_face;
        
        // user-defined PBuffer attributes
        std::vector<int>        _user_pbattr;
        

        OpenThreads::Barrier *_threadReady;

        bool _useConfigEventThread;
        bool _checkOwnEvents;
        bool _useDefaultEsc;
        
        std::vector <Producer::ref_ptr<Callback> >_realizeCallbacks;

        ref_ptr< Producer::Block > _realizeBlock;

        InputRectangle _inputRectangle;

        void _computeScreenSize( unsigned int &width, unsigned int &height ) const;

        virtual bool  _init();
        virtual void  _fini();
        virtual void  run();

		///////////////////////////////////////////////////////////////////////
		// Pixel Attributes

        struct PixelAttribute 
        {
                unsigned int  _attribute;
                bool _has_parameter;
                int  _parameter;
                bool _is_extension;

                PixelAttribute( PixelAttributeName attribute, int parameter ) :
                	_attribute(attribute),
                	_has_parameter(true),
                	_parameter(parameter),
                	_is_extension(false) {}

                PixelAttribute( PixelAttributeName attribute ) :
                	_attribute(attribute),
                	_has_parameter(false),
                	_parameter(0),
                	_is_extension(false) {}

                PixelAttribute( unsigned int attribute, int parameter ) :
                	_attribute(attribute),
                	_has_parameter(true),
                	_parameter(parameter),
                	_is_extension(true) {}

                PixelAttribute( unsigned int attribute ) :
                	_attribute(attribute),
                	_has_parameter(false),
                	_parameter(0),
                	_is_extension(true) {}

                unsigned int attribute()    { return _attribute; }
                bool hasParameter()         { return _has_parameter; }
                int  parameter()            { return _parameter; }
                bool isExtension()          { return _is_extension; }
          };

		void _choosePixelFormat( VisualInfo *);
        std::vector <PixelAttribute> _pixelAttributes;
		int _adaptPixelAttribute( PixelAttribute &attr, VisualInfo *vinfo );

#ifdef _WIN32_IMPLEMENTATION

		class Client : public Producer::Referenced
		{
			public:
				Client(unsigned long mask);
				unsigned int mask() { return _mask; }
				EventQueue *getQueue() { return q.get(); }
				void queue( ref_ptr<Event> ev );

			private :
				unsigned int _mask;
				Producer::ref_ptr<EventQueue> q;
		};
        std::vector < Producer::ref_ptr<Client> >clients;
        void dispatch( ref_ptr<Event> );

        int _ownWindow;
        int _ownVisualChooser;
        int _ownVisualInfo;

        HDC _hdc;
        HINSTANCE _hinstance;

        BOOL CreateGLWindow(char* title, int width, int height, int bits, bool fullscreenflag);
        void KillGLWindow();
        
        LONG WINAPI proc( Window, UINT, WPARAM, LPARAM );
        static LONG WINAPI s_proc( Window, UINT, WPARAM, LPARAM );
        static std::map <Window, RenderSurface *>registry;

        /* mouse things */
        void _setCursor(int cursorID);
        int _curCursor;
        int _mx, _my;
        unsigned int _mbutton;
        WNDPROC _oldWndProc;


public:
        EventQueue * selectInput( unsigned int mask );
        
        // if _parent is set, resize the window to 
        // fill the client area of the parent
        void resizeToParent();
#endif

};

}


#endif
