/* 
 * XRandR interface for DirectDraw
 *
 * Copyright 2003 TransGaming Technologies
 *
 */
 
#include "config.h"

#if defined( HAVE_LIBXRANDR )

#include "wine/port.h"

#include "ts_xrandr.h"
#include "x11drv.h"
#include "wine/debug.h"
#include "x11ddraw.h"

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

static int X11DRV_XF86RR_GetCurrentMode(void);
static void X11DRV_XF86RR_SetCurrentMode(int new_mode);
static int X11DRV_XF86RR_GetSystemMode(void);

static int xf86rr_event, xf86rr_error;
static int xf86rr_major, xf86rr_minor;
static int XF86RRErrorFlag;
int usexrandr = 0;
static unsigned int xf86rr_mode_count;
static LPDDHALMODEINFO xf86rr_modes;

static XRRScreenConfiguration* lpXrrconfig;
static XRRScreenSize* xf86rr_sizes;
static int xf86rr_nsizes;
static Rotation cur_rr_rotation;

static BOOL bXRandRAvailable;
static DWORD saved_DD_mode;

static void* xrandr_library_handle;
static void* xrender_library_handle;
static void* xext_library_handle;
static BOOL xrandr_loaded;

static Bool (*pXRRQueryExtension)(Display *dpy, int *event_basep, int *error_basep);
static Status (*pXRRQueryVersion)(Display *dpy, int *major_versionp,int *minor_versionp);
static XRRScreenConfiguration* (*pXRRGetScreenInfo)(Display *dpy,Drawable draw);
static XRRScreenSize* (*pXRRConfigSizes)(XRRScreenConfiguration *config, int *nsizes);
static short* (*pXRRConfigRates)(XRRScreenConfiguration *config, int sizeID, int *nrates);
static void (*pXRRFreeScreenConfigInfo)(XRRScreenConfiguration *config);
static SizeID (*pXRRConfigCurrentConfiguration)(XRRScreenConfiguration *config,Rotation *rotation);
static short (*pXRRConfigCurrentRate)(XRRScreenConfiguration *config);
static Status (*pXRRSetScreenConfigAndRate)(Display *dpy, XRRScreenConfiguration *config,
                                  Drawable draw, int size_index, Rotation rotation,
                                  short rate, Time timestamp);

#ifndef SONAME_LIBXRANDR
#  define SONAME_LIBXRANDR "libXrandr.so.2"
#endif

#ifndef SONAME_LIBXRENDER
#  define SONAME_LIBXRENDER "libXrender.so.1"
#endif

#ifndef SONAME_LIBXEXT
#  define SONAME_LIBXEXT "libXext.so.6"
#endif


void X11DRV_XF86RR_Finalize(void)
{
  if( xrandr_library_handle )
  {
    wine_dlclose( xrandr_library_handle, NULL, 0 );
    xrandr_library_handle = NULL;
    xrandr_loaded = FALSE;

    pXRRQueryExtension = NULL;
    pXRRQueryVersion = NULL;
    pXRRGetScreenInfo = NULL;
    pXRRConfigSizes = NULL;
    pXRRConfigRates = NULL;
    pXRRFreeScreenConfigInfo = NULL;
    pXRRConfigCurrentConfiguration = NULL;
    pXRRConfigCurrentRate = NULL;
    pXRRSetScreenConfigAndRate = NULL;
  }

  if( xrender_library_handle )
  {
    wine_dlclose( xrender_library_handle, NULL, 0 );
  }

  if( xext_library_handle )
  {
    wine_dlclose( xext_library_handle, NULL, 0 );

  }
}

static BOOL X11DRV_XF86RR_load_xrandr(void)
{
  static char buf[256];

  /* Some distros (SUSE, MDK, Knoppix) provide basically broken libXrandr as they don't have explicit library
   * dependencies. So, let's open up all the dependencies first. Sigh. */
  xext_library_handle = wine_dlopen(SONAME_LIBXEXT, RTLD_NOW|RTLD_GLOBAL, buf, sizeof(buf) );
  if (!xext_library_handle ) goto load_error;

  xrender_library_handle = wine_dlopen(SONAME_LIBXRENDER, RTLD_NOW|RTLD_GLOBAL,buf, sizeof(buf)); 
  if (!xrender_library_handle ) goto load_error;

  xrandr_library_handle = wine_dlopen(SONAME_LIBXRANDR, RTLD_NOW, buf, sizeof(buf));
  if (!xrandr_library_handle) goto load_error;

#define DYNLOAD_XRANDR(f) \
    do { p##f = wine_dlsym(xrandr_library_handle, #f, NULL, 0); \
    if (!p##f) \
    { \
        WARN("failed to load XRandR symbol '%s'\n", #f); \
        goto error; \
    }} while (0)

  /* Load symbols from xrandr version 1.1 */
  DYNLOAD_XRANDR( XRRQueryExtension );
  DYNLOAD_XRANDR( XRRQueryVersion );
  DYNLOAD_XRANDR( XRRGetScreenInfo );
  DYNLOAD_XRANDR( XRRConfigSizes );
  DYNLOAD_XRANDR( XRRConfigRates );
  DYNLOAD_XRANDR( XRRFreeScreenConfigInfo );
  DYNLOAD_XRANDR( XRRConfigCurrentConfiguration );
  DYNLOAD_XRANDR( XRRConfigCurrentRate );
  DYNLOAD_XRANDR( XRRSetScreenConfigAndRate );

#undef DYNLOAD_XRANDR

  xrandr_loaded = TRUE;

  return TRUE;

load_error:

  WARN( "Unable to load library %s\n", buf );

error:
  
  X11DRV_XF86RR_Finalize();

  return FALSE;
}

static int XF86RRErrorHandler(Display *dpy, XErrorEvent *event)
{
  XF86RRErrorFlag = 1;
  return 0;
}

static inline void X11DRV_XF86RR_convert_mode( LPDDHALMODEINFO lpMode, const XRRScreenSize* size, const short* rate )
{
  lpMode->dwWidth      = size->width;
  lpMode->dwHeight     = size->height;
  lpMode->wRefreshRate = *rate;
  /* Everything else is 0 already so just leave it that way */

  TRACE( "%ldx%ld @ %dHz\n", lpMode->dwWidth, lpMode->dwHeight, lpMode->wRefreshRate );
}

static inline int X11DRV_XF86RR_get_mode_from_rr_size_and_rate( int size, int rate )
{
  unsigned int i;
  int width, height;

  width  = xf86rr_sizes[ size ].width;
  height = xf86rr_sizes[ size ].height;

  for( i=0; i < xf86rr_mode_count; i++ )
  {
    if( ( xf86rr_modes[i].wRefreshRate == rate ) &&
        ( xf86rr_modes[i].dwWidth == width ) &&
        ( xf86rr_modes[i].dwHeight == height ) )
    {
      break;
    }
  }

  if( i >= xf86rr_mode_count ) 
  {
    ERR( "Mode not found for size=%d (%dx%d) rate %d\n", size, width, height, rate );
    i = 0;
  }

  return i;
}

static inline void X11DRV_XF86RR_get_rr_size_and_rate_from_mode( int mode, int* rr_size, int* rr_rate )
{
  int i;
  int width, height, rate;

  width  = xf86rr_modes[ mode ].dwWidth;
  height = xf86rr_modes[ mode ].dwHeight;
  rate   = xf86rr_modes[ mode ].wRefreshRate;

  for( i=0; i < xf86rr_nsizes; i++ )
  {
    if( ( xf86rr_sizes[i].width == width ) &&
        ( xf86rr_sizes[i].height == height ) )
    {
      break;
    }
  }

  if( i >= xf86rr_nsizes )
  {
    ERR( "RandR size not found for mode= %d (%dx%d@%dHz)\n", mode, width, height, rate );
    i = 0;
    rate = 0;
  }

  *rr_size = i;
  *rr_rate = rate; /* PH: FIXME: Is rate correct or should it be a # into rate list? */
}

void X11DRV_XF86RR_Init(void)
{
  static BOOL bInitDone = FALSE;
  Bool ok;
  short* rates;
  int nrates;
  int i, j, mode;

  /* Only initialize once */
  if( bInitDone ) return;

  bInitDone = TRUE;

  /* Make sure that RandR should be used */ 
  if( !usexrandr ) return;

  TRACE( "Attempting to load RandR\n" );
  if( !X11DRV_XF86RR_load_xrandr() ) return;

  TRACE( "Initializing RandR\n" );

  wine_tsx11_lock();
  {

    /* Is the extension available */
    if( !pXRRQueryExtension( gdi_display, &xf86rr_event, &xf86rr_error ) )
    {
      TRACE( "Extension not available\n" );
      goto done;
    }

    /* And what is capable of? */
    XF86RRErrorFlag = 0;
    X11DRV_SetXErrorHandler( XF86RRErrorHandler );
    {
      ok = pXRRQueryVersion( gdi_display, &xf86rr_major, &xf86rr_minor );
    }
    X11DRV_RestoreXErrorHandler();

    /* So was it actually not supported? */
    if( XF86RRErrorFlag || !ok )
    {
      TRACE( "Extension not supported\n" );
      goto done;
    }

    if( xf86rr_major < 1 || ( xf86rr_major == 1 && xf86rr_minor == 0 ) )
    {
      TRACE( "Version of XRandR %d.%d is too old\n", xf86rr_major, xf86rr_minor );
      goto done;
    }

    /* If we're in desktop mode, don't do anymore */
    if( root_window != DefaultRootWindow( gdi_display ) )
    {
      TRACE( "Desktop mode active - not continuing\n" );
      goto done;
    }

    bXRandRAvailable = TRUE;

    TRACE( "Found, and using, XRandR %d.%d\n", xf86rr_major, xf86rr_minor );

    lpXrrconfig = pXRRGetScreenInfo( gdi_display, root_window );
    xf86rr_sizes = pXRRConfigSizes(lpXrrconfig, &xf86rr_nsizes);

    /* Figure out how many modes we have */
    for( i=0; i < xf86rr_nsizes; i++ )
    {
      /* Available rates depends on chosen size */
      rates = pXRRConfigRates(lpXrrconfig, i, &nrates);
      xf86rr_mode_count += nrates;
    }

    TRACE( "RandR returned config @ %p,total_modes found = %d\n", lpXrrconfig, xf86rr_mode_count );

    /* Now populate our mode table */
    xf86rr_mode_count += 1; /* Include default dummy mode */
    xf86rr_modes = HeapAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DDHALMODEINFO) * xf86rr_mode_count );

    /* First mode, the dummy mode, is all 0 */
    mode = 1; /* Start from the 2nd index */

    for( i = 0; i < xf86rr_nsizes; i++ )
    {
       rates = pXRRConfigRates (lpXrrconfig, i, &nrates);

       for( j = 0; j < nrates; j++, mode++ )
       {
          X11DRV_XF86RR_convert_mode( &xf86rr_modes[mode], &xf86rr_sizes[i], &rates[j] );
       }
    }

    X11DRV_DD_ModeSwitch.Modes    = xf86rr_modes;
    X11DRV_DD_ModeSwitch.NumModes = xf86rr_mode_count;

    X11DRV_DD_ModeSwitch.get_current_mode = X11DRV_XF86RR_GetCurrentMode;
    X11DRV_DD_ModeSwitch.set_current_mode = X11DRV_XF86RR_SetCurrentMode;
    X11DRV_DD_ModeSwitch.get_system_mode = X11DRV_XF86RR_GetSystemMode;

    /* Keep track of what rotation we're presently using. We will always use this rotation */
    pXRRConfigCurrentConfiguration( lpXrrconfig, &cur_rr_rotation );      
  }

done:
  wine_tsx11_unlock();

}

void X11DRV_XF86RR_Cleanup(void)
{
  if( !xrandr_loaded ) return;

  if( !bXRandRAvailable ) return;

  bXRandRAvailable = FALSE;

  wine_tsx11_lock();
    pXRRFreeScreenConfigInfo( lpXrrconfig );
  wine_tsx11_unlock();

  lpXrrconfig = NULL;
  xf86rr_sizes = NULL;
  xf86rr_nsizes = 0;
}

static int X11DRV_XF86RR_GetCurrentMode(void)
{
  Rotation cur_rotation;
  int      cur_rr_size;
  int      cur_rr_rate;
  int      cur_mode;

  if( !bXRandRAvailable ) return 0;

  wine_tsx11_lock();  
    cur_rr_size = pXRRConfigCurrentConfiguration( lpXrrconfig, &cur_rotation );
    cur_rr_rate = pXRRConfigCurrentRate (lpXrrconfig);
  wine_tsx11_unlock();

  cur_mode = X11DRV_XF86RR_get_mode_from_rr_size_and_rate( cur_rr_size, cur_rr_rate );

  TRACE( "Returning present mode %d/%d is %d\n", cur_rr_size, cur_rr_rate, cur_mode );

  return cur_mode;
}

static void X11DRV_XF86RR_SetCurrentMode(int new_mode)
{
  Status status;
  int new_rr_size;
  int new_rr_rate;

  if( !bXRandRAvailable ) return;

  X11DRV_XF86RR_get_rr_size_and_rate_from_mode( new_mode, &new_rr_size, &new_rr_rate );

  TRACE( "Switching to mode %d\n", new_mode );
   
  wine_tsx11_lock();
    status = pXRRSetScreenConfigAndRate( gdi_display, lpXrrconfig, root_window,
                                        new_rr_size, cur_rr_rotation, new_rr_rate,
                                        CurrentTime );
  wine_tsx11_unlock();

  TRACE( "Status is %d for setting size=%d and rate=%d\n", status, new_rr_size, new_rr_rate );
}

static int X11DRV_XF86RR_GetSystemMode(void)
{
  if( !bXRandRAvailable ) return 0;
  if (saved_DD_mode) return saved_DD_mode;
  return X11DRV_XF86RR_GetCurrentMode();
}



/* DD HAL functions */

static DWORD PASCAL X11DRV_XF86RR_SetMode(LPDDHAL_SETMODEDATA data)
{
  TRACE( "\n" );


/* PH: Could abstract this...since the logic should be the same for all drivers */
  if( bXRandRAvailable )
  {
    if (!data->dwModeIndex) {
      if (saved_DD_mode) {
        X11DRV_XF86RR_SetCurrentMode(saved_DD_mode);
        saved_DD_mode = 0;
      }
    }
    else {
      X11DRV_ChangeDisplayMode(NULL);
      if (!saved_DD_mode) saved_DD_mode = X11DRV_XF86RR_GetCurrentMode();
      X11DRV_XF86RR_SetCurrentMode(data->dwModeIndex);
    }
  }

  X11DRV_DDHAL_SwitchMode(data->dwModeIndex, NULL, NULL, TRUE);

  data->ddRVal = DD_OK;

  return DDHAL_DRIVER_HANDLED;
}

int X11DRV_XF86RR_CreateDriver(LPDDHALINFO info)
{
  if( !bXRandRAvailable ) return FALSE;

  TRACE( "Creating driver\n" );

  info->dwNumModes = xf86rr_mode_count;
  info->lpModeInfo = xf86rr_modes;

  /* Set the present mode */
  X11DRV_DDHAL_SwitchMode(X11DRV_XF86RR_GetCurrentMode(), NULL, NULL, FALSE);
  info->dwModeIndex = 0;

  info->lpDDCallbacks->SetMode = X11DRV_XF86RR_SetMode;

  return TRUE;
}

#endif /* defined( HAVE_LIBXRANDR ) */
