/*
 *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

#ifndef WINVER
#define WINVER 0x0500
#endif

#include <X11/Xwindows.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#include "winresource.h"
#include "winprefs.h"
#include "winmsg.h"
#include "winmultiwindowicons.h"
#include "winglobals.h"
/*
 * global variables
 */
extern HINSTANCE g_hInstance;

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

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

    effXBPP = pixmap->bits_per_pixel;
    if (pixmap->bits_per_pixel == 15)
        effXBPP = 16;

    effXDepth = pixmap->depth;
    if (pixmap->depth == 15)
        effXDepth = 16;

    xStride = pixmap->bytes_per_line;
    if (stride == 0 || xStride == 0) {
        ErrorF("winScaleXBitmapToWindows - stride or xStride is zero.  "
               "Bailing.\n");
        return;
    }

    /* Get icon data */
    iconData = (unsigned char *) pixmap->data;

    /* Keep aspect ratio */
    factX = ((float) pixmap->width) / ((float) iconSize);
    factY = ((float) pixmap->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->width || posY >= pixmap->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->width || posY >= pixmap->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->width || posY >= pixmap->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 */
}

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;
    BITMAPV5HEADER bmh;

    /* Define an ARGB pixel format used for Color+Alpha icons */
    ZeroMemory(&bmh,sizeof(bmh));
    bmh.bV5Size = sizeof(bmh);
    bmh.bV5Width = width;
    bmh.bV5Height = -height; /* Invert the image */
    bmh.bV5Planes = 1;
    bmh.bV5BitCount = 32;
    bmh.bV5Compression = BI_BITFIELDS;
    bmh.bV5AlphaMask = 0xFF000000;
    bmh.bV5RedMask =   0x00FF0000;
    bmh.bV5GreenMask = 0x0000FF00;
    bmh.bV5BlueMask =  0x000000FF;

    ii.fIcon = TRUE;
    ii.xHotspot = 0;            /* ignored */
    ii.yHotspot = 0;            /* ignored */
    ii.hbmColor = CreateDIBSection(hdc, (BITMAPINFO *) &bmh,
                                   DIB_RGB_COLORS, (void **) &DIB_pixels, NULL,
                                   0);
    ReleaseDC(NULL, hdc);

    if (!ii.hbmColor)
      return NULL;

    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;

    HDC hdc = GetDC(NULL);
    HDC xorDC = CreateCompatibleDC(hdc);
    HDC andDC = CreateCompatibleDC(hdc);

    ii.fIcon = TRUE;
    ii.xHotspot = 0;            /* ignored */
    ii.yHotspot = 0;            /* ignored */
    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;

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

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

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

static
 HICON
winXIconToHICON(Display * pDisplay, Window id, int iconSize)
{
    unsigned char *mask, *image = NULL, *imageMask;
    unsigned char *dst, *src;
    int planes, bpp, i;
    int biggest_size = 0;
    HDC hDC;
    ICONINFO ii;
    XWMHints *hints;
    HICON hIcon = NULL;
    uint32_t *biggest_icon = NULL;

    static Atom _XA_NET_WM_ICON;
    static int generation;
    uint32_t *icon, *icon_data = NULL;
    unsigned long int size;
    unsigned long int type;
    int format;
    unsigned long int left;

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

    /* Always prefer _NET_WM_ICON icons */
    if (generation != serverGeneration) {
        generation = serverGeneration;
        _XA_NET_WM_ICON = XInternAtom(pDisplay, "_NET_WM_ICON", FALSE);
    }

    if ((XGetWindowProperty(pDisplay, id, _XA_NET_WM_ICON,
                            0, MAXINT, FALSE,
                            AnyPropertyType, &type, &format, &size, &left,
                            (unsigned char **) &icon_data) == Success) &&
        (icon_data != NULL)) {
        for (icon = icon_data; icon < &icon_data[size] && *icon;
             icon = &icon[icon[0] * icon[1] + 2]) {
            /* Find an exact match to the size we require...  */
            if (icon[0] == iconSize && icon[1] == iconSize) {
                winDebug("winXIconToHICON: found %lu x %lu NetIcon\n", icon[0],
                         icon[1]);
                hIcon = NetWMToWinIcon(bpp, icon);
                break;
            }
            /* Otherwise, find the biggest icon and let Windows scale the size */
            else if (biggest_size < icon[0]) {
                biggest_icon = icon;
                biggest_size = icon[0];
            }
        }

        if (!hIcon && biggest_icon) {
            winDebug
                ("winXIconToHICON: selected %lu x %lu NetIcon for scaling to %u x %u\n",
                 biggest_icon[0], biggest_icon[1], iconSize, iconSize);

            hIcon = NetWMToWinIcon(bpp, biggest_icon);
        }

        XFree(icon_data);
    }

    if (!hIcon) {
        winDebug("winXIconToHICON: no suitable NetIcon\n");

        hints = XGetWMHints(pDisplay, id);
        if (hints) {
            winDebug("winXIconToHICON: id 0x%x icon_pixmap hint %x\n", id,
                     hints->icon_pixmap);

            if (hints->icon_pixmap) {
                Window root;
                int x, y;
                unsigned int width, height, border_width, depth;
                XImage *xImageIcon;
                XImage *xImageMask = NULL;

                XGetGeometry(pDisplay, hints->icon_pixmap, &root, &x, &y,
                             &width, &height, &border_width, &depth);

                xImageIcon =
                    XGetImage(pDisplay, hints->icon_pixmap, 0, 0, width, height,
                              0xFFFFFFFF, ZPixmap);
                winDebug("winXIconToHICON: id 0x%x icon Ximage 0x%x\n", id,
                         xImageIcon);

                if (hints->icon_mask)
                    xImageMask =
                        XGetImage(pDisplay, hints->icon_mask, 0, 0, width,
                                  height, 0xFFFFFFFF, ZPixmap);

                if (xImageIcon) {
                    int effBPP, stride, maskStride;

                    /* 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);
                    mask = malloc(maskStride * iconSize);

                    /* Default to a completely black mask */
                    memset(imageMask, 0, stride * iconSize);
                    memset(mask, 0, maskStride * iconSize);

                    winScaleXImageToWindowsIcon(iconSize, effBPP, stride,
                                                xImageIcon, image);

                    if (xImageMask) {
                        winScaleXImageToWindowsIcon(iconSize, 1, maskStride,
                                                    xImageMask, mask);
                        winScaleXImageToWindowsIcon(iconSize, effBPP, stride,
                                                    xImageMask, 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);

                    if (xImageMask)
                        XDestroyImage(xImageMask);

                    XDestroyImage(xImageIcon);
                }
            }
            XFree(hints);
        }
    }
    return hIcon;
}

/*
 * Change the Windows window icon
 */

#ifdef XWIN_MULTIWINDOW
void
winUpdateIcon(HWND hWnd, Display * pDisplay, Window id, HICON hIconNew)
{
    HICON hIcon, hIconSmall = NULL, hIconOld;

    /* Start with the icon from preferences, if any */
    hIcon = hIconNew;
    hIconSmall = hIconNew;

    /* If we still need an icon, try and get the icon from WM_HINTS */
    if (!hIcon)
        hIcon = winXIconToHICON(pDisplay, id, GetSystemMetrics(SM_CXICON));
    if (!hIconSmall)
        hIconSmall =
            winXIconToHICON(pDisplay, id, GetSystemMetrics(SM_CXSMICON));

    /* If we got the small, but not the large one swap them */
    if (!hIcon && hIconSmall) {
        hIcon = hIconSmall;
        hIconSmall = NULL;
    }

    /* Set the large icon */
    hIconOld = (HICON) SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM) hIcon);
    /* Delete the old icon if its not the default */
    winDestroyIcon(hIconOld);

    /* Same for the small icon */
    hIconOld =
        (HICON) SendMessage(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(HICON * pIcon, HICON * pSmallIcon)
{
    HICON hIcon, hSmallIcon;

    winInitGlobalIcons();

    /* Use default X icon */
    hIcon = g_hIconX;
    hSmallIcon = g_hSmallIconX;

    if (pIcon)
        *pIcon = hIcon;

    if (pSmallIcon)
        *pSmallIcon = hSmallIcon;
}

void
winDestroyIcon(HICON hIcon)
{
    /* Delete the icon if its not one of the application defaults or an override */
    if (hIcon &&
        hIcon != g_hIconX &&
        hIcon != g_hSmallIconX && !winIconIsOverride(hIcon))
        DestroyIcon(hIcon);
}
#endif