/*
 * DirectDraw driver interface
 *
 * Copyright 2001 TransGaming Technologies, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * 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 GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#include <stdarg.h>
#include <string.h>
#include <X11/Xlib.h>

#define NONAMELESSUNION
#define NONAMELESSSTRUCT
#include "x11drv.h"

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

typedef struct _X11DRIVERINFO {
  const GUID *		lpGuid;
  DWORD			dwSize;
  LPVOID		lpvData;
  struct _X11DRIVERINFO*lpNext;
} X11DRIVERINFO,*LPX11DRIVERINFO;

typedef struct _X11DEVICE {
  LPX11DRIVERINFO	lpInfo;
} X11DEVICE,*LPX11DEVICE;

static LPDDRAWI_DDRAWSURFACE_LCL X11DRV_DD_Primary;
static LPDDRAWI_DDRAWSURFACE_GBL X11DRV_DD_PrimaryGbl;
static HWND X11DRV_DD_PrimaryWnd;
static HBITMAP X11DRV_DD_PrimaryDIB;
static Drawable X11DRV_DD_PrimaryDrawable;
static ATOM X11DRV_DD_UserClass;
static UINT X11DRV_DD_GrabMessage;
static WNDPROC X11DRV_DD_GrabOldProcedure;

static void X11DRV_DDHAL_SetPalEntries(Colormap pal, DWORD dwBase, DWORD dwNumEntries,
                                       LPPALETTEENTRY lpEntries);

static void SetPrimaryDIB(HBITMAP hBmp)
{
  X11DRV_DD_PrimaryDIB = hBmp;
  if (hBmp) {
    X11DRV_DD_PrimaryDrawable = X11DRV_get_pixmap( hBmp );
  } else {
    X11DRV_DD_PrimaryDrawable = 0;
  }
}

static LRESULT WINAPI GrabWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  struct x11drv_thread_data *data = x11drv_thread_data();

  if(message != X11DRV_DD_GrabMessage)
    return CallWindowProcA(X11DRV_DD_GrabOldProcedure, hWnd, message, wParam, lParam);

  TRACE("hwnd=%p, grab=%ld\n", hWnd, wParam);

  if (wParam)
  {
    /* find the X11 window that ddraw uses */
    Window win = X11DRV_get_whole_window(hWnd);
    TRACE("X11 window: %ld\n", win);
    if (!win) {
      TRACE("host off desktop\n");
      win = root_window;
    }

    wine_tsx11_lock();
    XGrabPointer(data->display, win, True, 0, GrabModeAsync, GrabModeAsync, win, None, CurrentTime);
    wine_tsx11_unlock();
    data->grab_window = win;
  }
  else
  {
    wine_tsx11_lock();
    XUngrabPointer(data->display, CurrentTime);
    wine_tsx11_unlock();
    data->grab_window = None;
  }

  return 0;
}

static void GrabPointer(BOOL grab)
{
  if(grab) {
    Window window = X11DRV_get_whole_window(GetFocus());
    if(window)
    {
      wine_tsx11_lock();
      XSetInputFocus(thread_display(), window, RevertToParent, CurrentTime);
      wine_tsx11_unlock();
    }
  }

  if(!X11DRV_DD_GrabMessage)
    X11DRV_DD_GrabMessage = RegisterWindowMessageA("WINE_X11DRV_GRABPOINTER");

  X11DRV_DD_GrabOldProcedure = (WNDPROC)SetWindowLongPtrA(X11DRV_DD_PrimaryWnd,
                                                       GWLP_WNDPROC, (LONG_PTR)GrabWndProc);

  SendMessageW(X11DRV_DD_PrimaryWnd, X11DRV_DD_GrabMessage, grab, 0);

  if(SetWindowLongPtrA(X11DRV_DD_PrimaryWnd, GWLP_WNDPROC,
                    (LONG_PTR)X11DRV_DD_GrabOldProcedure) != (LONG_PTR)GrabWndProc)
    ERR("Window procedure has been changed!\n");
}

static DWORD PASCAL X11DRV_DDHAL_DestroyDriver(LPDDHAL_DESTROYDRIVERDATA data)
{
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_CreateSurface(LPDDHAL_CREATESURFACEDATA data)
{
  if (data->lpDDSurfaceDesc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) {
    X11DRV_DD_Primary = *data->lplpSList;
    X11DRV_DD_PrimaryWnd = (HWND)X11DRV_DD_Primary->lpSurfMore->lpDDRAWReserved;
    X11DRV_DD_PrimaryGbl = X11DRV_DD_Primary->lpGbl;
    SetPrimaryDIB((HBITMAP)GET_LPDDRAWSURFACE_GBL_MORE(X11DRV_DD_PrimaryGbl)->hKernelSurface);
    X11DRV_DD_UserClass = GlobalFindAtomA("WINE_DDRAW");
    if (dxgrab) GrabPointer(TRUE);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_NOTHANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_CreatePalette(LPDDHAL_CREATEPALETTEDATA data)
{
  FIXME("stub\n");
  /* only makes sense to do anything if the X server is running at 8bpp,
   * which few people do nowadays */
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDCALLBACKS hal_ddcallbacks = {
  sizeof(DDHAL_DDCALLBACKS),
  0x3ff, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroyDriver,
  X11DRV_DDHAL_CreateSurface,
  NULL, /* SetColorKey */
  NULL, /* SetMode */
  NULL, /* WaitForVerticalBlank */
  NULL, /* CanCreateSurface */
  X11DRV_DDHAL_CreatePalette,
  NULL, /* GetScanLine */
  NULL, /* SetExclusiveMode */
  NULL  /* FlipToGDISurface */
};

static DWORD PASCAL X11DRV_DDHAL_DestroySurface(LPDDHAL_DESTROYSURFACEDATA data)
{
  if (data->lpDDSurface == X11DRV_DD_Primary) {
    if (dxgrab) GrabPointer(FALSE);
    X11DRV_DD_Primary = NULL;
    X11DRV_DD_PrimaryWnd = 0;
    X11DRV_DD_PrimaryGbl = NULL;
    SetPrimaryDIB(0);
    X11DRV_DD_UserClass = 0;
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_SetPalette(LPDDHAL_SETPALETTEDATA data)
{
  if (data->lpDDPalette && data->lpDDPalette->u1.dwReserved1) {
    if (data->lpDDSurface == X11DRV_DD_Primary) {
      FIXME("stub\n");
      /* we should probably find the ddraw window (maybe data->lpDD->lpExclusiveOwner->hWnd),
       * and attach the palette to it
       *
       * Colormap pal = data->lpDDPalette->u1.dwReserved1;
       */
    }
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDSURFACECALLBACKS hal_ddsurfcallbacks = {
  sizeof(DDHAL_DDSURFACECALLBACKS),
  0x3fff, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroySurface,
  NULL, /* Flip */
  NULL, /* SetClipList */
  NULL, /* Lock */
  NULL, /* Unlock */
  NULL, /* Blt */
  NULL, /* SetColorKey */
  NULL, /* AddAttachedSurface */
  NULL, /* GetBltStatus */
  NULL, /* GetFlipStatus */
  NULL, /* UpdateOverlay */
  NULL, /* SetOverlayPosition */
  NULL, /* reserved4 */
  X11DRV_DDHAL_SetPalette
};

static DWORD PASCAL X11DRV_DDHAL_DestroyPalette(LPDDHAL_DESTROYPALETTEDATA data)
{
  Colormap pal = data->lpDDPalette->u1.dwReserved1;
  if (pal)
  {
      wine_tsx11_lock();
      XFreeColormap(gdi_display, pal);
      wine_tsx11_unlock();
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_DDHAL_SetPaletteEntries(LPDDHAL_SETENTRIESDATA data)
{
  X11DRV_DDHAL_SetPalEntries(data->lpDDPalette->u1.dwReserved1,
			     data->dwBase, data->dwNumEntries,
			     data->lpEntries);
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DDHAL_DDPALETTECALLBACKS hal_ddpalcallbacks = {
  sizeof(DDHAL_DDPALETTECALLBACKS),
  0x3, /* all callbacks are 32-bit */
  X11DRV_DDHAL_DestroyPalette,
  X11DRV_DDHAL_SetPaletteEntries
};

static X11DEVICE x11device = {
  NULL
};

static DWORD PASCAL X11DRV_DDHAL_GetDriverInfo(LPDDHAL_GETDRIVERINFODATA data)
{
  LPX11DRIVERINFO info = x11device.lpInfo;
  while (info) {
    if (IsEqualGUID(&data->guidInfo, info->lpGuid)) {
      DWORD dwSize = info->dwSize;
      data->dwActualSize = dwSize;
      if (data->dwExpectedSize < dwSize) dwSize = data->dwExpectedSize;
      memcpy(data->lpvData, info->lpvData, dwSize);
      data->ddRVal = DD_OK;
      return DDHAL_DRIVER_HANDLED;
    }
    info = info->lpNext;
  }
  data->ddRVal = DDERR_CURRENTLYNOTAVAIL;
  return DDHAL_DRIVER_HANDLED;
}

static DDHALINFO hal_info = {
  sizeof(DDHALINFO),
  &hal_ddcallbacks,
  &hal_ddsurfcallbacks,
  &hal_ddpalcallbacks,
  {	/* vmiData */
   0	 /* fpPrimary */
  },
  {	/* ddCaps (only stuff the HAL implements here) */
   sizeof(DDCORECAPS),							/* dwSize */
   DDCAPS_GDI | DDCAPS_PALETTE,						/* dwCaps */
   DDCAPS2_CERTIFIED | DDCAPS2_NONLOCALVIDMEM | DDCAPS2_NOPAGELOCKREQUIRED | DDCAPS2_CANRENDERWINDOWED |
   DDCAPS2_WIDESURFACES | DDCAPS2_PRIMARYGAMMA | DDCAPS2_FLIPNOVSYNC,   /* dwCaps2 */
   0,									/* dwCKeyCaps */
   0,									/* dwFXCaps */
   0,									/* dwFXAlphaCaps */
   DDPCAPS_8BIT | DDPCAPS_PRIMARYSURFACE,				/* dwPalCaps */
   0,									/* dwSVCaps */
   0,									/* dwAlphaBltConstBitDepths */
   0,									/* dwAlphaBltPixelBitDepths */
   0,									/* dwAlphaBltSurfaceBitDepths */
   0,									/* dwAlphaOverlayBltConstBitDepths */
   0,									/* dwAlphaOverlayBltPixelBitDepths */
   0,									/* dwAlphaOverlayBltSurfaceBitDepths */
   0,									/* dwZBufferBitDepths */
   16*1024*1024,							/* dwVidMemTotal */
   16*1024*1024,							/* dwVidMemFree */
   0,									/* dwMaxVisibleOverlays */
   0,									/* dwCurrVisibleOverlays */
   0,									/* dwNumFourCCCodes */
   0,									/* dwAlignBoundarySrc */
   0,									/* dwAlignSizeSrc */
   0,									/* dwAlignBoundaryDest */
   0,									/* dwAlignSizeDest */
   0,									/* dwAlignStrideAlign */
   {0},									/* dwRops */
   {									/* ddsCaps */
    DDSCAPS_BACKBUFFER | DDSCAPS_FLIP | DDSCAPS_FRONTBUFFER |
    DDSCAPS_OFFSCREENPLAIN | DDSCAPS_PALETTE | DDSCAPS_PRIMARYSURFACE |
    DDSCAPS_SYSTEMMEMORY | DDSCAPS_TEXTURE | DDSCAPS_VIDEOMEMORY |
    DDSCAPS_VISIBLE | DDSCAPS_LOCALVIDMEM | DDSCAPS_NONLOCALVIDMEM	/* dwCaps */
   },
   0,									/* dwMinOverlayStretch */
   0,									/* dwMaxOverlayStretch */
   0,									/* dwMinLiveVideoStretch */
   0,									/* dwMaxLiveVideoStretch */
   0,									/* dwMinHwCodecStretch */
   0,									/* dwMaxHwCodecStretch */
   0,									/* dwReserved1 */
   0,									/* dwReserved2 */
   0,									/* dwReserved2 */
   0,									/* dwSVBCaps */
   0,									/* dwSVBCKeyCaps */
   0,									/* dwSVBFXCaps */
   {0},									/* dwSVBRops */
   0,									/* dwVSBCaps */
   0,									/* dwVSBCKeyCaps */
   0,									/* dwVSBFXCaps */
   {0},									/* dwVSBRops */
   0,									/* dwSSBCaps */
   0,									/* dwSSBCKeyCaps */
   0,									/* dwSSBFXCaps */
   {0},									/* dwSSBRops */
   0,									/* dwMaxVideoPorts */
   0,									/* dwCurrVideoPorts */
   0									/* dwSVBCaps */
  },
  0,	/* dwMonitorFrequency */
  X11DRV_DDHAL_GetDriverInfo,
  0,	/* dwModeIndex */
  NULL,	/* lpdwFourCC */
  0,	/* dwNumModes */
  NULL,	/* lpModeInfo */
  DDHALINFO_ISPRIMARYDISPLAY | DDHALINFO_MODEXILLEGAL | DDHALINFO_GETDRIVERINFOSET, /* dwFlags */
  &x11device,
  0,	/* hInstance */
  0,	/* lpD3DGlobalDriverData */
  0,	/* lpD3DHALCallbacks */
  NULL	/* lpDDExeBufCallbacks */
};

static LPDDHALDDRAWFNS ddraw_fns;
static DWORD ddraw_ver;

static void X11DRV_DDHAL_SetInfo(void)
{
  (ddraw_fns->lpSetInfo)(&hal_info, FALSE);
}

INT X11DRV_DCICommand(INT cbInput, const DCICMD *lpCmd, LPVOID lpOutData)
{
  TRACE("(%d,(%d,%d,%d),%p)\n", cbInput, lpCmd->dwCommand,
	lpCmd->dwParam1, lpCmd->dwParam2, lpOutData);

  switch (lpCmd->dwCommand) {
  case DDNEWCALLBACKFNS:
    ddraw_fns = (LPDDHALDDRAWFNS)lpCmd->dwParam1;
    return TRUE;
  case DDVERSIONINFO:
    {
      LPDDVERSIONDATA lpVer = (LPDDVERSIONDATA)lpOutData;
      ddraw_ver = lpCmd->dwParam1;
      if (!lpVer) break;
      /* well, whatever... the DDK says so */
      lpVer->dwHALVersion = DD_RUNTIME_VERSION;
    }
    return TRUE;
  case DDGET32BITDRIVERNAME:
    {
      LPDD32BITDRIVERDATA lpData = (LPDD32BITDRIVERDATA)lpOutData;
      /* here, we could ask ddraw to load a separate DLL, that
       * would contain the 32-bit ddraw HAL */
      strcpy(lpData->szName,"x11drv");
      /* the entry point named here should initialize our hal_info
       * with 32-bit entry points (ignored for now) */
      strcpy(lpData->szEntryPoint,"DriverInit");
      lpData->dwContext = 0;
    }
    return TRUE;
  case DDCREATEDRIVEROBJECT:
    {
      LPDWORD lpInstance = (LPDWORD)lpOutData;

      /* FIXME: get x11drv's hInstance */
      X11DRV_Settings_CreateDriver(&hal_info);

      (ddraw_fns->lpSetInfo)(&hal_info, FALSE);
      *lpInstance = hal_info.hInstance;
    }
    return TRUE;
  }
  return 0;
}

void X11DRV_DDHAL_SwitchMode(DWORD dwModeIndex, LPVOID fb_addr, LPVIDMEM fb_mem)
{
  LPDDHALMODEINFO info = &hal_info.lpModeInfo[dwModeIndex];

  hal_info.dwModeIndex        = dwModeIndex;
  hal_info.dwMonitorFrequency = info->wRefreshRate;
  hal_info.vmiData.fpPrimary  = (FLATPTR)fb_addr;
  hal_info.vmiData.dwDisplayWidth  = info->dwWidth;
  hal_info.vmiData.dwDisplayHeight = info->dwHeight;
  hal_info.vmiData.lDisplayPitch   = info->lPitch;
  hal_info.vmiData.ddpfDisplay.dwSize = info->dwBPP ? sizeof(hal_info.vmiData.ddpfDisplay) : 0;
  hal_info.vmiData.ddpfDisplay.dwFlags = (info->wFlags & DDMODEINFO_PALETTIZED) ? DDPF_PALETTEINDEXED8 : 0;
  hal_info.vmiData.ddpfDisplay.u1.dwRGBBitCount = (info->dwBPP > 24) ? 24 : info->dwBPP;
  hal_info.vmiData.ddpfDisplay.u2.dwRBitMask = info->dwRBitMask;
  hal_info.vmiData.ddpfDisplay.u3.dwGBitMask = info->dwGBitMask;
  hal_info.vmiData.ddpfDisplay.u4.dwBBitMask = info->dwBBitMask;
  hal_info.vmiData.dwNumHeaps = fb_mem ? 1 : 0;
  hal_info.vmiData.pvmList = fb_mem;

  X11DRV_DDHAL_SetInfo();
}

static void X11DRV_DDHAL_SetPalEntries(Colormap pal, DWORD dwBase, DWORD dwNumEntries,
                                       LPPALETTEENTRY lpEntries)
{
  XColor c;
  unsigned int n;

  if (pal) {
    wine_tsx11_lock();
    c.flags = DoRed|DoGreen|DoBlue;
    c.pixel = dwBase;
    for (n=0; n<dwNumEntries; n++,c.pixel++) {
      c.red   = lpEntries[n].peRed   << 8;
      c.green = lpEntries[n].peGreen << 8;
      c.blue  = lpEntries[n].peBlue  << 8;
      XStoreColor(gdi_display, pal, &c);
    }
    XFlush(gdi_display); /* update display immediately */
    wine_tsx11_unlock();
  }
}