/*
 *Copyright (C) 1994-2000 The XFree86 Project, Inc. All Rights Reserved.
 *
 *Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 *"Software"), to deal in the Software without restriction, including
 *without limitation the rights to use, copy, modify, merge, publish,
 *distribute, sublicense, and/or sell copies of the Software, and to
 *permit persons to whom the Software is furnished to do so, subject to
 *the following conditions:
 *
 *The above copyright notice and this permission notice shall be
 *included in all copies or substantial portions of the Software.
 *
 *THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *NONINFRINGEMENT. IN NO EVENT SHALL THE XFREE86 PROJECT BE LIABLE FOR
 *ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 *CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 *Except as contained in this notice, the name of the XFree86 Project
 *shall not be used in advertising or otherwise to promote the sale, use
 *or other dealings in this Software without prior written authorization
 *from the XFree86 Project.
 *
 * Authors:	Dakshinamurthy Karra
 *		Suhaib M Siddiqi
 *		Peter Busch
 *		Harold L Hunt II
 */

#ifdef HAVE_XWIN_CONFIG_H
#include <xwin-config.h>
#endif
#include "win.h"
#include "winmsg.h"
#include <cursorstr.h>
#include <mipointrst.h>
#include <servermd.h>
#include "misc.h"

#define BRIGHTNESS(x) (x##Red * 0.299 + x##Green * 0.587 + x##Blue * 0.114)

#if 0
# define WIN_DEBUG_MSG winDebug
#else
# define WIN_DEBUG_MSG(...)
#endif

/*
 * Local function prototypes
 */

static void
winPointerWarpCursor (DeviceIntPtr pDev, ScreenPtr pScreen, int x, int y);

static Bool
winCursorOffScreen (ScreenPtr *ppScreen, int *x, int *y);

static void
winCrossScreen (ScreenPtr pScreen, Bool fEntering);

miPointerScreenFuncRec g_winPointerCursorFuncs =
{
  winCursorOffScreen,
  winCrossScreen,
  winPointerWarpCursor
};


static void
winPointerWarpCursor (DeviceIntPtr pDev, ScreenPtr pScreen, int x, int y)
{
  winScreenPriv(pScreen);
  RECT			rcClient;
  static Bool		s_fInitialWarp = TRUE;

  /* Discard first warp call */
  if (s_fInitialWarp)
    {
      /* First warp moves mouse to center of window, just ignore it */

      /* Don't ignore subsequent warps */
      s_fInitialWarp = FALSE;

      winErrorFVerb (2, "winPointerWarpCursor - Discarding first warp: %d %d\n",
	      x, y);
      
      return;
    }

  /*
     Only update the Windows cursor position if root window is active,
     or we are in a rootless mode
  */
  if ((pScreenPriv->hwndScreen == GetForegroundWindow ())
      || pScreenPriv->pScreenInfo->fRootless
#ifdef XWIN_MULTIWINDOW
      || pScreenPriv->pScreenInfo->fMultiWindow
#endif
      )
    {
      /* Get the client area coordinates */
      GetClientRect (pScreenPriv->hwndScreen, &rcClient);
      
      /* Translate the client area coords to screen coords */
      MapWindowPoints (pScreenPriv->hwndScreen,
		       HWND_DESKTOP,
		       (LPPOINT)&rcClient,
		       2);
      
      /* 
       * Update the Windows cursor position so that we don't
       * immediately warp back to the current position.
       */
      SetCursorPos (rcClient.left + x, rcClient.top + y);
    }

  /* Call the mi warp procedure to do the actual warping in X. */
  miPointerWarpCursor (pDev, pScreen, x, y);
}

static Bool
winCursorOffScreen (ScreenPtr *ppScreen, int *x, int *y)
{
  return FALSE;
}

static void
winCrossScreen (ScreenPtr pScreen, Bool fEntering)
{
}

static unsigned char
reverse(unsigned char c)
{
  int i;
  unsigned char ret = 0;
  for (i = 0; i < 8; ++i)
    {
      ret |= ((c >> i)&1) << (7 - i);
    }
  return ret;
}

/*
 * Convert X cursor to Windows cursor
 * FIXME: Perhaps there are more smart code
 */
static HCURSOR
winLoadCursor (ScreenPtr pScreen, CursorPtr pCursor, int screen)
{
  winScreenPriv(pScreen);
  HCURSOR hCursor = NULL;
  unsigned char *pAnd;
  unsigned char *pXor;
  int nCX, nCY;
  int nBytes;
  double dForeY, dBackY;
  BOOL fReverse;
  HBITMAP hAnd, hXor;
  ICONINFO ii;
  unsigned char *pCur;
  int x, y;
  unsigned char bit;
  HDC hDC;
  BITMAPV4HEADER bi;
  BITMAPINFO *pbmi;
  unsigned long *lpBits;

  WIN_DEBUG_MSG("winLoadCursor: Win32: %dx%d X11: %dx%d hotspot: %d,%d\n", 
          pScreenPriv->cursor.sm_cx, pScreenPriv->cursor.sm_cy,
          pCursor->bits->width, pCursor->bits->height,
          pCursor->bits->xhot, pCursor->bits->yhot
          );

  /* We can use only White and Black, so calc brightness of color 
   * Also check if the cursor is inverted */  
  dForeY = BRIGHTNESS(pCursor->fore);
  dBackY = BRIGHTNESS(pCursor->back);
  fReverse = dForeY < dBackY;
 
  /* Check wether the X11 cursor is bigger than the win32 cursor */
  if (pScreenPriv->cursor.sm_cx < pCursor->bits->width || 
      pScreenPriv->cursor.sm_cy < pCursor->bits->height)
    {
      winErrorFVerb (3, "winLoadCursor - Windows requires %dx%d cursor but X requires %dx%d\n",
	      pScreenPriv->cursor.sm_cx, pScreenPriv->cursor.sm_cy,
	      pCursor->bits->width, pCursor->bits->height);
    }

  /* Get the number of bytes required to store the whole cursor image 
   * This is roughly (sm_cx * sm_cy) / 8 
   * round up to 8 pixel boundary so we can convert whole bytes */
  nBytes = bits_to_bytes(pScreenPriv->cursor.sm_cx) * pScreenPriv->cursor.sm_cy;

  /* Get the effective width and height */
  nCX = min(pScreenPriv->cursor.sm_cx, pCursor->bits->width);
  nCY = min(pScreenPriv->cursor.sm_cy, pCursor->bits->height);

  /* Allocate memory for the bitmaps */
  pAnd = malloc (nBytes);
  memset (pAnd, 0xFF, nBytes);
  pXor = calloc (1, nBytes);

  /* Convert the X11 bitmap to a win32 bitmap 
   * The first is for an empty mask */
  if (pCursor->bits->emptyMask)
    {
      int x, y, xmax = bits_to_bytes(nCX);
      for (y = 0; y < nCY; ++y)
	for (x = 0; x < xmax; ++x)
	  {
	    int nWinPix = bits_to_bytes(pScreenPriv->cursor.sm_cx) * y + x;
	    int nXPix = BitmapBytePad(pCursor->bits->width) * y + x;

	    pAnd[nWinPix] = 0;
	    if (fReverse)
	      pXor[nWinPix] = reverse (~pCursor->bits->source[nXPix]);
	    else
	      pXor[nWinPix] = reverse (pCursor->bits->source[nXPix]);
	  }
    }
  else
    {
      int x, y, xmax = bits_to_bytes(nCX);
      for (y = 0; y < nCY; ++y)
	for (x = 0; x < xmax; ++x)
	  {
	    int nWinPix = bits_to_bytes(pScreenPriv->cursor.sm_cx) * y + x;
	    int nXPix = BitmapBytePad(pCursor->bits->width) * y + x;

	    unsigned char mask = pCursor->bits->mask[nXPix];
	    pAnd[nWinPix] = reverse (~mask);
	    if (fReverse)
	      pXor[nWinPix] = reverse (~pCursor->bits->source[nXPix] & mask);
	    else
	      pXor[nWinPix] = reverse (pCursor->bits->source[nXPix] & mask);
	  }
    }

  /* prepare the pointers */ 
  hCursor = NULL;
  lpBits = NULL;

  /* We have a truecolor alpha-blended cursor and can use it! */
  if (pCursor->bits->argb) 
    {
      WIN_DEBUG_MSG("winLoadCursor: Trying truecolor alphablended cursor\n"); 
      memset (&bi, 0, sizeof (BITMAPV4HEADER));
      bi.bV4Size = sizeof(BITMAPV4HEADER);
      bi.bV4Width = pScreenPriv->cursor.sm_cx;
      bi.bV4Height = -(pScreenPriv->cursor.sm_cy); /* right-side up */
      bi.bV4Planes = 1;
      bi.bV4BitCount = 32;
      bi.bV4V4Compression = BI_BITFIELDS;
      bi.bV4RedMask = 0x00FF0000;
      bi.bV4GreenMask = 0x0000FF00;
      bi.bV4BlueMask = 0x000000FF;
      bi.bV4AlphaMask = 0xFF000000; 
      
      lpBits = (unsigned long *) calloc (pScreenPriv->cursor.sm_cx*pScreenPriv->cursor.sm_cy,
					 sizeof (unsigned long));
      
      if (lpBits)
	{
	  for (y=0; y<nCY; y++)
	    {
	      unsigned long *src, *dst;
	      src = &(pCursor->bits->argb[y * pCursor->bits->width]);
	      dst = &(lpBits[y * pScreenPriv->cursor.sm_cx]);
	      memcpy (dst, src, 4*nCX);
	    }
	}
    } /* End if-truecolor-icon */
  
  if (!lpBits)
    {
      /* Bicolor, use a palettized DIB */
      WIN_DEBUG_MSG("winLoadCursor: Trying two color cursor\n"); 
      pbmi = (BITMAPINFO*)&bi;
      memset (pbmi, 0, sizeof (BITMAPINFOHEADER));
      pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
      pbmi->bmiHeader.biWidth = pScreenPriv->cursor.sm_cx;
      pbmi->bmiHeader.biHeight = -abs(pScreenPriv->cursor.sm_cy); /* right-side up */
      pbmi->bmiHeader.biPlanes = 1;
      pbmi->bmiHeader.biBitCount = 8;
      pbmi->bmiHeader.biCompression = BI_RGB;
      pbmi->bmiHeader.biSizeImage = 0;
      pbmi->bmiHeader.biClrUsed = 3;
      pbmi->bmiHeader.biClrImportant = 3;
      pbmi->bmiColors[0].rgbRed = 0; /* Empty */
      pbmi->bmiColors[0].rgbGreen = 0;
      pbmi->bmiColors[0].rgbBlue = 0;
      pbmi->bmiColors[0].rgbReserved = 0;
      pbmi->bmiColors[1].rgbRed = pCursor->backRed>>8; /* Background */
      pbmi->bmiColors[1].rgbGreen = pCursor->backGreen>>8;
      pbmi->bmiColors[1].rgbBlue = pCursor->backBlue>>8;
      pbmi->bmiColors[1].rgbReserved = 0;
      pbmi->bmiColors[2].rgbRed = pCursor->foreRed>>8; /* Foreground */
      pbmi->bmiColors[2].rgbGreen = pCursor->foreGreen>>8;
      pbmi->bmiColors[2].rgbBlue = pCursor->foreBlue>>8;
      pbmi->bmiColors[2].rgbReserved = 0;
      
      lpBits = (unsigned long *) calloc (pScreenPriv->cursor.sm_cx*pScreenPriv->cursor.sm_cy,
					 sizeof (char));
      
      pCur = (unsigned char *)lpBits;
      if (lpBits)
	{
	  for (y=0; y<pScreenPriv->cursor.sm_cy; y++)
	    {
	      for (x=0; x<pScreenPriv->cursor.sm_cx; x++)
		{
		  if (x>=nCX || y>=nCY) /* Outside of X11 icon bounds */
		    (*pCur++) = 0;
		  else /* Within X11 icon bounds */
		    {
		      int nWinPix = bits_to_bytes(pScreenPriv->cursor.sm_cx) * y + (x/8);

		      bit = pAnd[nWinPix];
		      bit = bit & (1<<(7-(x&7)));
		      if (!bit) /* Within the cursor mask? */
			{
			  int nXPix = BitmapBytePad(pCursor->bits->width) * y + (x/8);
			  bit = ~reverse(~pCursor->bits->source[nXPix] & pCursor->bits->mask[nXPix]);
			  bit = bit & (1<<(7-(x&7)));
			  if (bit) /* Draw foreground */
			    (*pCur++) = 2;
			  else /* Draw background */
			    (*pCur++) = 1;
			}
		      else /* Outside the cursor mask */
			(*pCur++) = 0;
		    }
		} /* end for (x) */
	    } /* end for (y) */
	} /* end if (lpbits) */
    }

  /* If one of the previous two methods gave us the bitmap we need, make a cursor */
  if (lpBits)
    {
      WIN_DEBUG_MSG("winLoadCursor: Creating bitmap cursor: hotspot %d,%d\n",
              pCursor->bits->xhot, pCursor->bits->yhot);

      hAnd = NULL;
      hXor = NULL;

      hAnd = CreateBitmap (pScreenPriv->cursor.sm_cx, pScreenPriv->cursor.sm_cy, 1, 1, pAnd);

      hDC = GetDC (NULL);
      if (hDC)
	{
	  hXor = CreateCompatibleBitmap (hDC, pScreenPriv->cursor.sm_cx, pScreenPriv->cursor.sm_cy);
	  SetDIBits (hDC, hXor, 0, pScreenPriv->cursor.sm_cy, lpBits, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
	  ReleaseDC (NULL, hDC);
	}
      free (lpBits);
      
      
      if (hAnd && hXor)
	{
	  ii.fIcon = FALSE;
	  ii.xHotspot = pCursor->bits->xhot;
	  ii.yHotspot = pCursor->bits->yhot;
	  ii.hbmMask = hAnd;
	  ii.hbmColor = hXor;
	  hCursor = (HCURSOR) CreateIconIndirect( &ii );

	  if (hCursor == NULL)
	    winW32Error(2, "winLoadCursor - CreateIconIndirect failed:");
	  else 
	    {
	      if (GetIconInfo(hCursor, &ii))
		{
		  if (ii.fIcon)
		    {
		      WIN_DEBUG_MSG("winLoadCursor: CreateIconIndirect returned  no cursor. Trying again.\n");
		      
		      DestroyCursor(hCursor);
		      
		      ii.fIcon = FALSE;
		      ii.xHotspot = pCursor->bits->xhot;
		      ii.yHotspot = pCursor->bits->yhot;
		      hCursor = (HCURSOR) CreateIconIndirect( &ii );
		      
		      if (hCursor == NULL)
			winW32Error(2, "winLoadCursor - CreateIconIndirect failed:");
		    }
		  /* GetIconInfo creates new bitmaps. Destroy them again */
		  if (ii.hbmMask)
     	            DeleteObject(ii.hbmMask);
		  if (ii.hbmColor)
		    DeleteObject(ii.hbmColor);
		}
	    }
	}

      if (hAnd)
	DeleteObject (hAnd);
      if (hXor)
	DeleteObject (hXor);
    }

  if (!hCursor)
    {
      /* We couldn't make a color cursor for this screen, use
	 black and white instead */
      hCursor = CreateCursor (g_hInstance,
			      pCursor->bits->xhot, pCursor->bits->yhot,
			      pScreenPriv->cursor.sm_cx, pScreenPriv->cursor.sm_cy,
			      pAnd, pXor);
      if (hCursor == NULL)
	winW32Error(2, "winLoadCursor - CreateCursor failed:");
    }
  free (pAnd);
  free (pXor);

  return hCursor;
}

/*
===========================================================================

 Pointer sprite functions

===========================================================================
*/

/*
 * winRealizeCursor
 *  Convert the X cursor representation to native format if possible.
 */
static Bool
winRealizeCursor (DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor)
{
  if(pCursor == NULL || pCursor->bits == NULL)
    return FALSE;
  
  /* FIXME: cache ARGB8888 representation? */

  return TRUE;
}


/*
 * winUnrealizeCursor
 *  Free the storage space associated with a realized cursor.
 */
static Bool
winUnrealizeCursor(DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor)
{
  return TRUE;
}


/*
 * winSetCursor
 *  Set the cursor sprite and position.
 */
static void
winSetCursor (DeviceIntPtr pDev, ScreenPtr pScreen, CursorPtr pCursor, int x, int y)
{
  POINT ptCurPos, ptTemp;
  HWND  hwnd;
  RECT  rcClient;
  BOOL  bInhibit;
  winScreenPriv(pScreen);
  WIN_DEBUG_MSG("winSetCursor: cursor=%p\n", pCursor); 
  
  /* Inhibit changing the cursor if the mouse is not in a client area */
  bInhibit = FALSE;
  if (GetCursorPos (&ptCurPos))
    {
      hwnd = WindowFromPoint (ptCurPos);
      if (hwnd)
	{
	  if (GetClientRect (hwnd, &rcClient))
	    {
	      ptTemp.x = rcClient.left;
	      ptTemp.y = rcClient.top;
	      if (ClientToScreen (hwnd, &ptTemp))
		{
		  rcClient.left = ptTemp.x;
		  rcClient.top = ptTemp.y;
		  ptTemp.x = rcClient.right;
		  ptTemp.y = rcClient.bottom;
		  if (ClientToScreen (hwnd, &ptTemp))
		    {
		      rcClient.right = ptTemp.x;
		      rcClient.bottom = ptTemp.y;
		      if (!PtInRect (&rcClient, ptCurPos))
			bInhibit = TRUE;
		    }
		}
	    }
	}
    }

  if (pCursor == NULL)
    {
      if (pScreenPriv->cursor.visible)
	{
	  if (!bInhibit && g_fSoftwareCursor)
	    ShowCursor (FALSE);
	  pScreenPriv->cursor.visible = FALSE;
	}
    }
  else
    {
      if (pScreenPriv->cursor.handle)
	{
	  if (!bInhibit)
	    SetCursor (NULL);
	  DestroyCursor (pScreenPriv->cursor.handle);
	  pScreenPriv->cursor.handle = NULL;
	}
      pScreenPriv->cursor.handle =
	winLoadCursor (pScreen, pCursor, pScreen->myNum);
      WIN_DEBUG_MSG("winSetCursor: handle=%p\n", pScreenPriv->cursor.handle); 

      if (!bInhibit)
	SetCursor (pScreenPriv->cursor.handle);

      if (!pScreenPriv->cursor.visible)
	{
	  if (!bInhibit && g_fSoftwareCursor)
	    ShowCursor (TRUE);
	  pScreenPriv->cursor.visible = TRUE;
	}
    }
}


/*
 * winMoveCursor
 *  Move the cursor. This is a noop for us.
 */
static void
winMoveCursor (DeviceIntPtr pDev, ScreenPtr pScreen, int x, int y)
{
}

static Bool
winDeviceCursorInitialize(DeviceIntPtr pDev, ScreenPtr pScr)
{
  winScreenPriv(pScr);
  return pScreenPriv->cursor.spriteFuncs->DeviceCursorInitialize(pDev, pScr);
}

static void
winDeviceCursorCleanup(DeviceIntPtr pDev, ScreenPtr pScr)
{
  winScreenPriv(pScr);
  pScreenPriv->cursor.spriteFuncs->DeviceCursorCleanup(pDev, pScr);
}

static miPointerSpriteFuncRec winSpriteFuncsRec = {
  winRealizeCursor,
  winUnrealizeCursor,
  winSetCursor,
  winMoveCursor,
  winDeviceCursorInitialize,
  winDeviceCursorCleanup
};


/*
===========================================================================

 Other screen functions

===========================================================================
*/

/*
 * winCursorQueryBestSize
 *  Handle queries for best cursor size
 */
static void
winCursorQueryBestSize (int class, unsigned short *width,
				     unsigned short *height, ScreenPtr pScreen)
{
  winScreenPriv(pScreen);
  
  if (class == CursorShape)
    {
      *width = pScreenPriv->cursor.sm_cx;
      *height = pScreenPriv->cursor.sm_cy;
    }
  else
    {
      if (pScreenPriv->cursor.QueryBestSize)
        (*pScreenPriv->cursor.QueryBestSize)(class, width, height, pScreen);
    }
}

/*
 * winInitCursor
 *  Initialize cursor support
 */
Bool
winInitCursor (ScreenPtr pScreen)
{
  winScreenPriv(pScreen);
  miPointerScreenPtr pPointPriv;
  /* override some screen procedures */
  pScreenPriv->cursor.QueryBestSize = pScreen->QueryBestSize;
  pScreen->QueryBestSize = winCursorQueryBestSize;
  
  pPointPriv = (miPointerScreenPtr)
      dixLookupPrivate(&pScreen->devPrivates, miPointerScreenKey);
  
  pScreenPriv->cursor.spriteFuncs = pPointPriv->spriteFuncs;
  pPointPriv->spriteFuncs = &winSpriteFuncsRec;

  pScreenPriv->cursor.handle = NULL;
  pScreenPriv->cursor.visible = FALSE;
  
  pScreenPriv->cursor.sm_cx = GetSystemMetrics (SM_CXCURSOR);
  pScreenPriv->cursor.sm_cy = GetSystemMetrics (SM_CYCURSOR);

  return TRUE;
}
