/*
 * DirectDraw driver interface
 *
 * Copyright 2001-2002 TransGaming Technologies, Inc.
 */

#include "config.h"

#include <string.h>

#include "windef.h"
#include "wingdi.h"
#include "ddrawi.h"
#include "bitmap.h"
#include "wine/debug.h"
#include "sdldrv.h"
#include "osmesa.h"
#include "sdlddraw.h"

WINE_DEFAULT_DEBUG_CHANNEL(sdldrv);

LPDDRAWI_DDRAWSURFACE_LCL SDLDRV_DD_Primary;
LPDDRAWI_DDRAWSURFACE_GBL SDLDRV_DD_PrimaryGbl;
HWND SDLDRV_DD_PrimaryWnd;
HBITMAP SDLDRV_DD_PrimaryDIB;
ATOM SDLDRV_DD_UserClass;
BOOL SDLDRV_DD_IsDirect;

static SDL_Surface* GetSurface(LPDDRAWI_DDRAWSURFACE_LCL lcl)
{
  LPDDRAWI_DDRAWSURFACE_GBL gbl = lcl->lpGbl;
  HBITMAP hbmp = GET_LPDDRAWSURFACE_GBL_MORE(gbl)->hKernelSurface;
  BITMAPOBJ *bmp;
  SDL_Surface *surf;
  if (!hbmp) return NULL;
  bmp = (BITMAPOBJ *)GDI_GetObjPtr(hbmp, BITMAP_MAGIC);
  if (!bmp) return NULL;
  surf = bmp->physBitmap;
  GDI_ReleaseObj(hbmp);
  return surf;
}

static void SetPrimaryDIB(HBITMAP hBmp)
{
  SDLDRV_DD_PrimaryDIB = hBmp;
}

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

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

  if (desc->ddsCaps.dwCaps & DDSCAPS_PRIMARYSURFACE) {
    SDLDRV_DD_Primary = *data->lplpSList;
    SDLDRV_DD_PrimaryWnd = (HWND)SDLDRV_DD_Primary->lpSurfMore->lpDDRAWReserved;
    SDLDRV_DD_PrimaryGbl = SDLDRV_DD_Primary->lpGbl;
    SetPrimaryDIB(GET_LPDDRAWSURFACE_GBL_MORE(SDLDRV_DD_PrimaryGbl)->hKernelSurface);
    SDLDRV_DD_UserClass = GlobalFindAtomA("WINE_DDRAW");

    gbl->fpVidMem = (FLATPTR)primary_surface->pixels;
    gbl->u4.lPitch = primary_surface->pitch;
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
  }
  if (desc->ddsCaps.dwCaps & DDSCAPS_BACKBUFFER) {
    /* let backbuffer point to same data as primary surface,
     * this seems to be the best way to map DX flips onto SDL */
    gbl->fpVidMem = (FLATPTR)primary_surface->pixels;
    gbl->u4.lPitch = primary_surface->pitch;
    data->ddRVal = DD_OK;
    return DDHAL_DRIVER_HANDLED;
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_NOTHANDLED;
}

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

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

static DWORD PASCAL SDLDRV_DDHAL_DestroySurface(LPDDHAL_DESTROYSURFACEDATA data)
{
  if (data->lpDDSurface == SDLDRV_DD_Primary) {
    SDLDRV_DD_Primary = NULL;
    SDLDRV_DD_PrimaryWnd = 0;
    SDLDRV_DD_PrimaryGbl = NULL;
    SetPrimaryDIB(0);
    SDLDRV_DD_UserClass = 0;
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL SDLDRV_DDHAL_Flip(LPDDHAL_FLIPDATA data)
{
  if (data->lpSurfCurr == SDLDRV_DD_Primary) {
    TRACE("flipping primary\n");
    SDL_UpdateRect(primary_surface, 0, 0, 0, 0);
  }
  data->ddRVal = DD_OK;
  return DDHAL_DRIVER_HANDLED;
}

static DWORD PASCAL SDLDRV_DDHAL_Lock(LPDDHAL_LOCKDATA data)
{
  SDL_Surface *surf = GetSurface(data->lpDDSurface);
  DWORD ret = DDHAL_DRIVER_NOTHANDLED;

  TRACE("locking %p (%p)\n", data->lpDDSurface, surf);
  if (surf) {
    SDL_LockSurface(surf);
    data->lpSurfData = surf->pixels;
    ret = DDHAL_DRIVER_HANDLED;
  }
  data->ddRVal = DD_OK;
  return ret;
}

static DWORD PASCAL SDLDRV_DDHAL_Unlock(LPDDHAL_UNLOCKDATA data)
{
  SDL_Surface *surf = GetSurface(data->lpDDSurface);
  DWORD ret = DDHAL_DRIVER_NOTHANDLED;

  if (surf) {
    SDL_UnlockSurface(surf);
    TRACE("unlocked %p (%p)\n", data->lpDDSurface, surf);
    ret = DDHAL_DRIVER_HANDLED;
  }
  if (data->lpDDSurface == SDLDRV_DD_Primary) {
    SDL_UpdateRect(primary_surface, 0, 0, 0, 0);
    TRACE("updated primary\n");
  }
  data->ddRVal = DD_OK;
  return ret;
}

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

static DDHAL_DDSURFACECALLBACKS hal_ddsurfcallbacks = {
  sizeof(DDHAL_DDSURFACECALLBACKS),
  0x3fff, /* all callbacks are 32-bit */
  SDLDRV_DDHAL_DestroySurface,
  SDLDRV_DDHAL_Flip,
  NULL, /* SetClipList */
  SDLDRV_DDHAL_Lock,
  SDLDRV_DDHAL_Unlock,
  NULL, /* Blt */
  NULL, /* SetColorKey */
  NULL, /* AddAttachedSurface */
  NULL, /* GetBltStatus */
  NULL, /* GetFlipStatus */
  NULL, /* UpdateOverlay */
  NULL, /* SetOverlayPosition */
  NULL, /* reserved4 */
  SDLDRV_DDHAL_SetPalette
};

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

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

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

static SDLDEVICE sdldevice = {
  NULL
};

static DWORD PASCAL SDLDRV_DDHAL_GetDriverInfo(LPDDHAL_GETDRIVERINFODATA data)
{
  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 */ 0,					/* dwCaps */
   DDCAPS2_CERTIFIED | DDCAPS2_NONLOCALVIDMEM | DDCAPS2_NOPAGELOCKREQUIRED |
   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 */
  SDLDRV_DDHAL_GetDriverInfo,
  0,	/* dwModeIndex */
  NULL,	/* lpdwFourCC */
  0,	/* dwNumModes */
  NULL,	/* lpModeInfo */
  DDHALINFO_ISPRIMARYDISPLAY | DDHALINFO_MODEXILLEGAL | DDHALINFO_GETDRIVERINFOSET, /* dwFlags */
  &sdldevice, /* lpPDevice */
  0,	/* hInstance */
  0,	/* lpD3DGlobalDriverData */
  0,	/* lpD3DHALCallbacks */
  NULL	/* lpDDExeBufCallbacks */
};

static LPDDHALDDRAWFNS ddraw_fns;
static DWORD ddraw_ver;

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

INT SDLDRV_DCICommand(INT cbInput, const DCICMD *lpCmd, LPVOID lpOutData)
{
  TRACE("(%d,(%ld,%ld,%ld),%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,"sdldrv");
      /* 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;

#if defined(HAVE_OSMESA)
      SDLDRV_OSMesa_CreateDriver(&hal_info);
#endif

      /* FIXME: get sdldrv's hInstance */

      SDLDRV_DDHAL_SetInfo();
      *lpInstance = hal_info.hInstance;
    }
    return TRUE;
  }
  return 0;
}

void SDLDRV_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;

  SDLDRV_DDHAL_SetInfo();
}

INT SDLDRV_DC_ExtEscape( DC *dc, INT escape, INT in_count, LPCVOID in_data,
			 INT out_count, LPVOID out_data )
{
  switch(escape) {
  case QUERYESCSUPPORT:
    if (in_data) {
      switch (*(INT *)in_data) {
      case DCICOMMAND:
	return DD_HAL_VERSION;
      }
    }
    break;

  case DCICOMMAND:
    if (in_data) {
      const DCICMD *lpCmd = in_data;
      if (lpCmd->dwVersion != DD_VERSION) break;
      return SDLDRV_DCICommand(in_count, lpCmd, out_data);
    }
    break;
  }
  return 0;
}
