/***************************************************************************
 *   copyright       : (C) 2003-2022 by Pascal Brachet                     *
 *   https://www.xm1math.net/texmaker/                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   @license GPL-2.0+ <https://spdx.org/licenses/GPL-2.0+.html>           *
 ***************************************************************************/

#include <QtGui>
#include <QAbstractItemModel>
#include <QPainter>
#include <QApplication>
#include <QPixmap>
#include <QByteArray>
#include <QDebug>
#include "icondelegate.h"

static const int textMargin = 2;

void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const
{
    Q_ASSERT(index.isValid());
    const QAbstractItemModel *model = index.model();
    Q_ASSERT(model);

    QStyleOptionViewItem opt = option;

    // set font
    QVariant value = model->data(index, Qt::FontRole);
    if (value.isValid())
        opt.font = qvariant_cast<QFont>(value);

    // set text alignment
    value = model->data(index, Qt::TextAlignmentRole);
    if (value.isValid())
        opt.displayAlignment = QFlag(value.toInt());

    // set text color
    value = model->data(index, Qt::ForegroundRole);
    if (value.isValid() && qvariant_cast<QColor>(value).isValid())
        opt.palette.setColor(QPalette::Text, qvariant_cast<QColor>(value));

    // do layout
    value = model->data(index, Qt::DecorationRole);
    QPixmap pixmap = decoration(opt, value);
    QRect pixmapRect(0,0,32,32);//=pixmap.rect();


    QFontMetrics fontMetrics(opt.font);
    QString text = model->data(index, Qt::DisplayRole).toString();
    QRect textRect(0, 0, 0,0);
    //QRect textRect(0, 0, fontMetrics.width(text), fontMetrics.lineSpacing());

    value = model->data(index, Qt::CheckStateRole);
    QRect checkRect = check(opt, opt.rect, value);
    Qt::CheckState checkState = static_cast<Qt::CheckState>(value.toInt());

    doLayout(opt, &checkRect, &pixmapRect, &textRect, false);

    // draw the background color
    if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) {
        QPalette::ColorGroup cg = option.state & QStyle::State_Enabled
                                  ? QPalette::Normal : QPalette::Disabled;
        painter->fillRect(option.rect, option.palette.brush(cg, QPalette::Highlight));
//painter->fillRect(option.rect,QColor("#78A9dc"));
painter->fillRect(option.rect,QColor("#2E2E2E"));
    } else {
        value = model->data(index, Qt::BackgroundRole);
        if (value.isValid() && qvariant_cast<QColor>(value).isValid())
            painter->fillRect(option.rect, qvariant_cast<QColor>(value));
    }

    // draw the item
//    drawCheck(painter, opt, checkRect, checkState);

    drawDecoration(painter, opt, pixmapRect, pixmap);
//    drawDisplay(painter, opt, textRect, text);
//    drawFocus(painter, opt, textRect);
}

QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option,
                              const QModelIndex &index) const
{
    Q_ASSERT(index.isValid());
    const QAbstractItemModel *model = index.model();
    Q_ASSERT(model);

    QVariant value = model->data(index, Qt::FontRole);
    QFont fnt = value.isValid() ? qvariant_cast<QFont>(value) : option.font;
   // QString text = model->data(index, Qt::DisplayRole).toString();
    QRect pixmapRect;
    if (model->data(index, Qt::DecorationRole).isValid())
        pixmapRect = QRect(0, 0, option.decorationSize.width(),
                           option.decorationSize.height());

    //QFontMetrics fontMetrics(fnt);
    QRect textRect(0, 0, 0,0);
    //QRect textRect(0, 0, fontMetrics.width(text), fontMetrics.lineSpacing());
    QRect checkRect = check(option, textRect, model->data(index, Qt::CheckStateRole));
    doLayout(option, &checkRect, &pixmapRect, &textRect, true);

    return (pixmapRect|textRect|checkRect).size();
}


/*!
    Renders the decoration \a pixmap within the rectangle specified by
    \a rect using the given \a painter and style \a option.
*/

void IconDelegate::drawDecoration(QPainter *painter, const QStyleOptionViewItem &option,
                                   const QRect &rect, const QPixmap &pixmap) const
{
    QPoint p=rect.topLeft();

    if (!pixmap.isNull() && !rect.isEmpty()) {
        if (option.state & QStyle::State_Selected) {
            bool enabled = option.state & QStyle::State_Enabled;

        #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
        QPixmap pm = selectedPixmap(pixmap, option.palette, option.state & QStyle::State_Enabled);
        if (qApp->devicePixelRatio()>=2)
            {
            pm.setDevicePixelRatio(qApp->devicePixelRatio());
            p-=QPoint(16,0);
            }
            painter->drawPixmap(p,pm);
        #else
        QPixmap *pm = selected(pixmap, option.palette, enabled);
        if (qApp->devicePixelRatio()>=2)
            {
            pm->setDevicePixelRatio(qApp->devicePixelRatio());
            p-=QPoint(16,0);
            }
            painter->drawPixmap(p, *pm);
        #endif
        } else {
        #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
        QPixmap pm = selectedPixmap(pixmap, option.palette, true);
           if (qApp->devicePixelRatio()>=2)
            {
                pm.setDevicePixelRatio(qApp->devicePixelRatio());
                p-=QPoint(16,0);
            }
            painter->drawPixmap(p,pm);
        #else
        QPixmap *pm=selected(pixmap, option.palette, true);
           if (qApp->devicePixelRatio()>=2)
            {
                pm->setDevicePixelRatio(qApp->devicePixelRatio());
                p-=QPoint(16,0);
            }
            painter->drawPixmap(p, *pm);
            #endif
        }
    }
}

/*!
    Renders the region within the rectangle specified by \a rect, indicating
    that it has the focus, using the given \a painter and style \a option.
*/

void IconDelegate::drawFocus(QPainter *painter,
                              const QStyleOptionViewItem &option,
                              const QRect &rect) const
{
    if (option.state & QStyle::State_HasFocus) {
        QStyleOptionFocusRect o;
        o.QStyleOption::operator=(option);
        o.rect = rect;
        QPalette::ColorGroup cg = (option.state & QStyle::State_Enabled)
                                  ? QPalette::Normal : QPalette::Disabled;
        o.backgroundColor = option.palette.color(cg, (option.state & QStyle::State_Selected)
                                                 ? QPalette::Highlight : QPalette::Window);
        QApplication::style()->drawPrimitive(QStyle::PE_FrameFocusRect, &o, painter);
    }
}

/*!
    Renders a check indicator within the rectangle specified by \a
    rect, using the given \a painter and style \a option, using the
    given \a state.
*/

void IconDelegate::drawCheck(QPainter *painter,
                              const QStyleOptionViewItem &option,
                              const QRect &rect, Qt::CheckState state) const
{
    if (!rect.isValid())
        return;

    QStyleOptionViewItem opt;
    opt.QStyleOption::operator=(option);
    opt.rect = rect;
    opt.state = opt.state & ~QStyle::State_HasFocus;

    switch (state) {
    case Qt::Unchecked:
        opt.state |= QStyle::State_Off;
        break;
    case Qt::PartiallyChecked:
        opt.state |= QStyle::State_NoChange;
        break;
    case Qt::Checked:
        opt.state |= QStyle::State_On;
        break;
    }
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QApplication::style()->drawPrimitive(QStyle::PE_IndicatorViewItemCheck, &opt, painter);
#else
QApplication::style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemCheck, &opt, painter);
#endif
}

void IconDelegate::doLayout(const QStyleOptionViewItem &option,
                             QRect *checkRect, QRect *pixmapRect, QRect *textRect,
                             bool hint) const
{
    Q_ASSERT(checkRect && pixmapRect && textRect);
    int x = option.rect.left();
    int y = option.rect.top();
    int w, h;

    textRect->adjust(-textMargin, 0, textMargin, 0); // add width padding

    QSize pm(0, 0);
    if (pixmapRect->isValid())
        pm = option.decorationSize;

    if (hint) {
        w = qMax(textRect->width(), pm.width());
        h = qMax(textRect->height(), pm.height());
    } else {
        w = option.rect.width();
        h = option.rect.height();
    }

    int cw = 0;
    QRect check;
    if (checkRect->isValid()) {
        check.setRect(x, y, checkRect->width() + textMargin * 2, h);
        cw = check.width();
        if (option.direction == Qt::LeftToRight)
            x += cw;
    }

    QRect display;
    QRect decoration;
    QStyleOptionViewItem::Position position = option.decorationPosition;
    if (option.direction == Qt::RightToLeft) {
        if (position == QStyleOptionViewItem::Right)
            position = QStyleOptionViewItem::Left;
        else if (position == QStyleOptionViewItem::Left)
            position = QStyleOptionViewItem::Right;
    }

    switch (position) {
    case QStyleOptionViewItem::Top: {
        if (!pm.isEmpty())
            pm.setHeight(pm.height() + textMargin); // add space
        decoration.setRect(x, y, w, pm.height());
        h = hint ? textRect->height() : h - pm.height();
        display.setRect(x, y + pm.height(), w, h);
        break; }
    case QStyleOptionViewItem::Bottom: {
        if (!textRect->isEmpty())
            textRect->setHeight(textRect->height() + textMargin); // add space
        h = hint ? textRect->height() + pm.height() : h;
        decoration.setRect(x, y + h - pm.height(), w, pm.height());
        h = hint ? textRect->height() : h - pm.height();
        display.setRect(x, y, w, h);
        break; }
    case QStyleOptionViewItem::Left: {
        if (!pm.isEmpty())
            pm.setWidth(pm.width() + textMargin); // add space
        decoration.setRect(x, y, pm.width(), h);
        w = hint ? textRect->width() : w - pm.width() - cw;
        display.setRect(x + pm.width(), y, w, h);
        break; }
    case QStyleOptionViewItem::Right: {
        if (!textRect->isEmpty())
            textRect->setWidth(textRect->width() + textMargin); // add space
        w = hint ? textRect->width() + pm.width() : w;
        decoration.setRect(x + w - pm.width() - cw, y, pm.width(), h);
        w = hint ? textRect->width() : w - pm.width() - cw;
        display.setRect(x, y, w, h);
        break; }
    default:
        qWarning("doLayout: decoration positon is invalid");
        decoration = *pixmapRect;
        break;
    }

    if (!hint) { // we only need to do the internal layout if we are going to paint
        *checkRect = QStyle::alignedRect(option.direction, Qt::AlignCenter,
                                         checkRect->size(), check);
        *pixmapRect = QStyle::alignedRect(option.direction, option.decorationAlignment,
                                          pixmapRect->size(), decoration);
    } else {
        *checkRect = check;
        *pixmapRect = decoration;
    }
    *textRect = display;
}

QPixmap IconDelegate::decoration(const QStyleOptionViewItem &option, const QVariant &variant) const
{
    switch (variant.type()) {
    case QVariant::Icon:
        return qvariant_cast<QIcon>(variant).pixmap(option.decorationSize,
                                       option.state & QStyle::State_Enabled
                                       ? QIcon::Normal : QIcon::Disabled,
                                       option.state & QStyle::State_Open
                                       ? QIcon::On : QIcon::Off);
    case QVariant::Color: {
        static QPixmap pixmap(20, 20);
        pixmap.fill(qvariant_cast<QColor>(variant));
        return pixmap; }
    default:
        break;
    }
    return qvariant_cast<QPixmap>(variant);
}

/*!
  \internal
*/

static QString qPixmapSerial(quint64 i, bool enabled)
{
    ushort arr[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', ushort('0' + enabled) };
    ushort *ptr = &arr[16];

    while (i > 0) {
        // hey - it's our internal representation, so use the ascii character after '9'
        // instead of 'a' for hex
        *(--ptr) = '0' + i % 16;
        i >>= 4;
    }

    return QString((const QChar *)ptr, int(&arr[sizeof(arr) / sizeof(ushort)] - ptr));
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
QPixmap IconDelegate::selectedPixmap(const QPixmap &pixmap, const QPalette &palette, bool enabled)
{
    const QString key = qPixmapSerial(pixmap.cacheKey(), enabled);
    QPixmap pm;
    if (!QPixmapCache::find(key, &pm)) {
        QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);

        QColor color = palette.color(enabled ? QPalette::Normal : QPalette::Disabled,
                                     QPalette::Highlight);
        color.setAlphaF(0.3f);

        QPainter painter(&img);
        painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
        painter.fillRect(0, 0, img.width(), img.height(), color);
        painter.end();

        pm = QPixmap(QPixmap::fromImage(img));
        const int n = (img.sizeInBytes() >> 10) + 1;
        if (QPixmapCache::cacheLimit() < n)
            QPixmapCache::setCacheLimit(n);

        QPixmapCache::insert(key, pm);
    }
    return pm;
}
#else
QPixmap *IconDelegate::selected(const QPixmap &pixmap, const QPalette &palette, bool enabled) const
{
    //QString key;
    //key.sprintf("%d-%d", pixmap.serialNumber(), enabled);
    QString key = qPixmapSerial(pixmap.cacheKey(), enabled);
    QPixmap *pm = QPixmapCache::find(key);
//    QPixmap *pm;
//    QPixmapCache::find(key,pm);
    if (!pm) {
        QImage img = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);

        QColor color = palette.color(enabled ? QPalette::Normal : QPalette::Disabled,QPalette::Highlight);

        color.setAlphaF(0.3);

        QPainter painter(&img);
        painter.setCompositionMode(QPainter::CompositionMode_SourceAtop);
        painter.fillRect(0, 0, img.width(), img.height(), color);
        painter.end();

        pm = new QPixmap(QPixmap::fromImage(img));
        QPixmapCache::insert(key, *pm);
    }
    return pm;
}
#endif
/*!
  \internal
*/

QRect IconDelegate::check(const QStyleOptionViewItem &option,
                           const QRect &bounding, const QVariant &value) const
{
    if (value.isValid()) {
        QStyleOptionButton opt;
        opt.QStyleOption::operator=(option);
        opt.rect = bounding;
        return QApplication::style()->subElementRect(QStyle::SE_ItemViewItemCheckIndicator, &opt);
    }
    return QRect();
}
