/*
 * X11DRV desktop window handling
 *
 * Copyright 2001 Alexandre Julliard
 */

#include "config.h"
#include <X11/cursorfont.h>

#include "ts_xlib.h"

#include "wine/winuser16.h"
#include "win.h"
#include "x11drv.h"
#include "x11ddraw.h"
#include "xvidmode.h"
#include "wine/debug.h"

#include "sysmetrics.h"

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);


/* desktop window procedure */
static LRESULT WINAPI desktop_winproc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    switch(message)
    {
    case WM_ERASEBKGND:
        PaintDesktop( (HDC)wParam );
        ValidateRect( hwnd, NULL );
        break;

    case WM_SYSCOMMAND:
        if ((wParam & 0xfff0) == SC_CLOSE) ExitWindows( 0, 0 );
        break;

    case WM_SETCURSOR:
        return SetCursor( LoadCursorA( 0, IDC_ARROWA ) );

    case WM_NCHITTEST:
        return HTCLIENT;
    }
    return 0;
}


/* desktop window manager thread */
static DWORD CALLBACK desktop_thread( LPVOID driver_data )
{
    Display *display;
    MSG msg;
    HWND hwnd;
    WND *win;

    NtCurrentTeb()->driver_data = driver_data;
    display = thread_display();
    hwnd = GetDesktopWindow();

    /* patch the desktop window queue to point to our queue */
    win = WIN_GetPtr( hwnd );
    win->tid = GetCurrentThreadId();
    win->hmemTaskQ = InitThreadInput16( 0, 0 );
    X11DRV_register_window( display, hwnd, win->pDriverData );
    WIN_ReleasePtr( win );

    SetWindowLongW( hwnd, GWL_WNDPROC, (LONG)desktop_winproc );
    wine_tsx11_lock();
    XSetWMProtocols( display, root_window, &wmDeleteWindow, 1 );
    XMapWindow( display, root_window );
    wine_tsx11_unlock();

    while (GetMessageW( &msg, hwnd, 0, 0 )) DispatchMessageW( &msg );
    return 0;
}


/***********************************************************************
 *		X11DRV_create_desktop_thread
 *
 * Create the thread that manages the desktop window
 */
void X11DRV_create_desktop_thread(void)
{
    HANDLE handle = CreateThread( NULL, 0, desktop_thread, NtCurrentTeb()->driver_data, 0, NULL );
    if (!handle)
    {
        MESSAGE( "Could not create desktop thread\n" );
        ExitProcess(1);
    }
    /* we transferred our driver data to the new thread */
    NtCurrentTeb()->driver_data = NULL;
    CloseHandle( handle );
}


/***********************************************************************
 *		X11DRV_create_desktop
 *
 * Create the X11 desktop window for the desktop mode.
 */
Window X11DRV_create_desktop( XVisualInfo *desktop_vi, const char *geometry )
{
    int x = 0, y = 0, flags;
    unsigned int width = 640, height = 480;  /* Default size = 640x480 */
    char *name = GetCommandLineA();
    XSizeHints *size_hints;
    XWMHints   *wm_hints;
    XClassHint *class_hints;
    XSetWindowAttributes win_attr;
    XTextProperty window_name;
    Window win;
    Display *display = thread_display();

    wine_tsx11_lock();
    flags = XParseGeometry( geometry, &x, &y, &width, &height );
    default_screen_width  = width;
    default_screen_height = height;

    /* Create window */
    win_attr.background_pixel = BlackPixel(display, 0);
    win_attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask |
                          PointerMotionMask | ButtonPressMask | ButtonReleaseMask;
    win_attr.cursor = XCreateFontCursor( display, XC_top_left_arrow );

    if (desktop_vi)
        win_attr.colormap = XCreateColormap( display, DefaultRootWindow(display),
                                             visual, AllocNone );
    else
        win_attr.colormap = None;

    win = XCreateWindow( display, DefaultRootWindow(display),
                         x, y, width, height, 0, screen_depth, InputOutput, visual,
                         CWBackPixel | CWEventMask | CWCursor | CWColormap, &win_attr );

    /* Set window manager properties */
    size_hints  = XAllocSizeHints();
    wm_hints    = XAllocWMHints();
    class_hints = XAllocClassHint();
    if (!size_hints || !wm_hints || !class_hints)
    {
        wine_tsx11_unlock();
        MESSAGE("Not enough memory for window manager hints.\n" );
        ExitProcess(1);
    }
    size_hints->min_width = size_hints->max_width = width;
    size_hints->min_height = size_hints->max_height = height;
    size_hints->flags = PMinSize | PMaxSize;
    if (flags & (XValue | YValue)) size_hints->flags |= USPosition;
    if (flags & (WidthValue | HeightValue)) size_hints->flags |= USSize;
    else size_hints->flags |= PSize;

    wm_hints->flags = InputHint | StateHint;
    wm_hints->input = True;
    wm_hints->initial_state = NormalState;
    class_hints->res_name  = "wine";
    class_hints->res_class = "Wine";

    XStringListToTextProperty( &name, 1, &window_name );
    XSetWMProperties( display, win, &window_name, &window_name,
                      NULL, 0, size_hints, wm_hints, class_hints );
    XFree( size_hints );
    XFree( wm_hints );
    XFree( class_hints );
    XFlush( display );
    wine_tsx11_unlock();
    return win;
}

/************************ MODE CHANGE STUFF ***********************/
/*              related to x11ddraw.c and xvidmode.c              */
static int app_mode, old_mode;
int app_resolution_change;
static const int rates[] = { 0 /* the true refresh rate */, 
                             60, 70, 72, 75, 84, 85 /* some fake refresh rates */ };
static const int num_rates = sizeof(rates)/sizeof(rates[0]);

int GetDisplayModeCount() 
{
  int i,j, c = X11DRV_DD_ModeSwitch.NumModes-1; /* yes this should be -1 because NumModes includes a fake mode! */

  /* we always start off with the initial X11DRV_DD_ModeSwitch.NumModes display modes
   * and then follow these with some video modes using fake refresh rates 
   * (but only allowing refresh rates lower than the one detected by xf86config) */
  for( i=1; i<num_rates; i++ ) {
    for( j=1; j<X11DRV_DD_ModeSwitch.NumModes; j++ ) {
      if( rates[i] < X11DRV_DD_ModeSwitch.Modes[j].wRefreshRate ) {
	c++;
      }
    }
  }
  return c;
}

DWORD GetRealMode(DWORD fakemode, unsigned int *refresh)
{
  DWORD i,j, c = X11DRV_DD_ModeSwitch.NumModes-1;
  for( i=1; i<num_rates; i++ ) {
    for( j=1; j<X11DRV_DD_ModeSwitch.NumModes; j++ ) {
      if( rates[i] < X11DRV_DD_ModeSwitch.Modes[j].wRefreshRate ) {
	c++;
	if (c == fakemode) {
	  *refresh = rates[i];
	  return j;
	}
      }
    }
  }
  ERR("unknown fake mode, should not get here\n");
  /* but at least provide a correct refresh rate if we do */
  *refresh = X11DRV_DD_ModeSwitch.Modes[1].wRefreshRate;
  return 1; /* return first mode */
}

BOOL X11DRV_EnumDisplayModes(LPDEVMODEA devmode, DWORD n)
{
  unsigned int depth, depths, refresh;
  int mode_count = GetDisplayModeCount();

  TRACE("requested mode %ld (of %ld/%d)\n", n, X11DRV_DD_ModeSwitch.NumModes, mode_count);

  /* NOTE: Even though this function is documented to fail
           if an incorrect dmSize is passed in, what has been observed of windows's
           actual behaviour is that it overwrites dmSize with the size of the structure.
           Some games seem to depend on this behaviour */
  devmode->dmSize = sizeof(WINE_WIN31_DEVMODEA);
  devmode->dmDriverExtra = 0;

  depth = GetSystemMetrics(SM_WINE_BPP);
  /* in 16bpp we also report 32bpp and in 32bpp we also report 16bpp (since some apps are picky about this) */
  if (depth >= 16) depths = 2;
  else depths = 1;

  if (n == ENUM_REGISTRY_SETTINGS ||
      n == ENUM_CURRENT_SETTINGS ||
      n == 0) {
    if (!X11DRV_DD_ModeSwitch.NumModes) { 
      /* mode switching not available (includes desktop mode) */
      devmode->dmDisplayFlags = 0;
      devmode->dmDisplayFrequency = 0; /* FIXME: Not correct */
      devmode->dmBitsPerPel = GetSystemMetrics(SM_WINE_BPP);
      devmode->dmPelsHeight = GetSystemMetrics(SM_CYSCREEN);
      devmode->dmPelsWidth = GetSystemMetrics(SM_CXSCREEN);
      devmode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH |
                          DM_PELSHEIGHT | DM_DISPLAYFLAGS;
      TRACE("returning (default): dmDisplayFlags = %ld dmDisplayFrequency = %ld, dmBitsPerPel = %ld,\n\tdmPelsWidth = %ld, dmPelsHeight = %ld\n",
                  devmode->dmDisplayFlags, devmode->dmDisplayFrequency, devmode->dmBitsPerPel,
                  devmode->dmPelsWidth, devmode->dmPelsHeight);
      return TRUE;
    }
    if (app_resolution_change) {
      if (n == ENUM_REGISTRY_SETTINGS) n = old_mode;
      else n = app_mode;
    }
    else n = X11DRV_DD_ModeSwitch.get_system_mode();
  }
  if (n > mode_count * depths)
  {
    return FALSE;
  }
  /* fake some 16 or 32 bpp modes as necessary */
  if (n > mode_count)
  {
    /* if 16bpp we're faking 32bpp otherwise we are in 32bpp faking 16bpp */
    depth = (depth==16) ? 32 : 16;
    n -= mode_count;
  }
  /* fake some lower refresh rates if necessary */
  if (n >= X11DRV_DD_ModeSwitch.NumModes)
  {
    n = GetRealMode(n, &refresh);
  } else {
    refresh = X11DRV_DD_ModeSwitch.Modes[n].wRefreshRate;
  }

  /* fill the requested data */
  devmode->dmDisplayFlags = 0;
  devmode->dmDisplayFrequency = refresh;
  devmode->dmBitsPerPel = depth;
  devmode->dmPelsHeight = X11DRV_DD_ModeSwitch.Modes[n].dwHeight;
  devmode->dmPelsWidth = X11DRV_DD_ModeSwitch.Modes[n].dwWidth;
  devmode->dmFields = DM_BITSPERPEL | DM_PELSWIDTH |
                      DM_PELSHEIGHT | DM_DISPLAYFREQUENCY |
                      DM_DISPLAYFLAGS;

  TRACE("returning: dmDisplayFlags = %ld dmDisplayFrequency = %ld, dmBitsPerPel = %ld,\n\tdmPelsWidth = %ld, dmPelsHeight = %ld\n",
                  devmode->dmDisplayFlags, devmode->dmDisplayFrequency, devmode->dmBitsPerPel,
                  devmode->dmPelsWidth, devmode->dmPelsHeight);
  return TRUE;
 }

BOOL X11DRV_ChangeDisplayMode(LPDEVMODEA devmode)
{
  int new_mode = -1;
  BOOL got_mode = devmode && devmode->dmFields;

  if (got_mode) {
    TRACE("switching to %ldx%ldx%ld (%ld Hz)\n", devmode->dmPelsWidth, devmode->dmPelsHeight, devmode->dmBitsPerPel, devmode->dmDisplayFrequency);
    TRACE("fields %08lx\n", devmode->dmFields);
    TRACE("flags %08lx\n", devmode->dmDisplayFlags);
  } else {
    TRACE("switching to default mode\n");
  }

  /* deinitialize old mode, if any */
  if (app_resolution_change) {
    X11DRV_DisableGrabs();
    app_resolution_change = FALSE;
    new_mode = old_mode;
    app_mode = 0;
    X11DRV_DD_DestroyDesktop();
  }


  /* initialize new mode, if any */
  if (got_mode) {
    int i, best = -1;
    app_resolution_change = TRUE;
    if (new_mode != -1) old_mode = new_mode;
    else old_mode = X11DRV_DD_ModeSwitch.get_current_mode();

    for (i = 0; i < X11DRV_DD_ModeSwitch.NumModes; i++)
    {
      TRACE(" I found a mode: X11DRV_DD_ModeSwitch.Modes[%i] width = %ld\n", i, X11DRV_DD_ModeSwitch.Modes[i].dwWidth);
      /* we want to choose the smallest mode available that is still large enough for us
       * I'm not doing BPP or refresh rate matching, because its unneccesary. Just res is fine. */
      if (X11DRV_DD_ModeSwitch.Modes[i].dwWidth >= devmode->dmPelsWidth &&
          X11DRV_DD_ModeSwitch.Modes[i].dwHeight >= devmode->dmPelsHeight)
      {
        if (best == -1) best = i;
        else
        {
          if (X11DRV_DD_ModeSwitch.Modes[i].dwWidth < X11DRV_DD_ModeSwitch.Modes[best].dwWidth ||
              X11DRV_DD_ModeSwitch.Modes[i].dwHeight < X11DRV_DD_ModeSwitch.Modes[best].dwHeight)
            best = i;
        }
      }
    }
    if (best == -1)
    {
      TRACE( "%s requested (%ldx%ld). Using current mode.\n",
               X11DRV_DD_ModeSwitch.NumModes ? "All modes available smaller than"
	                         : "No modes available for", 
               devmode->dmPelsWidth, devmode->dmPelsHeight);
      best = old_mode;
    }

    new_mode = best;
    app_mode = new_mode;

    X11DRV_DD_CreateDesktop(devmode->dmPelsWidth, devmode->dmPelsHeight);
    X11DRV_EnableGrabs();
  }

  if (new_mode == -1) /* bogus request */
    return FALSE;

  if( X11DRV_DD_ModeSwitch.Modes )
  {
    TRACE (" using mode: %d (%ldx%ld)\n", new_mode, X11DRV_DD_ModeSwitch.Modes[new_mode].dwWidth, X11DRV_DD_ModeSwitch.Modes[new_mode].dwHeight);
    X11DRV_DD_ModeSwitch.set_current_mode( new_mode );
  }
  else
  {
    TRACE( "No modes available.\n" );
  }

  return got_mode;
}
