/* -*-c++-*- header - Open Producer - Copyright (C) 2002 Don Burns
 * Distributed under the terms of the GNU LIBRARY GENERAL PUBLIC LICENSE (LGPL)
 * as published by the Free Software Foundation.
 */

#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 :

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

        RenderSurface( void );

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

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

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

        /** 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 */ 
        virtual bool makeCurrent(void);
        /** Makes the graphics context and RenderSurface current for rendering (const version)*/ 
        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 );

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

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

        std::string        _hostname;
        int                _displayNum;
        float              _windowLeft, _windowRight, 
                           _windowBottom, _windowTop;
        unsigned int       _windowX, _windowY, 
                           _windowWidth, _windowHeight;
        unsigned int       _screenWidth, _screenHeight;
        Display *          _dpy;
        int                _screen;
        Window             _win;
        Window             _parent;
        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;

        Producer::ref_ptr<RefBarrier> _threadReady;

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

        ref_ptr< Producer::Block > _realizeBlock;

        InputRectangle _inputRectangle;

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

#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
