/* -*-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;
        static const unsigned int UnknownAmount;
        static unsigned int _numScreens;

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

        static unsigned int getNumberOfScreens(void);

        void setDrawableType( DrawableType );
        DrawableType getDrawableType();
        void setReadDrawable( RenderSurface *);
        RenderSurface* getReadDrawable() { return _readDrawableRenderSurface; }

        void setInputRectangle( const InputRectangle &ir );
        const InputRectangle &getInputRectangle() const;
        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;

        void setGLContext( GLContext );
        /** 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();

        /** Use overrideRedirect (X11 only).  This bypasses the window manager
          * control over the Window.  Ignored on Win32 and Mac CGL versions.
          * This call will only have effect if called before realize().  Calling
          * it subsequent to realize() will issue a warning. */
        void useOverrideRedirect(bool);
        bool usesOverrideRedirect();

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

        /** Set the current window cursor.  Producer provides no functionality to create
          * cursors.  It is the application's responsiblity to create a Cursor using the
          * windowing system of choice.  setCursor() will simply set a predefined cursor
          * as the current Cursor */
        void setCursor( Cursor );
        void setCursorToDefault();

        /** 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 "sharedGLContext", then the graphics context created will share certain 
          * graphics constructs (such as display lists) with "sharedGLContext". */
        bool realize( VisualChooser *vc=NULL, GLContext sharedGLContext=0 );

        /** shareAllGLContexts will share all OpenGL Contexts between render surfaces
          * upon realize.  This must be called before any call to renderSurface::realize().
          */ 
        static void shareAllGLContexts(bool);

        /** Returns true or false indicating the the state flag for sharing contexts
          * between RenderSurfaces
          */
        static bool allGLContextsAreShared();

        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();

        /** 
         * 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();

    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 _setWindowName( const std::string & );
        void _useCursor(bool);
        void _setCursor(Cursor);
        void _setCursorToDefault();
        void _positionPointer(int x, int y);
        void _resizeWindow();
        bool _overrideRedirectFlag;

    protected :

        virtual ~RenderSurface( void );
        void _useOverrideRedirect( bool );

        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;
        unsigned int       _visualID;
        GLContext          _glcontext;
        GLContext          _sharedGLContext;
        bool               _decorations;
        bool               _useCursorFlag;
        Cursor             _currentCursor;
        Cursor             _nullCursor;
        Cursor             _defaultCursor;
        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;

#ifdef _OSX_AGL_IMPLEMENTATION
        GLContext          _windowGlcontext;
        GLContext          _fullscreenGlcontext;
        CFDictionaryRef    _oldDisplayMode;
        
        bool _captureDisplayOrWindow();
        void _releaseDisplayOrWindow();
#endif
        
        // 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;

        bool  _createVisualInfo();

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

        static  void  _initThreads();

#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 */
        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

        static bool _shareAllGLContexts;
        static GLContext _globallySharedGLContext;

};

}


#endif

