/*
 * DirectDraw DGA2 interface
 *
 * Copyright 2001 TransGaming Technologies, Inc.
 */

#include "config.h"

#ifdef HAVE_LIBXXF86DGA2

#include "ts_xlib.h"
#include "ts_xf86dga2.h"
#include "x11drv.h"
#include "x11ddraw.h"
#include "dga2.h"

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

WINE_DEFAULT_DEBUG_CHANNEL(x11drv);

static int X11DRV_XF86DGA2_GetCurrentMode(void);
static void X11DRV_XF86DGA2_SetCurrentMode(int mode);
static int X11DRV_XF86DGA2_GetSystemMode(void);

extern int usedga;

LPDDHALMODEINFO xf86dga2_modes;
unsigned xf86dga2_mode_count;
static XDGAMode* modes;
static int dga_event, dga_error;

static void convert_mode(XDGAMode *mode, LPDDHALMODEINFO info)
{
  info->dwWidth        = mode->viewportWidth;
  info->dwHeight       = mode->viewportHeight;
  info->wRefreshRate   = mode->verticalRefresh;
  info->lPitch         = mode->bytesPerScanline;
  info->dwBPP          = (mode->depth < 24) ? mode->depth : mode->bitsPerPixel;
  info->wFlags         = (mode->depth == 8) ? DDMODEINFO_PALETTIZED : 0;
  info->dwRBitMask     = mode->redMask;
  info->dwGBitMask     = mode->greenMask;
  info->dwBBitMask     = mode->blueMask;
  info->dwAlphaBitMask = 0;
  TRACE(" width=%ld, height=%ld, bpp=%ld, refresh=%d\n",
        info->dwWidth, info->dwHeight, info->dwBPP, info->wRefreshRate);
}

void X11DRV_XF86DGA2_Init(void)
{
  int nmodes, major, minor, i;

  if (xf86dga2_modes) return; /* already initialized? */

  /* if in desktop mode, don't use DGA */
  if (root_window != DefaultRootWindow(gdi_display)) return;

  if (!usedga) return;

  if (!TSXDGAQueryExtension(gdi_display, &dga_event, &dga_error)) return;

  if (!TSXDGAQueryVersion(gdi_display, &major, &minor)) return;

  if (major < 2) return; /* only bother with DGA 2+ */

  /* test that it works */
  if (!TSXDGAOpenFramebuffer(gdi_display, DefaultScreen(gdi_display))) {
    WARN("disabling XF86DGA2 (insufficient permissions?)\n");
    return;
  }
  TSXDGACloseFramebuffer(gdi_display, DefaultScreen(gdi_display));

  /* retrieve modes */
  modes = TSXDGAQueryModes(gdi_display, DefaultScreen(gdi_display), &nmodes);
  if (!modes) return;

  TRACE("DGA modes: count=%d\n", nmodes);

  xf86dga2_mode_count = nmodes+1;
  xf86dga2_modes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DDHALMODEINFO) * (nmodes+1));

  /* make dummy mode for exiting DGA */
  memset(&xf86dga2_modes[0], 0, sizeof(xf86dga2_modes[0]));

  /* convert modes to DDHALMODEINFO format */
  for (i=0; i<nmodes; i++)
    convert_mode(&modes[i], &xf86dga2_modes[i+1]);

  X11DRV_DD_ModeSwitch.Modes    = xf86dga2_modes;
  X11DRV_DD_ModeSwitch.NumModes = xf86dga2_mode_count;

  X11DRV_DD_ModeSwitch.get_current_mode = X11DRV_XF86DGA2_GetCurrentMode;
  X11DRV_DD_ModeSwitch.set_current_mode = X11DRV_XF86DGA2_SetCurrentMode;
  X11DRV_DD_ModeSwitch.get_system_mode = X11DRV_XF86DGA2_GetSystemMode;

  TRACE("Enabling XF86DGA2 mode\n");
}

void X11DRV_XF86DGA2_Cleanup(void)
{
  if (modes) TSXFree(modes);
}

static int X11DRV_XF86DGA2_GetCurrentMode(void)
{
  FIXME( ":stub\n" );
  return 0;
}

static void X11DRV_XF86DGA2_SetCurrentMode(int mode)
{
  FIXME( ":stub\n" );
}

static int X11DRV_XF86DGA2_GetSystemMode(void)
{
  FIXME( ":stub\n" );
  return 0;
}

static XDGADevice *dga_dev;

static VIDMEM dga_mem = {
  VIDMEM_ISRECTANGULAR | VIDMEM_ISHEAP
};

static DWORD PASCAL X11DRV_XF86DGA2_SetMode(LPDDHAL_SETMODEDATA data)
{
  LPDDRAWI_DIRECTDRAW_LCL ddlocal = data->lpDD->lpExclusiveOwner;
  DWORD vram;
  Display *display = gdi_display;

  data->ddRVal = DD_OK;
  if (data->dwModeIndex) {
    /* enter DGA */
    XDGADevice *new_dev = NULL;
    if (dga_dev || TSXDGAOpenFramebuffer(display, DefaultScreen(display)))
      new_dev = TSXDGASetMode(display, DefaultScreen(display), modes[data->dwModeIndex-1].num);
    if (new_dev) {
      TSXDGASetViewport(display, DefaultScreen(display), 0, 0, XDGAFlipImmediate);
      if (dga_dev) {
	VirtualFree(dga_dev->data, 0, MEM_RELEASE);
	TSXFree(dga_dev);
      } else {
	TSXDGASelectInput(display, DefaultScreen(display),
			  KeyPressMask|KeyReleaseMask|
			  ButtonPressMask|ButtonReleaseMask|
			  PointerMotionMask);
	X11DRV_EVENT_SetDGAStatus(ddlocal->hWnd, dga_event);
	X11DRV_EVENT_SetInputMethod(X11DRV_INPUT_RELATIVE);
      }
      dga_dev = new_dev;
      vram = dga_dev->mode.bytesPerScanline * dga_dev->mode.imageHeight;
      VirtualAlloc(dga_dev->data, vram, MEM_RESERVE|MEM_SYSTEM, PAGE_READWRITE);
      dga_mem.fpStart = (FLATPTR)dga_dev->data;
      dga_mem.u1.dwWidth = dga_dev->mode.bytesPerScanline;
      dga_mem.u2.dwHeight = dga_dev->mode.imageHeight;
      X11DRV_DDHAL_SwitchMode(data->dwModeIndex, dga_dev->data, &dga_mem, FALSE);
      screen_width = xf86dga2_modes[data->dwModeIndex].dwWidth;
      screen_height = xf86dga2_modes[data->dwModeIndex].dwHeight;
      X11DRV_DD_IsDirect = TRUE;
    }
    else {
      ERR("failed\n");
      if (!dga_dev) TSXDGACloseFramebuffer(display, DefaultScreen(display));
      data->ddRVal = DDERR_GENERIC;
    }
  }
  else if (dga_dev) {
    /* exit DGA */
    X11DRV_DD_IsDirect = FALSE;
    screen_width = default_screen_width;
    screen_height = default_screen_height;
    X11DRV_DDHAL_SwitchMode(0, NULL, NULL, FALSE);
    TSXDGASetMode(display, DefaultScreen(display), 0);
    VirtualFree(dga_dev->data, 0, MEM_RELEASE);
    X11DRV_EVENT_SetInputMethod(X11DRV_INPUT_ABSOLUTE);
    X11DRV_EVENT_SetDGAStatus(0, -1);
    TSXFree(dga_dev);
    TSXDGACloseFramebuffer(display, DefaultScreen(display));
    dga_dev = NULL;
  }
  return DDHAL_DRIVER_HANDLED;
}

static LPDDHAL_CREATESURFACE X11DRV_XF86DGA2_old_create_surface;

static DWORD PASCAL X11DRV_XF86DGA2_CreateSurface(LPDDHAL_CREATESURFACEDATA data)
{
  LPDDRAWI_DDRAWSURFACE_LCL lcl = *data->lplpSList;
  LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
  LPDDSURFACEDESC2 desc = (LPDDSURFACEDESC2)data->lpDDSurfaceDesc;
  HRESULT hr = DDHAL_DRIVER_NOTHANDLED;

  if (X11DRV_XF86DGA2_old_create_surface)
    hr = X11DRV_XF86DGA2_old_create_surface(data);

  if (desc->ddsCaps.dwCaps & (DDSCAPS_PRIMARYSURFACE | DDSCAPS_BACKBUFFER)) {
    gbl->fpVidMem = 0; /* tell ddraw to allocate the memory */
    hr = DDHAL_DRIVER_HANDLED;
  }
  return hr;
}

static DWORD PASCAL X11DRV_XF86DGA2_CreatePalette(LPDDHAL_CREATEPALETTEDATA data)
{
  Display *display = gdi_display;
  data->lpDDPalette->u1.dwReserved1 = TSXDGACreateColormap(display, DefaultScreen(display), dga_dev, AllocAll);
  if (data->lpColorTable)
    X11DRV_DDHAL_SetPalEntries(data->lpDDPalette->u1.dwReserved1, 0, 256,
			       data->lpColorTable);
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_XF86DGA2_Flip(LPDDHAL_FLIPDATA data)
{
  Display *display = gdi_display;
  if (data->lpSurfCurr == X11DRV_DD_Primary) {
    DWORD ofs = data->lpSurfCurr->lpGbl->fpVidMem - dga_mem.fpStart;
    TSXDGASetViewport(display, DefaultScreen(display),
                      (ofs % dga_dev->mode.bytesPerScanline)*8/dga_dev->mode.bitsPerPixel,
                      ofs / dga_dev->mode.bytesPerScanline,
                      XDGAFlipImmediate);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL X11DRV_XF86DGA2_SetPalette(LPDDHAL_SETPALETTEDATA data)
{
  Display *display = gdi_display;
  if ((data->lpDDSurface == X11DRV_DD_Primary) &&
      data->lpDDPalette && data->lpDDPalette->u1.dwReserved1) {
    TSXDGAInstallColormap(display, DefaultScreen(display), data->lpDDPalette->u1.dwReserved1);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

int X11DRV_XF86DGA2_CreateDriver(LPDDHALINFO info)
{
  if (!xf86dga2_mode_count) return 0; /* no DGA */

  info->dwNumModes = xf86dga2_mode_count;
  info->lpModeInfo = xf86dga2_modes;
  info->dwModeIndex = 0;

  X11DRV_XF86DGA2_old_create_surface = info->lpDDCallbacks->CreateSurface;
  info->lpDDCallbacks->SetMode = X11DRV_XF86DGA2_SetMode;
  info->lpDDCallbacks->CreateSurface = X11DRV_XF86DGA2_CreateSurface;
  info->lpDDCallbacks->CreatePalette = X11DRV_XF86DGA2_CreatePalette;
  info->lpDDSurfaceCallbacks->Flip = X11DRV_XF86DGA2_Flip;
  info->lpDDSurfaceCallbacks->SetPalette = X11DRV_XF86DGA2_SetPalette;
  return TRUE;
}

#endif /* HAVE_LIBXXF86DGA2 */
