/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtGui module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "qbitmap.h"
#include "qpixmap.h"
#include <qpa/qplatformpixmap.h>
#include "qpixmap_raster_p.h"

#include <qdebug.h>
#include <QScopedArrayPointer>
#include <qt_windows.h>

#include <algorithm>
#include <iterator>

QT_BEGIN_NAMESPACE

template <typename Int>
static inline Int pad4(Int v)
{
    return (v + Int(3)) & ~Int(3);
}

#ifndef QT_NO_DEBUG_STREAM
QDebug operator<<(QDebug d, const BITMAPINFOHEADER &bih)
{
    QDebugStateSaver saver(d);
    d.nospace();
    d << "BITMAPINFOHEADER(" << bih.biWidth << 'x' << qAbs(bih.biHeight)
      << (bih.biHeight < 0 ? ", top-down" : ", bottom-up")
      << ", planes=" << bih.biPlanes << ", bitCount=" << bih.biBitCount
      << ", compression="  << bih.biCompression << ", size="
      << bih.biSizeImage << ')';
    return d;
}
#endif // !QT_NO_DEBUG_STREAM

static inline void initBitMapInfoHeader(int width, int height, bool topToBottom,
                                        DWORD compression, DWORD bitCount,
                                        BITMAPINFOHEADER *bih)
{
    memset(bih, 0, sizeof(BITMAPINFOHEADER));
    bih->biSize        = sizeof(BITMAPINFOHEADER);
    bih->biWidth       = width;
    bih->biHeight      = topToBottom ? -height : height;
    bih->biPlanes      = 1;
    bih->biBitCount    = WORD(bitCount);
    bih->biCompression = compression;
     // scan lines are word-aligned (unless RLE)
    const DWORD bytesPerLine = pad4(DWORD(width) * bitCount / 8);
    bih->biSizeImage   = bytesPerLine * DWORD(height);
}

enum { Indexed8ColorTableSize = 256 };

struct BITMAPINFO_COLORTABLE256 { // BITMAPINFO with 256 entry color table for Indexed 8 format
    BITMAPINFOHEADER    bmiHeader;
    RGBQUAD             bmiColors[Indexed8ColorTableSize];
};

template <class BITMAPINFO_T> // BITMAPINFO, BITMAPINFO_COLORTABLE256
static inline void initBitMapInfo(int width, int height, bool topToBottom,
                                  DWORD compression, DWORD bitCount,
                                  BITMAPINFO_T *bmi)
{
    initBitMapInfoHeader(width, height, topToBottom, compression, bitCount, &bmi->bmiHeader);
    memset(bmi->bmiColors, 0, sizeof(bmi->bmiColors));
}

static inline uchar *getDiBits(HDC hdc, HBITMAP bitmap, int width, int height, bool topToBottom = true)
{
    BITMAPINFO bmi;
    initBitMapInfo(width, height, topToBottom, BI_RGB, 32u, &bmi);
    uchar *result = new uchar[bmi.bmiHeader.biSizeImage];
    if (!GetDIBits(hdc, bitmap, 0, UINT(height), result, &bmi, DIB_RGB_COLORS)) {
        delete [] result;
        qErrnoWarning("%s: GetDIBits() failed to get bitmap bits.", __FUNCTION__);
        return nullptr;
    }
    return result;
}

static inline void copyImageDataCreateAlpha(const uchar *data, QImage *target)
{
    const uint mask = target->format() == QImage::Format_RGB32 ? 0xff000000 : 0;
    const int height = target->height();
    const int width = target->width();
    const int bytesPerLine = width * int(sizeof(QRgb));
    for (int y = 0; y < height; ++y) {
        QRgb *dest = reinterpret_cast<QRgb *>(target->scanLine(y));
        const QRgb *src = reinterpret_cast<const QRgb *>(data + y * bytesPerLine);
        for (int x = 0; x < width; ++x) {
            const uint pixel = src[x];
            if ((pixel & 0xff000000) == 0 && (pixel & 0x00ffffff) != 0)
                dest[x] = pixel | 0xff000000;
            else
                dest[x] = pixel | mask;
        }
    }
}

// Flip RGB triplets from DIB to QImage formats. Scan lines are padded to 32bit
// both in QImage and DIB.
static inline void flipRgb3(uchar *p, int width, int height)
{
    const int lineSize = 3 * width;
    const int linePad = pad4(lineSize) - lineSize;
    for (int y = 0; y < height; ++y) {
        uchar *end = p + lineSize;
        for ( ; p < end; p += 3)
            std::swap(*p, *(p + 2));
        p += linePad;
    }
}

static inline RGBQUAD qRgbToRgbQuad(QRgb qrgb)
{
    RGBQUAD result = {BYTE(qBlue(qrgb)), BYTE(qGreen(qrgb)), BYTE(qRed(qrgb)), 0};
    return result;
}

static inline QRgb rgbQuadToQRgb(RGBQUAD quad)
{
    return QRgb(quad.rgbBlue) + (QRgb(quad.rgbGreen) << 8) + (QRgb(quad.rgbRed) << 16)
        + 0xff000000;
}

// Helper for imageFromWinHBITMAP_*(), create image in desired format
static QImage copyImageData(const BITMAPINFOHEADER &header, const RGBQUAD *colorTableIn,
                            const void *data, QImage::Format format)
{
    const QSize size = QSize(header.biWidth, qAbs(header.biHeight));
    QImage image(size, format);

    int colorTableSize = 0;
    switch (format) {
    case QImage::Format_Mono:
        colorTableSize = 2;
        break;
    case QImage::Format_Indexed8:
        colorTableSize = Indexed8ColorTableSize;
        break;
    default:
        break;
    }
    if (colorTableSize) {
        Q_ASSERT(colorTableIn);
        QVector<QRgb> colorTable;
        colorTable.reserve(colorTableSize);
        std::transform(colorTableIn, colorTableIn + colorTableSize,
                       std::back_inserter(colorTable), rgbQuadToQRgb);
        image.setColorTable(colorTable);
    }

    switch (header.biBitCount) {
    case 32:
        copyImageDataCreateAlpha(static_cast<const uchar *>(data), &image);
        break;
    case 1:
    case 8:
    case 16:
    case 24:
        Q_ASSERT(DWORD(image.sizeInBytes()) == header.biSizeImage);
        memcpy(image.bits(), data, header.biSizeImage);
        if (format == QImage::Format_RGB888)
            image = image.rgbSwapped();
        break;
    default:
        Q_UNREACHABLE();
        break;
    }
    return image;
}

class DisplayHdc
{
    Q_DISABLE_COPY(DisplayHdc)
public:
    DisplayHdc() : m_displayDc(GetDC(nullptr)) {}
    ~DisplayHdc() { ReleaseDC(nullptr, m_displayDc); }

    operator HDC() const { return m_displayDc; }

private:
     const HDC m_displayDc;
};

enum HBitmapFormat
{
    HBitmapNoAlpha,
    HBitmapPremultipliedAlpha,
    HBitmapAlpha
};

Q_GUI_EXPORT HBITMAP qt_createIconMask(const QBitmap &bitmap)
{
    QImage bm = bitmap.toImage().convertToFormat(QImage::Format_Mono);
    const int w = bm.width();
    const int h = bm.height();
    const int bpl = ((w+15)/16)*2; // bpl, 16 bit alignment
    QScopedArrayPointer<uchar> bits(new uchar[size_t(bpl * h)]);
    bm.invertPixels();
    for (int y = 0; y < h; ++y)
        memcpy(bits.data() + y * bpl, bm.constScanLine(y), size_t(bpl));
    HBITMAP hbm = CreateBitmap(w, h, 1, 1, bits.data());
    return hbm;
}

static inline QImage::Format format32(int hbitmapFormat)
{
    switch (hbitmapFormat) {
    case HBitmapNoAlpha:
        return QImage::Format_RGB32;
    case HBitmapAlpha:
        return QImage::Format_ARGB32;
    default:
        break;
    }
    return QImage::Format_ARGB32_Premultiplied;
}

Q_GUI_EXPORT HBITMAP qt_imageToWinHBITMAP(const QImage &imageIn, int hbitmapFormat = 0)
{
    if (imageIn.isNull())
        return nullptr;

    // Define the header
    DWORD compression = 0;
    DWORD bitCount = 0;

    // Copy over the data
    QImage image = imageIn;
    switch (image.format()) {
    case QImage::Format_Mono:
        bitCount = 1u;
        break;
    case QImage::Format_RGB32:
    case QImage::Format_ARGB32:
    case QImage::Format_ARGB32_Premultiplied: {
        compression = BI_RGB;
        bitCount = 32u;
        const QImage::Format targetFormat = format32(hbitmapFormat);
        if (targetFormat != image.format())
            image = image.convertToFormat(targetFormat);
    }
        break;
    case QImage::Format_RGB888:
        compression = BI_RGB;
        bitCount = 24u;
        break;
    case QImage::Format_Indexed8:
        bitCount = 8u;
        break;
    case QImage::Format_RGB555:
        bitCount = 16u;
        break;
    default: {
        QImage::Format fallbackFormat = QImage::Format_ARGB32_Premultiplied;
        switch (image.format()) { // Convert to a suitable format.
        case QImage::Format_MonoLSB:
            fallbackFormat = QImage::Format_Mono;
            break;
        case QImage::Format_RGB16:
            fallbackFormat = QImage::Format_RGB555;
            break;
        case QImage::Format_Grayscale8:
            fallbackFormat = QImage::Format_Indexed8;
            break;
        default:
            break;
        } // switch conversion format
        return qt_imageToWinHBITMAP(imageIn.convertToFormat(fallbackFormat), hbitmapFormat);
    }
    }

    const int w = image.width();
    const int h = image.height();

    BITMAPINFO_COLORTABLE256 bmiColorTable256;
    initBitMapInfo(w, h, true, compression, bitCount, &bmiColorTable256);
    BITMAPINFO &bmi = reinterpret_cast<BITMAPINFO &>(bmiColorTable256);
    switch (image.format()) {
    case QImage::Format_Mono: // Color table with 2 entries
    case QImage::Format_Indexed8:
        std::transform(image.colorTable().constBegin(), image.colorTable().constEnd(),
                       bmiColorTable256.bmiColors, qRgbToRgbQuad);
        break;
    default:
        break;
    }

    // Create the pixmap
    uchar *pixels = nullptr;
    const HBITMAP bitmap = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS,
                                            reinterpret_cast<void **>(&pixels), nullptr, 0);
    if (!bitmap) {
        qErrnoWarning("%s, failed to create dibsection", __FUNCTION__);
        return nullptr;
    }
    if (!pixels) {
        qErrnoWarning("%s, did not allocate pixel data", __FUNCTION__);
        return nullptr;
    }
    memcpy(pixels, image.constBits(), bmi.bmiHeader.biSizeImage);
    if (image.format() == QImage::Format_RGB888)
        flipRgb3(pixels, w, h);
    return bitmap;
}

Q_GUI_EXPORT HBITMAP qt_pixmapToWinHBITMAP(const QPixmap &p, int hbitmapFormat = 0)
{
    if (p.isNull())
        return nullptr;

    QPlatformPixmap *platformPixmap = p.handle();
    if (platformPixmap->classId() != QPlatformPixmap::RasterClass) {
        QRasterPlatformPixmap *data = new QRasterPlatformPixmap(p.depth() == 1 ?
            QRasterPlatformPixmap::BitmapType : QRasterPlatformPixmap::PixmapType);
        data->fromImage(p.toImage(), Qt::AutoColor);
        return qt_pixmapToWinHBITMAP(QPixmap(data), hbitmapFormat);
    }

    return qt_imageToWinHBITMAP(*static_cast<QRasterPlatformPixmap*>(platformPixmap)->buffer(), hbitmapFormat);
}

static QImage::Format imageFromWinHBITMAP_Format(const BITMAPINFOHEADER &header, int hbitmapFormat)
{
    QImage::Format result = QImage::Format_Invalid;
    switch (header.biBitCount) {
    case 32:
        result = hbitmapFormat == HBitmapNoAlpha
            ? QImage::Format_RGB32 : QImage::Format_ARGB32_Premultiplied;
        break;
    case 24:
        result = QImage::Format_RGB888;
        break;
    case 16:
        result = QImage::Format_RGB555;
        break;
    case 8:
        result = QImage::Format_Indexed8;
        break;
    case 1:
        result = QImage::Format_Mono;
        break;
    }
    return result;
}

// Fast path for creating a QImage directly from a HBITMAP created by CreateDIBSection(),
// not requiring memory allocation.
static QImage imageFromWinHBITMAP_DibSection(HBITMAP bitmap, int hbitmapFormat)
{
    DIBSECTION dibSection;
    memset(&dibSection, 0, sizeof(dibSection));
    dibSection.dsBmih.biSize = sizeof(dibSection.dsBmih);

    if (!GetObject(bitmap, sizeof(dibSection), &dibSection)
        || !dibSection.dsBm.bmBits
        || dibSection.dsBmih.biBitCount <= 8 // Cannot access the color table for Indexed8, Mono
        || dibSection.dsBmih.biCompression != BI_RGB) {
        return QImage();
    }

    const QImage::Format imageFormat = imageFromWinHBITMAP_Format(dibSection.dsBmih, hbitmapFormat);
    if (imageFormat == QImage::Format_Invalid)
        return QImage();

    return copyImageData(dibSection.dsBmih, nullptr, dibSection.dsBm.bmBits, imageFormat);
}

// Create QImage from a HBITMAP using GetDIBits(), potentially with conversion.
static QImage imageFromWinHBITMAP_GetDiBits(HBITMAP bitmap, bool forceQuads, int hbitmapFormat)
{
    BITMAPINFO_COLORTABLE256 bmiColorTable256;
    BITMAPINFO &info = reinterpret_cast<BITMAPINFO &>(bmiColorTable256);
    memset(&info, 0, sizeof(info));
    info.bmiHeader.biSize = sizeof(info.bmiHeader);

    DisplayHdc displayDc;
    if (!GetDIBits(displayDc, bitmap, 0, 1, 0, &info, DIB_RGB_COLORS)) {
        qErrnoWarning("%s: GetDIBits() failed to query data.", __FUNCTION__);
        return QImage();
    }

    if (info.bmiHeader.biHeight > 0) // Force top-down
        info.bmiHeader.biHeight = -info.bmiHeader.biHeight;
    info.bmiHeader.biCompression = BI_RGB; // Extract using no compression (can be BI_BITFIELD)
    size_t allocSize = info.bmiHeader.biSizeImage;
    if (forceQuads) {
       info.bmiHeader.biBitCount = 32;
       allocSize = info.bmiHeader.biWidth * qAbs(info.bmiHeader.biHeight) * 4;
    }

    const QImage::Format imageFormat = imageFromWinHBITMAP_Format(info.bmiHeader, hbitmapFormat);
    if (imageFormat == QImage::Format_Invalid) {
        qWarning().nospace() << __FUNCTION__ << ": unsupported image format:" << info.bmiHeader;
        return QImage();
    }

    QScopedArrayPointer<uchar> data(new uchar[allocSize]);
    if (!GetDIBits(displayDc, bitmap, 0, qAbs(info.bmiHeader.biHeight), data.data(), &info, DIB_RGB_COLORS)) {
        qErrnoWarning("%s: GetDIBits() failed to get data.", __FUNCTION__);
        return QImage();
    }
    return copyImageData(info.bmiHeader, bmiColorTable256.bmiColors, data.data(), imageFormat);
}

Q_GUI_EXPORT QImage qt_imageFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0)
{
    QImage result = imageFromWinHBITMAP_DibSection(bitmap, hbitmapFormat);
    if (result.isNull())
        result = imageFromWinHBITMAP_GetDiBits(bitmap, /* forceQuads */ false, hbitmapFormat);
    return result;
}

Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP bitmap, int hbitmapFormat = 0)
{
    return QPixmap::fromImage(imageFromWinHBITMAP_GetDiBits(bitmap, /* forceQuads */ true, hbitmapFormat));
}

Q_GUI_EXPORT HICON qt_pixmapToWinHICON(const QPixmap &p)
{
    if (p.isNull())
        return nullptr;

    QBitmap maskBitmap = p.mask();
    if (maskBitmap.isNull()) {
        maskBitmap = QBitmap(p.size());
        maskBitmap.fill(Qt::color1);
    }

    ICONINFO ii;
    ii.fIcon    = true;
    ii.hbmMask  = qt_createIconMask(maskBitmap);
    ii.hbmColor = qt_pixmapToWinHBITMAP(p, HBitmapAlpha);
    ii.xHotspot = 0;
    ii.yHotspot = 0;

    HICON hIcon = CreateIconIndirect(&ii);

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

    return hIcon;
}

Q_GUI_EXPORT QImage qt_imageFromWinHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h)
{
    QImage image(w, h, QImage::Format_ARGB32_Premultiplied);
    if (image.isNull())
        return image;
    QScopedArrayPointer<uchar> data(getDiBits(hdc, bitmap, w, h, true));
    if (data.isNull())
        return QImage();
    copyImageDataCreateAlpha(data.data(), &image);
    return image;
}

static QImage qt_imageFromWinIconHBITMAP(HDC hdc, HBITMAP bitmap, int w, int h)
{
    QImage image(w, h, QImage::Format_ARGB32_Premultiplied);
    if (image.isNull())
        return image;
    QScopedArrayPointer<uchar> data(getDiBits(hdc, bitmap, w, h, true));
    if (data.isNull())
        return QImage();
    memcpy(image.bits(), data.data(), size_t(image.sizeInBytes()));
    return image;
}

static inline bool hasAlpha(const QImage &image)
{
    const int w = image.width();
    const int h = image.height();
    for (int y = 0; y < h; ++y) {
        const QRgb *scanLine = reinterpret_cast<const QRgb *>(image.scanLine(y));
        for (int x = 0; x < w; ++x) {
            if (qAlpha(scanLine[x]) != 0)
                return true;
        }
    }
    return false;
}

Q_GUI_EXPORT QPixmap qt_pixmapFromWinHICON(HICON icon)
{
    HDC screenDevice = GetDC(nullptr);
    HDC hdc = CreateCompatibleDC(screenDevice);
    ReleaseDC(nullptr, screenDevice);

    ICONINFO iconinfo;
    const bool result = GetIconInfo(icon, &iconinfo); //x and y Hotspot describes the icon center
    if (!result) {
        qErrnoWarning("QPixmap::fromWinHICON(), failed to GetIconInfo()");
        DeleteDC(hdc);
        return QPixmap();
    }

    const int w = int(iconinfo.xHotspot) * 2;
    const int h = int(iconinfo.yHotspot) * 2;

    BITMAPINFOHEADER bitmapInfo;
    initBitMapInfoHeader(w, h, false, BI_RGB, 32u, &bitmapInfo);
    DWORD* bits;

    HBITMAP winBitmap = CreateDIBSection(hdc, reinterpret_cast<BITMAPINFO *>(&bitmapInfo),
                                         DIB_RGB_COLORS, reinterpret_cast<VOID **>(&bits),
                                         nullptr, 0);
    HGDIOBJ oldhdc = static_cast<HBITMAP>(SelectObject(hdc, winBitmap));
    DrawIconEx(hdc, 0, 0, icon, w, h, 0, nullptr, DI_NORMAL);
    QImage image = qt_imageFromWinIconHBITMAP(hdc, winBitmap, w, h);

    if (!image.isNull() && !hasAlpha(image)) { //If no alpha was found, we use the mask to set alpha values
        DrawIconEx( hdc, 0, 0, icon, w, h, 0, nullptr, DI_MASK);
        const QImage mask = qt_imageFromWinIconHBITMAP(hdc, winBitmap, w, h);

        for (int y = 0 ; y < h ; y++){
            QRgb *scanlineImage = reinterpret_cast<QRgb *>(image.scanLine(y));
            const QRgb *scanlineMask = mask.isNull() ? nullptr : reinterpret_cast<const QRgb *>(mask.scanLine(y));
            for (int x = 0; x < w ; x++){
                if (scanlineMask && qRed(scanlineMask[x]) != 0)
                    scanlineImage[x] = 0; //mask out this pixel
                else
                    scanlineImage[x] |= 0xff000000; // set the alpha channel to 255
            }
        }
    }
    //dispose resources created by iconinfo call
    DeleteObject(iconinfo.hbmMask);
    DeleteObject(iconinfo.hbmColor);

    SelectObject(hdc, oldhdc); //restore state
    DeleteObject(winBitmap);
    DeleteDC(hdc);
    return QPixmap::fromImage(std::move(image));
}

QT_END_NAMESPACE
