/*
 *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:	Earle F. Philhower, III
 */

#ifdef HAVE_XWIN_CONFIG_H
#include <xwin-config.h>
#endif
#include "win.h"
#include "dixevents.h"
#include "winmultiwindowclass.h"
#include "winprefs.h"

#include "propertyst.h"

#include "propertyst.h"
#include "windowstr.h"

/*
 * External global variables
 */

extern HICON		g_hIconX;
extern HICON		g_hSmallIconX;


/*
 * Prototypes for local functions
 */

static void
winScaleXBitmapToWindows (int iconSize, int effBPP,
			  PixmapPtr pixmap, unsigned char *image);


/*
 * Scale an X icon bitmap into a Windoze icon bitmap
 */

static void
winScaleXBitmapToWindows (int iconSize,
			  int effBPP,
			  PixmapPtr pixmap,
			  unsigned char *image)
{
  int			row, column, effXBPP, effXDepth;
  unsigned char		*outPtr;
  char		*iconData = 0;
  int			stride, xStride;
  float			factX, factY;
  int			posX, posY;
  unsigned char		*ptr;
  unsigned int		zero;
  unsigned int		color;

  effXBPP = BitsPerPixel(pixmap->drawable.depth);
  effXDepth = pixmap->drawable.depth;

  if (pixmap->drawable.bitsPerPixel == 15)
    effXBPP = 16;
  
  if (pixmap->drawable.depth == 15)
    effXDepth = 16;

  /* Need 16-bit aligned rows for DDBitmaps */
  stride = ((iconSize * effBPP + 15) & (~15)) / 8;
  xStride = PixmapBytePad (pixmap->drawable.width, pixmap->drawable.depth);
  if (stride == 0 || xStride == 0)
    {
      ErrorF ("winScaleXBitmapToWindows - stride or xStride is zero.  "
	      "Bailing.\n");
      return;
    }

  /* Allocate memory for icon data */
  iconData = malloc (xStride * pixmap->drawable.height);
  if (!iconData)
    {
      ErrorF ("winScaleXBitmapToWindows - malloc failed for iconData.  "
	      "Bailing.\n");
      return;
    }

  /* Get icon data */
  miGetImage ((DrawablePtr) &(pixmap->drawable), 0, 0,
	      pixmap->drawable.width, pixmap->drawable.height,
	      ZPixmap, 0xffffffff, iconData);

  /* Keep aspect ratio */
  factX = ((float)pixmap->drawable.width) / ((float)iconSize);
  factY = ((float)pixmap->drawable.height) / ((float)iconSize);
  if (factX > factY)
    factY = factX;
  else
    factX = factY;
  
  /* Out-of-bounds, fill icon with zero */
  zero = 0;
 
  for (row = 0; row < iconSize; row++)
    {
      outPtr = image + stride * row;
      for (column = 0; column < iconSize; column++)
	{
	  posX = factX * column;
	  posY = factY * row;
	  
	  ptr = (unsigned char*) iconData + posY*xStride;
	  if (effXBPP == 1)
	    {
	      ptr += posX / 8;
	      
	      /* Out of X icon bounds, leave space blank */
	      if (posX >= pixmap->drawable.width
		  || posY >= pixmap->drawable.height)
		ptr = (unsigned char *) &zero;
	      
	      if ((*ptr) & (1 << (posX & 7)))
		switch (effBPP)
		  {
		  case 32:
		    *(outPtr++) = 0;
		  case 24:
		    *(outPtr++) = 0;
		  case 16:
		    *(outPtr++) = 0;
		  case 8:
		    *(outPtr++) = 0;
		    break;
		  case 1:
		    outPtr[column / 8] &= ~(1 << (7 - (column & 7)));
		    break;
		  }
	      else
		switch (effBPP)
		  {
		  case 32:
		    *(outPtr++) = 255;
		    *(outPtr++) = 255;
		    *(outPtr++) = 255;
		    *(outPtr++) = 0;
		    break;
		  case 24:
		    *(outPtr++) = 255;
		  case 16:
		    *(outPtr++) = 255;
		  case 8: 
		    *(outPtr++) = 255;
		    break;
		  case 1:
		    outPtr[column / 8] |= (1 << (7 - (column & 7)));
		    break;
		  }
	    }
	  else if (effXDepth == 24 || effXDepth == 32)
	    {
	      ptr += posX * (effXBPP / 8);

	      /* Out of X icon bounds, leave space blank */
	      if (posX >= pixmap->drawable.width
		  || posY >= pixmap->drawable.height)
		ptr = (unsigned char *) &zero;
	      color = (((*ptr) << 16)
		       + ((*(ptr + 1)) << 8)
		       + ((*(ptr + 2)) << 0));
	      switch (effBPP)
		{
		case 32:
		  *(outPtr++) = *(ptr++); /* b */
		  *(outPtr++) = *(ptr++); /* g */
		  *(outPtr++) = *(ptr++); /* r */
		  *(outPtr++) = (effXDepth == 32) ? *(ptr++) : 0x0; /* alpha */
		  break;
		case 24:
		  *(outPtr++) = *(ptr++);
		  *(outPtr++) = *(ptr++);
		  *(outPtr++) = *(ptr++);
		  break;
		case 16:
		  color = ((((*ptr) >> 2) << 10)
			   + (((*(ptr + 1)) >> 2) << 5)
			   + (((*(ptr + 2)) >> 2)));
		  *(outPtr++) = (color >> 8);
		  *(outPtr++) = (color & 255);
		  break;
		case 8:
		  color = (((*ptr))) + (((*(ptr + 1)))) + (((*(ptr + 2))));
		  color /= 3;
		  *(outPtr++) = color;
		  break;
		case 1:
		  if (color)
		    outPtr[column / 8] |= (1 << (7 - (column & 7)));
		  else
		    outPtr[column / 8] &= ~(1 << (7 - (column & 7)));
		}
	    }
	  else if (effXDepth == 16)
	    {
	      ptr += posX * (effXBPP / 8);
	
	      /* Out of X icon bounds, leave space blank */
	      if (posX >= pixmap->drawable.width
		  || posY >= pixmap->drawable.height)
		ptr = (unsigned char *) &zero;
	      color = ((*ptr) << 8) + (*(ptr + 1));
	      switch (effBPP)
		{
		case 32:
		  *(outPtr++) = (color & 31) << 2;
		  *(outPtr++) = ((color >> 5) & 31) << 2;
		  *(outPtr++) = ((color >> 10) & 31) << 2;
		  *(outPtr++) = 0; /* resvd */
		  break;
		case 24:
		  *(outPtr++) = (color & 31) << 2;
		  *(outPtr++) = ((color >> 5) & 31) << 2;
		  *(outPtr++) = ((color >> 10) & 31) << 2;
		  break;
		case 16:
		  *(outPtr++) = *(ptr++);
		  *(outPtr++) = *(ptr++);
		  break;
		case 8:
		  *(outPtr++) = (((color & 31)
				  + ((color >> 5) & 31)
				  + ((color >> 10) & 31)) / 3) << 2;
		  break;
		case 1:
		  if (color)
		    outPtr[column / 8] |= (1 << (7 - (column & 7)));
		  else
		    outPtr[column / 8] &= ~(1 << (7 - (column & 7)));
		  break;
		} /* end switch(effbpp) */
	    } /* end if effxbpp==16) */
	} /* end for column */
    } /* end for row */
  free (iconData);
}

static HICON
NetWMToWinIconAlpha(uint32_t *icon)
{
  int width = icon[0];
  int height = icon[1];
  uint32_t *pixels = &icon[2];
  HICON result;
  HDC hdc = GetDC(NULL);
  uint32_t *DIB_pixels;
  ICONINFO ii = {TRUE};
  BITMAPV4HEADER bmh = {sizeof(bmh)};

  /* Define an ARGB pixel format used for Color+Alpha icons */
  bmh.bV4Width = width;
  bmh.bV4Height = -height; /* Invert the image */
  bmh.bV4Planes = 1;
  bmh.bV4BitCount = 32;
  bmh.bV4V4Compression = BI_BITFIELDS;
  bmh.bV4AlphaMask = 0xFF000000;
  bmh.bV4RedMask =   0x00FF0000;
  bmh.bV4GreenMask = 0x0000FF00;
  bmh.bV4BlueMask =  0x000000FF;

  ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO*)&bmh,
                DIB_RGB_COLORS, (void**)&DIB_pixels, NULL, 0);
  ReleaseDC(NULL, hdc);
  ii.hbmMask = CreateBitmap(width, height, 1, 1, NULL);
  memcpy(DIB_pixels, pixels, height*width*4);

  /* CreateIconIndirect() traditionally required DDBitmaps */
  /* Systems from WinXP accept 32-bit ARGB DIBitmaps with full 8-bit alpha support */
  /* The icon is created with a DIB + empty DDB mask (an MS example does the same) */
  result = CreateIconIndirect(&ii);

  DeleteObject(ii.hbmColor);
  DeleteObject(ii.hbmMask);

  winDebug("NetWMToWinIconAlpha - %d x %d = %p\n", icon[0], icon[1], result);
  return result;
}

static HICON
NetWMToWinIconThreshold(uint32_t *icon)
{
  int width = icon[0];
  int height = icon[1];
  uint32_t *pixels = &icon[2];
  int row, col;
  HICON result;
  ICONINFO ii = {TRUE};

  HDC hdc = GetDC(NULL);
  HDC xorDC = CreateCompatibleDC(hdc);
  HDC andDC = CreateCompatibleDC(hdc);
  ii.hbmColor = CreateCompatibleBitmap(hdc, width, height);
  ii.hbmMask = CreateCompatibleBitmap(hdc, width, height);
  ReleaseDC(NULL, hdc);
  SelectObject(xorDC, ii.hbmColor);
  SelectObject(andDC, ii.hbmMask);

  for (row = 0; row < height; row++) {
    for (col = 0; col < width; col++) {
      if ((*pixels & 0xFF000000) > 31<<24) { /* 31 alpha threshold, i.e. opaque above, transparent below */
	SetPixelV(xorDC, col, row, RGB(((char*)pixels)[2], ((char*)pixels)[1],
		((char*)pixels)[0]));
	SetPixelV(andDC, col, row, RGB(0, 0, 0)); /* black mask */
      }
      else {
	SetPixelV(xorDC, col, row, RGB(0, 0, 0));
	SetPixelV(andDC, col, row, RGB(255, 255, 255)); /* white mask */
      }
      pixels++;
    }
  }
  DeleteDC(xorDC);
  DeleteDC(andDC);

  result = CreateIconIndirect(&ii);

  DeleteObject(ii.hbmColor);
  DeleteObject(ii.hbmMask );

  winDebug("NetWMToWinIconThreshold - %d x %d = %p\n", icon[0], icon[1], result);
  return result;
}

static HICON
NetWMToWinIcon(int bpp, uint32_t *icon)
{
  static Bool hasIconAlphaChannel = FALSE;
  static BOOL versionChecked = FALSE;

  if (!versionChecked)
    {
      OSVERSIONINFOEX osvi = {0};
      ULONGLONG dwlConditionMask = 0;

      osvi.dwOSVersionInfoSize = sizeof (osvi);
      osvi.dwMajorVersion = 5;
      osvi.dwMinorVersion = 1;

      /* Windows versions later than XP have icon alpha channel suport, 2000 does not */
      VER_SET_CONDITION(dwlConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
      VER_SET_CONDITION(dwlConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
      hasIconAlphaChannel = VerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION, dwlConditionMask);
      versionChecked = TRUE;

      ErrorF("OS has icon alpha channel support: %s\n", hasIconAlphaChannel ? "yes" : "no");
    }

  if (hasIconAlphaChannel && (bpp==32))
    return NetWMToWinIconAlpha(icon);
  else
    return NetWMToWinIconThreshold(icon);
}

static pointer
GetWindowProp(WindowPtr pWin, Atom name, long int *size_return)
{
  struct _Window	*pwin;
  struct _Property	*prop;

  if (!pWin || !name) {
    ErrorF ("GetWindowProp - pWin or name was NULL\n");
    return 0;
  }
  pwin = (struct _Window*) pWin;
  if (!pwin->optional) return NULL;
  for (prop = (struct _Property *) pwin->optional->userProps;
       prop;
       prop=prop->next){
    if (prop->propertyName == name) {
      *size_return=prop->size;
      return prop->data;
    }
  }
  return NULL;
}

/*
 * Attempt to create a custom icon from the WM_HINTS bitmaps
 */

HICON
winXIconToHICON (WindowPtr pWin, int iconSize)
{
  unsigned char		*mask, *image, *imageMask;
  unsigned char		*dst, *src;
  PixmapPtr		iconPtr;
  PixmapPtr		maskPtr;
  int			planes, bpp, effBPP, stride, maskStride, i;
  int			biggest_size = 0;
  HDC			hDC;
  ICONINFO		ii;
  WinXWMHints		hints;
  HICON			hIcon = NULL;
  uint32_t		*biggest_icon = NULL;

  /* Try to get _NET_WM_ICON icons first */
  static Atom _XA_NET_WM_ICON;
  static int generation;
  uint32_t *icon, *icon_data = NULL;
  long int size=0;

  hDC = GetDC (GetDesktopWindow ());
  planes = GetDeviceCaps (hDC, PLANES);
  bpp = GetDeviceCaps (hDC, BITSPIXEL);
  ReleaseDC (GetDesktopWindow (), hDC);

  if (generation != serverGeneration) {
     generation = serverGeneration;
     _XA_NET_WM_ICON = MakeAtom("_NET_WM_ICON", 12, TRUE);
  }

  if (_XA_NET_WM_ICON) icon_data = GetWindowProp(pWin, _XA_NET_WM_ICON, &size);
  if (icon_data)
    {
      for(icon = icon_data;
	  icon < &icon_data[size] && *icon;
	  icon = &icon[icon[0]*icon[1]+2])
	{
	  if (icon[0]==iconSize && icon[1]==iconSize)
            return NetWMToWinIcon(bpp, icon);
	  /* Find the biggest icon and let Windows scale the size */
	  else if (biggest_size < icon[0])
	    {
	      biggest_icon = icon;
	      biggest_size = icon[0];
	    }
	}
      if (biggest_icon)
	return NetWMToWinIcon(bpp, biggest_icon);
    }
  winDebug("winXIconToHICON - pWin %x: no suitable NetIcon\n",(int)pWin, iconSize);

  winMultiWindowGetWMHints (pWin, &hints);
  if (!hints.icon_pixmap) return NULL;

  dixLookupResourceByType((pointer) &iconPtr, hints.icon_pixmap, RT_PIXMAP,
				NullClient, DixUnknownAccess);
  
  if (!iconPtr) return NULL;

  /* 15 BPP is really 16BPP as far as we care */
  if (bpp == 15)
    effBPP = 16;
  else
    effBPP = bpp;
  
  /* Need 16-bit aligned rows for DDBitmaps */
  stride = ((iconSize * effBPP + 15) & (~15)) / 8;

  /* Mask is 1-bit deep */
  maskStride = ((iconSize * 1 + 15) & (~15)) / 8;

  image = malloc (stride * iconSize);
  imageMask = malloc (stride * iconSize);
  /* Default to a completely black mask */
  mask = calloc (maskStride, iconSize);
  
  winScaleXBitmapToWindows (iconSize, effBPP, iconPtr, image);
  dixLookupResourceByType((pointer) &maskPtr, hints.icon_mask, RT_PIXMAP,
				NullClient, DixUnknownAccess);

  if (maskPtr) 
    {
      winScaleXBitmapToWindows (iconSize, 1, maskPtr, mask);
      
      winScaleXBitmapToWindows (iconSize, effBPP, maskPtr, imageMask);
      
      /* Now we need to set all bits of the icon which are not masked */
      /* on to 0 because Color is really an XOR, not an OR function */
      dst = image;
      src = imageMask;

      for (i = 0; i < (stride * iconSize); i++)
	if ((*(src++)))
	  *(dst++) = 0;
	else
	  dst++;
    }
  
  ii.fIcon = TRUE;
  ii.xHotspot = 0; /* ignored */
  ii.yHotspot = 0; /* ignored */
  
  /* Create Win32 mask from pixmap shape */
  ii.hbmMask = CreateBitmap (iconSize, iconSize, planes, 1, mask);

  /* Create Win32 bitmap from pixmap */
  ii.hbmColor = CreateBitmap (iconSize, iconSize, planes, bpp, image);

  /* Merge Win32 mask and bitmap into icon */
  hIcon = CreateIconIndirect (&ii);

  /* Release Win32 mask and bitmap */
  DeleteObject (ii.hbmMask);
  DeleteObject (ii.hbmColor);

  /* Free X mask and bitmap */
  free (mask);
  free (image);
  free (imageMask);

  return hIcon;
}



/*
 * Change the Windows window icon 
 */

#ifdef XWIN_MULTIWINDOW
void
winUpdateIcon (Window id)
{
  WindowPtr		pWin;
  HICON			hIcon, hIconSmall=NULL, hIconOld;

  dixLookupResourceByType((pointer) &pWin, id, RT_WINDOW, NullClient, DixUnknownAccess);
  if (pWin)
    {
      winWindowPriv(pWin);
      if (pWinPriv->hWnd) {
        hIcon = winOverrideIcon ((unsigned long)pWin);
        if (!hIcon) {
          hIcon = winXIconToHICON (pWin, GetSystemMetrics(SM_CXICON));
          if (!hIcon) {
            hIcon = g_hIconX;
            hIconSmall = g_hSmallIconX;
          } else {
            /* Leave undefined if not found */
            hIconSmall = winXIconToHICON (pWin, GetSystemMetrics(SM_CXSMICON));
          }
        }

        /* Set the large icon */
        hIconOld = (HICON) SendMessage (pWinPriv->hWnd,
                                        WM_SETICON, ICON_BIG, (LPARAM) hIcon);

        /* Delete the icon if its not the default */
        winDestroyIcon(hIconOld);

        /* Same for the small icon */
        hIconOld = (HICON) SendMessage (pWinPriv->hWnd,
                                        WM_SETICON, ICON_SMALL, (LPARAM) hIconSmall);
        winDestroyIcon(hIconOld);
      }
  }
}

void winInitGlobalIcons (void)
{
  int sm_cx = GetSystemMetrics(SM_CXICON);
  int sm_cxsm = GetSystemMetrics(SM_CXSMICON);
  /* Load default X icon in case it's not ready yet */
  if (!g_hIconX) 
    {  
      g_hIconX = winOverrideDefaultIcon(sm_cx);
      g_hSmallIconX = winOverrideDefaultIcon(sm_cxsm);
    }
  
  if (!g_hIconX)
    {   
      g_hIconX = (HICON)LoadImage (g_hInstance,
	      MAKEINTRESOURCE(IDI_XWIN),
	      IMAGE_ICON,
	      GetSystemMetrics(SM_CXICON),
	      GetSystemMetrics(SM_CYICON),
	      0);
      g_hSmallIconX = (HICON)LoadImage (g_hInstance,
	      MAKEINTRESOURCE(IDI_XWIN),
	      IMAGE_ICON,
	      GetSystemMetrics(SM_CXSMICON),
	      GetSystemMetrics(SM_CYSMICON),
	      LR_DEFAULTSIZE);
    }
}

void winSelectIcons(WindowPtr pWin, HICON *pIcon, HICON *pSmallIcon)
{
  HICON hIcon, hSmallIcon;
  
  winInitGlobalIcons();  
  
  /* Try and get the icon from WM_HINTS */
  hIcon = winXIconToHICON (pWin, GetSystemMetrics(SM_CXICON));
  hSmallIcon = winXIconToHICON (pWin, GetSystemMetrics(SM_CXSMICON));

  /* If we got the small, but not the large one swap them */
  if (!hIcon && hSmallIcon) 
  {
      hIcon = hSmallIcon;
      hSmallIcon = NULL;
  }
  
  /* Use default X icon if no icon loaded from WM_HINTS */
  if (!hIcon) {
    hIcon = g_hIconX;
    hSmallIcon = g_hSmallIconX;
  }

  if (pIcon)
    *pIcon = hIcon;
  else
    winDestroyIcon(hIcon);
  if (pSmallIcon)
    *pSmallIcon = hSmallIcon;
  else
    winDestroyIcon(hSmallIcon);
}

void winDestroyIcon(HICON hIcon)
{
  /* Delete the icon if its not the default */
  if (hIcon &&
      hIcon != g_hIconX &&
      hIcon != g_hSmallIconX &&
      !winIconIsOverride((unsigned long)hIcon))
    DestroyIcon (hIcon);
}
#endif
