//========================================================================
// GLFW - An OpenGL framework
// File:        x11_fullscreen.c
// Platform:    X11 (Unix)
// API version: 2.6
// WWW:         http://glfw.sourceforge.net
//------------------------------------------------------------------------
// Copyright (c) 2002-2006 Camilla Berglund
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//    claim that you wrote the original software. If you use this software
//    in a product, an acknowledgment in the product documentation would
//    be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such, and must not
//    be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source
//    distribution.
//
//========================================================================

#include "internal.h"


//************************************************************************
//****                  GLFW internal functions                       ****
//************************************************************************

//========================================================================
// _glfwBPP2RGB() - Convert BPP to RGB bits (based on "best guess")
//========================================================================

static void _glfwBPP2RGB( int bpp, int *r, int *g, int *b )
{
    int delta;

    // Special case: BPP = 32 (I don't think this is necessary for X11??)
    if( bpp == 32 )
	bpp = 24;

    // Convert "bits per pixel" to red, green & blue sizes
    *r = *g = *b = bpp / 3;
    delta = bpp - (*r * 3);
    if( delta >= 1 )
    {
        *g = *g + 1;
    }
    if( delta == 2 )
    {
        *r = *r + 1;
    }
}


//========================================================================
// Finds the video mode closest in size to the specified desired size
//========================================================================

int _glfwGetClosestVideoMode( int screen, int *width, int *height, int *rate )
{
#if defined( _GLFW_HAS_XRANDR )
    int i, match, bestmatch;
    int sizecount, bestsize;
    int ratecount, bestrate;
    short *ratelist;
    XRRScreenConfiguration *sc;
    XRRScreenSize *sizelist;

    if( _glfwLibrary.XRandR.Available )
    {
	sc = XRRGetScreenInfo( _glfwLibrary.Dpy,
			       RootWindow( _glfwLibrary.Dpy, screen ) );

	sizelist = XRRConfigSizes( sc, &sizecount );

        // Find the best matching mode
        bestsize  = -1;
        bestmatch = 999999;
        for( i = 0; i < sizecount; i++ )
        {
            match = (*width - sizelist[i].width) *
                    (*width - sizelist[i].width) +
                    (*height - sizelist[i].height) *
                    (*height - sizelist[i].height);
            if( match < bestmatch )
            {
                bestmatch = match;
                bestsize  = i;
            }
        }

	if( bestsize != -1 )
	{
	    // Report width & height of best matching mode
	    *width = sizelist[bestsize].width;
	    *height = sizelist[bestsize].height;

	    if( *rate > 0 )
	    {
		ratelist = XRRConfigRates( sc, bestsize, &ratecount );

		bestrate = -1;
		bestmatch = 999999;
		for( i = 0; i < ratecount; i++ )
		{
		    match = abs( ratelist[i] - *rate );
		    if( match < bestmatch )
		    {
			bestmatch = match;
			bestrate = ratelist[i];
		    }
		}
		
		if( bestrate != -1 )
		{
		    *rate = bestrate;
		}
	    }
	}

        // Free modelist
	XRRFreeScreenConfigInfo( sc );

	if( bestsize != -1 )
	{
	    return bestsize;
	}
    }
#elif defined( _GLFW_HAS_XF86VIDMODE )
    XF86VidModeModeInfo **modelist;
    int modecount, i, bestmode, bestmatch, match;

    // Use the XF86VidMode extension to control video resolution
    if( _glfwLibrary.XF86VidMode.Available )
    {
        // Get a list of all available display modes
        XF86VidModeGetAllModeLines( _glfwLibrary.Dpy, screen,
                                    &modecount, &modelist );

        // Find the best matching mode
        bestmode  = -1;
        bestmatch = 999999;
        for( i = 0; i < modecount; i++ )
        {
            match = (*width - modelist[i]->hdisplay) *
                    (*width - modelist[i]->hdisplay) +
                    (*height - modelist[i]->vdisplay) *
                    (*height - modelist[i]->vdisplay);
            if( match < bestmatch )
            {
                bestmatch = match;
                bestmode  = i;
            }
        }

	if( bestmode != -1 )
	{
	    // Report width & height of best matching mode
	    *width = modelist[ bestmode ]->hdisplay;
	    *h = modelist[ bestmode ]->vdisplay;
	}

        // Free modelist
        XFree( modelist );

	if( bestmode != -1 )
	{
	    return bestmode;
	}
    }
#endif

    // Default: Simply use the screen resolution
    *width = DisplayWidth( _glfwLibrary.Dpy, screen );
    *height = DisplayHeight( _glfwLibrary.Dpy, screen );

    return 0;
}


//========================================================================
// Change the current video mode
//========================================================================

void _glfwSetVideoModeMODE( int screen, int mode, int rate )
{
#if defined( _GLFW_HAS_XRANDR )
    XRRScreenConfiguration *sc;
    Window root;

    if( _glfwLibrary.XRandR.Available )
    {
	root = RootWindow( _glfwLibrary.Dpy, screen );
	sc   = XRRGetScreenInfo( _glfwLibrary.Dpy, root );

        // Remember old size and flag that we have changed the mode
        if( !_glfwWin.FS.ModeChanged )
        {
	    _glfwWin.FS.OldSizeID = XRRConfigCurrentConfiguration( sc, &_glfwWin.FS.OldRotation );
	    _glfwWin.FS.OldWidth  = DisplayWidth( _glfwLibrary.Dpy, screen );
	    _glfwWin.FS.OldHeight = DisplayHeight( _glfwLibrary.Dpy, screen );

            _glfwWin.FS.ModeChanged = GL_TRUE;
        }

	if( rate > 0 )
	{
	    // Set desired configuration
	    XRRSetScreenConfigAndRate( _glfwLibrary.Dpy,
				       sc,
				       root,
				       mode,
				       RR_Rotate_0,
				       (short) rate,
				       CurrentTime );
	}
	else
	{
	    // Set desired configuration
	    XRRSetScreenConfig( _glfwLibrary.Dpy,
				sc,
				root,
				mode,
				RR_Rotate_0,
				CurrentTime );
	}

	XRRFreeScreenConfigInfo( sc );
    }
#elif defined( _GLFW_HAS_XF86VIDMODE )
    XF86VidModeModeInfo **modelist;
    int modecount;

    // Use the XF86VidMode extension to control video resolution
    if( _glfwLibrary.XF86VidMode.Available )
    {
        // Get a list of all available display modes
        XF86VidModeGetAllModeLines( _glfwLibrary.Dpy, screen,
                                    &modecount, &modelist );

        // Unlock mode switch if necessary
        if( _glfwWin.FS.ModeChanged )
        {
            XF86VidModeLockModeSwitch( _glfwLibrary.Dpy, screen, 0 );
        }

        // Change the video mode to the desired mode
        XF86VidModeSwitchToMode(  _glfwLibrary.Dpy, screen,
                                  modelist[ mode ] );

        // Set viewport to upper left corner (where our window will be)
        XF86VidModeSetViewPort( _glfwLibrary.Dpy, screen, 0, 0 );

        // Lock mode switch
        XF86VidModeLockModeSwitch( _glfwLibrary.Dpy, screen, 1 );

        // Remember old mode and flag that we have changed the mode
        if( !_glfwWin.FS.ModeChanged )
        {
            _glfwWin.FS.OldMode = *modelist[ 0 ];
            _glfwWin.FS.ModeChanged = GL_TRUE;
        }

        // Free mode list
        XFree( modelist );
    }
#endif
}


//========================================================================
// Change the current video mode
//========================================================================

void _glfwSetVideoMode( int screen, int *width, int *height, int *rate )
{
    int     bestmode;

    // Find a best match mode
    bestmode = _glfwGetClosestVideoMode( screen, width, height, rate );

    // Change mode
    _glfwSetVideoModeMODE( screen, bestmode, *rate );
}



//************************************************************************
//****               Platform implementation functions                ****
//************************************************************************

struct _glfwResolution {
    int width;
    int height;
};

//========================================================================
// List available video modes
//========================================================================

int _glfwPlatformGetVideoModes( GLFWvidmode *list, int maxcount )
{
    int     count, k, l, r, g, b, rgba, gl;
    int     depth, screen;
    Display *dpy;
    XVisualInfo *vislist, dummy;
    int     viscount, rgbcount, rescount;
    int     *rgbarray;
    struct _glfwResolution *resarray;
#if defined( _GLFW_HAS_XRANDR )
    XRRScreenConfiguration *sc;
    XRRScreenSize *sizelist;
    int     sizecount;
#elif defined( _GLFW_HAS_XF86VIDMODE )
    XF86VidModeModeInfo **modelist;
    int     modecount, width, height;
#endif

    // Get display and screen
    dpy = _glfwLibrary.Dpy;
    screen = DefaultScreen( dpy );

    // Get list of visuals
    vislist = XGetVisualInfo( dpy, 0, &dummy, &viscount );
    if( vislist == NULL )
    {
	return 0;
    }

    rgbarray = (int*) malloc( sizeof(int) * viscount );
    rgbcount = 0;

    // Build RGB array
    for( k = 0; k < viscount; k++ )
    {
        // Does the visual support OpenGL & true color?
        glXGetConfig( dpy, &vislist[k], GLX_USE_GL, &gl );
        glXGetConfig( dpy, &vislist[k], GLX_RGBA, &rgba );
        if( gl && rgba )
        {
            // Get color depth for this visual
            depth = vislist[k].depth;

            // Convert to RGB
            _glfwBPP2RGB( depth, &r, &g, &b );
            depth = (r<<16) | (g<<8) | b;

            // Is this mode unique?
            for( l = 0; l < rgbcount; l++ )
            {
                if( depth == rgbarray[ l ] )
                {
                    break;
                }
            }
            if( l >= rgbcount )
            {
                rgbarray[ rgbcount ] = depth;
                rgbcount++;
            }
        }
    }

    rescount = 0;
    resarray = NULL;

    // Build resolution array
#if defined( _GLFW_HAS_XRANDR )
    if( _glfwLibrary.XRandR.Available )
    {
	sc = XRRGetScreenInfo( dpy, RootWindow( dpy, screen ) );
	sizelist = XRRConfigSizes( sc, &sizecount );

	resarray = (struct _glfwResolution*) malloc( sizeof(struct _glfwResolution) * sizecount );

        for( k = 0; k < sizecount; k++ )
        {
	    resarray[ rescount ].width = sizelist[ k ].width;
	    resarray[ rescount ].height = sizelist[ k ].height;
	    rescount++;
        }

	XRRFreeScreenConfigInfo( sc );
    }
#elif defined( _GLFW_HAS_XF86VIDMODE )
    if( _glfwLibrary.XF86VidMode.Available )
    {
        XF86VidModeGetAllModeLines( dpy, screen, &modecount, &modelist );

	resarray = (struct _glfwResolution*) malloc( sizeof(struct _glfwResolution) * modecount );

        for( k = 0; k < modecount; k++ )
        {
            width  = modelist[ k ]->hdisplay;
            height = modelist[ k ]->vdisplay;

            // Is this mode unique?
            for( l = 0; l < rescount; l++ )
            {
                if( width == resarray[ l ].width && height == resarray[ l ].height )
                {
                    break;
                }
            }
            if( l >= rescount )
            {
                resarray[ rescount ].width = width;
                resarray[ rescount ].height = height;
                rescount++;
            }
        }

	XFree( modelist );
    }
#endif

    if( !resarray )
    {
	rescount = 1;
	resarray = (struct _glfwResolution*) malloc( sizeof(struct _glfwResolution) * rescount );

	resarray[ 0 ].width = DisplayWidth( dpy, screen );
	resarray[ 0 ].height = DisplayHeight( dpy, screen );
    }

    // Build permutations of colors and resolutions
    count = 0;
    for( k = 0; k < rgbcount && count < maxcount; k++ )
    {
        for( l = 0; l < rescount && count < maxcount; l++ )
        {
            list[count].Width     = resarray[ l ].width;
            list[count].Height    = resarray[ l ].height;
            list[count].RedBits   = (rgbarray[ k ] >> 16) & 255;
            list[count].GreenBits = (rgbarray[ k ] >> 8) & 255;
            list[count].BlueBits  = rgbarray[ k ] & 255;
            count++;
        }
    }

    // Free visuals list
    XFree( vislist );

    free( resarray );
    free( rgbarray );

    return count;
}


//========================================================================
// Get the desktop video mode
//========================================================================

void _glfwPlatformGetDesktopMode( GLFWvidmode *mode )
{
    Display *dpy;
    int     bpp, screen;
#if defined( _GLFW_HAS_XF86VIDMODE )
    XF86VidModeModeInfo **modelist;
    int     modecount;
#endif

    // Get display and screen
    dpy = _glfwLibrary.Dpy;
    screen = DefaultScreen( dpy );

    // Get display depth
    bpp = DefaultDepth( dpy, screen );

    // Convert BPP to RGB bits
    _glfwBPP2RGB( bpp, &mode->RedBits, &mode->GreenBits, &mode->BlueBits );

#if defined( _GLFW_HAS_XRANDR )
    if( _glfwLibrary.XRandR.Available )
    {
        if( _glfwWin.FS.ModeChanged )
        {
	    mode->Width  = _glfwWin.FS.OldWidth;
	    mode->Height = _glfwWin.FS.OldHeight;
	    return;
	}
    }
#elif defined( _GLFW_HAS_XF86VIDMODE )
    if( _glfwLibrary.XF86VidMode.Available )
    {
        if( _glfwWin.FS.ModeChanged )
        {
            // The old (desktop) mode is stored in _glfwWin.FS.OldMode
            mode->Width  = _glfwWin.FS.OldMode.hdisplay;
            mode->Height = _glfwWin.FS.OldMode.vdisplay;
        }
        else
        {
            // Use the XF86VidMode extension to get list of video modes
            XF86VidModeGetAllModeLines( dpy, screen, &modecount,
                                        &modelist );

            // The first mode in the list is the current (desktio) mode
            mode->Width  = modelist[ 0 ]->hdisplay;
            mode->Height = modelist[ 0 ]->vdisplay;

            // Free list
            XFree( modelist );
        }

	return;
    }
#endif

    // Get current display width and height
    mode->Width  = DisplayWidth( dpy, screen );
    mode->Height = DisplayHeight( dpy, screen );
}

