// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
// SPDX-FileCopyrightText: 2013 Dennis Nienhüser <nienhueser@kde.org>
//

#include "MapThemeDownloadDialog.h"
#include "ui_MapThemeDownloadDialog.h"

#include "MarbleDirs.h"
#include "MarbleWidget.h"
#include "NewstuffModel.h"

#include <QAbstractTextDocumentLayout>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QTextDocument>

namespace Marble
{

class MapItemDelegate : public QStyledItemDelegate
{
    Q_OBJECT

public:
    MapItemDelegate(QListView *view, NewstuffModel *newstuffModel, MarbleWidget *marbleWidget);
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
    QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override;

protected:
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;

private:
    enum Element {
        Icon,
        Text,
        InstallButton,
        UpgradeButton,
        OpenButton,
        CancelButton,
        RemoveButton,
        ProgressReport
    };

    int buttonWidth(const QStyleOptionViewItem &option) const;
    QStyleOptionButton button(Element element, const QStyleOptionViewItem &option) const;
    QRect position(Element element, const QStyleOptionViewItem &option) const;
    static QString text(const QModelIndex &index);
    QListView *m_view;
    NewstuffModel *m_newstuffModel;
    mutable int m_buttonWidth;
    int const m_margin;
    int const m_iconSize;
    MarbleWidget *m_marbleWidget;
};

class Q_DECL_HIDDEN MapThemeDownloadDialog::Private : public Ui::MapThemeDownloadDialog
{
public:
    Private()
        : m_model()
    {
    }

    NewstuffModel m_model;
};

MapThemeDownloadDialog::MapThemeDownloadDialog(MarbleWidget *marbleWidget)
    : QDialog(marbleWidget)
    , d(new Private)
{
    d->setupUi(this);

    d->m_model.setTargetDirectory(MarbleDirs::localPath() + QLatin1StringView("/maps"));
    d->m_model.setProvider("https://marble.kde.org/maps-v3.xml");
    d->m_model.setRegistryFile(MarbleDirs::localPath() + QLatin1StringView("/newstuff/marble-map-themes.knsregistry"), Marble::NewstuffModel::NameTag);

    d->listView->setIconSize(QSize(130, 130));
    d->listView->setAlternatingRowColors(true);
    d->listView->setUniformItemSizes(false);
    d->listView->setResizeMode(QListView::Adjust);
    d->listView->setItemDelegate(new MapItemDelegate(d->listView, &d->m_model, marbleWidget));
    d->listView->setModel(&d->m_model);
}

MapThemeDownloadDialog::~MapThemeDownloadDialog()
{
    delete d;
}

MapItemDelegate::MapItemDelegate(QListView *view, NewstuffModel *newstuffModel, MarbleWidget *marbleWidget)
    : m_view(view)
    , m_newstuffModel(newstuffModel)
    , m_buttonWidth(0)
    , m_margin(5)
    , m_iconSize(16)
    , m_marbleWidget(marbleWidget)
{
    // nothing to do
}

void MapItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    QStyleOptionViewItem styleOption = option;
    styleOption.text = QString();
    QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &styleOption, painter);

    QAbstractTextDocumentLayout::PaintContext paintContext;
    if (styleOption.state & QStyle::State_Selected) {
        paintContext.palette.setColor(QPalette::Text, styleOption.palette.color(QPalette::Active, QPalette::HighlightedText));
    }

    // Draw the map preview icon
    QRect const iconRect = position(Icon, option);
    auto const icon = index.data(Qt::DecorationRole).value<QIcon>();
    painter->drawPixmap(iconRect, icon.pixmap(iconRect.size()));

    // Draw summary, author, and similar information
    QTextDocument document;
    QRect const textRect = position(Text, option);
    document.setTextWidth(textRect.width());
    document.setDefaultFont(option.font);
    document.setHtml(text(index));

    painter->save();
    painter->translate(textRect.topLeft());
    painter->setClipRect(0, 0, textRect.width(), textRect.height());
    document.documentLayout()->draw(painter, paintContext);
    painter->restore();

    // Draw buttons and installation progress
    if (index.data(NewstuffModel::IsTransitioning).toBool()) {
        qint64 total = qMax(qint64(1), index.data(NewstuffModel::PayloadSize).value<qint64>());
        auto progress = index.data(NewstuffModel::DownloadedSize).value<qint64>();

        QStyleOptionProgressBar progressBarOption;
        progressBarOption.rect = position(ProgressReport, option);
        progressBarOption.minimum = 0;
        progressBarOption.maximum = 100;
        progressBarOption.progress = (100.0 * progress / total);
        progressBarOption.text = QString::number(progressBarOption.progress) + QLatin1Char('%');
        progressBarOption.textVisible = true;
        QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);

        QStyleOptionButton cancelButton = button(CancelButton, option);
        QRect installRect = position(CancelButton, option);
        cancelButton.rect = installRect;
        QApplication::style()->drawControl(QStyle::CE_PushButton, &cancelButton, painter);
        QRect buttonTextRect(installRect);
        buttonTextRect.adjust(cancelButton.iconSize.width() + 4, 0, 0, 0);
        painter->drawText(buttonTextRect, Qt::AlignCenter, cancelButton.text);
    } else {
        bool const installed = index.data(NewstuffModel::IsInstalled).toBool();
        bool const upgradable = index.data(NewstuffModel::IsUpgradable).toBool();
        Element element = InstallButton;
        if (installed) {
            element = upgradable ? UpgradeButton : OpenButton;
        }
        QStyleOptionButton actionButton = button(element, option);
        QRect installRect = position(element, option);
        actionButton.rect = installRect;
        QApplication::style()->drawControl(QStyle::CE_PushButton, &actionButton, painter);
        QRect buttonTextRect(installRect);
        buttonTextRect.adjust(actionButton.iconSize.width() + 4, 0, 0, 0);
        painter->drawText(buttonTextRect, Qt::AlignCenter, actionButton.text);

        if (installed) {
            QStyleOptionButton removeButton = button(RemoveButton, option);
            QRect removeRect = position(RemoveButton, option);
            removeButton.rect = removeRect;
            QApplication::style()->drawControl(QStyle::CE_PushButton, &removeButton, painter);
            buttonTextRect = removeRect;
            buttonTextRect.adjust(removeButton.iconSize.width() + 4, 0, 0, 0);
            painter->drawText(buttonTextRect, Qt::AlignCenter, removeButton.text);
        }
    }
}

QSize MapItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if (index.column() == 0) {
        QSize const iconSize = option.decorationSize;
        QTextDocument doc;
        doc.setDefaultFont(option.font);
        doc.setTextWidth(qMax(200, m_view->contentsRect().width() - iconSize.width() - buttonWidth(option) - 3 * m_margin));
        doc.setHtml(text(index));
        return QSize(iconSize.width() + doc.size().width() + buttonWidth(option) + 3 * m_margin, 2 + qMax(iconSize.height(), qRound(doc.size().height())));
    }

    return {};
}

bool MapItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if ((event->type() == QEvent::MouseButtonRelease)) {
        auto mouseEvent = static_cast<QMouseEvent *>(event);
        if (index.data(NewstuffModel::IsTransitioning).toBool()) {
            QRect cancelRect = position(CancelButton, option);
            if (cancelRect.contains(mouseEvent->pos())) {
                m_newstuffModel->cancel(index.row());
                return true;
            }
        } else {
            bool const installed = index.data(NewstuffModel::IsInstalled).toBool();
            bool const upgradable = index.data(NewstuffModel::IsUpgradable).toBool();

            if (!installed || upgradable) {
                QRect installRect = position(InstallButton, option);
                if (installRect.contains(mouseEvent->pos())) {
                    m_newstuffModel->install(index.row());
                    return true;
                }
            }

            if (installed && !upgradable && m_marbleWidget) {
                QRect openRect = position(OpenButton, option);
                if (openRect.contains(mouseEvent->pos())) {
                    QStringList const files = index.data(NewstuffModel::InstalledFiles).toStringList();
                    for (const QString &file : files) {
                        if (file.endsWith(QLatin1StringView(".dgml"))) {
                            QFileInfo dgmlFile(file);
                            QDir baseDir = dgmlFile.dir();
                            baseDir.cdUp();
                            baseDir.cdUp();
                            int const index = baseDir.absolutePath().size();
                            QString const mapTheme = dgmlFile.absoluteFilePath().mid(index + 1);
                            m_marbleWidget->setMapThemeId(mapTheme);
                            return true;
                        }
                    }
                }
            }

            if (installed) {
                QRect removeRect = position(RemoveButton, option);
                if (removeRect.contains(mouseEvent->pos())) {
                    m_newstuffModel->uninstall(index.row());
                    return true;
                }
            }
        }
    }

    return false;
}

int MapItemDelegate::buttonWidth(const QStyleOptionViewItem &option) const
{
    if (m_buttonWidth <= 0) {
        int const installWidth = option.fontMetrics.size(0, tr("Install")).width();
        int const removeWidth = option.fontMetrics.size(0, tr("Remove")).width();
        int const cancelWidth = option.fontMetrics.size(0, tr("Cancel")).width();
        int const upgradeWidth = option.fontMetrics.size(0, tr("Upgrade")).width();
        m_buttonWidth = 2 * m_iconSize + qMax(qMax(installWidth, removeWidth), qMax(cancelWidth, upgradeWidth));
    }

    return m_buttonWidth;
}

QStyleOptionButton MapItemDelegate::button(Element element, const QStyleOptionViewItem &option) const
{
    QStyleOptionButton result;
    result.state = option.state;
    result.state &= ~QStyle::State_HasFocus;

    result.palette = option.palette;
    result.features = QStyleOptionButton::None;

    switch (element) {
    case InstallButton:
        result.text = tr("Install");
        result.icon = QIcon(QStringLiteral(":/marble/dialog-ok.png"));
        result.iconSize = QSize(m_iconSize, m_iconSize);
        break;
    case UpgradeButton:
        result.text = tr("Update");
        result.icon = QIcon(QStringLiteral(":/marble/system-software-update.png"));
        result.iconSize = QSize(m_iconSize, m_iconSize);
        break;
    case OpenButton:
        result.text = tr("Open");
        result.icon = QIcon(QStringLiteral(":/marble/document-open.png"));
        result.iconSize = QSize(m_iconSize, m_iconSize);
        break;
    case CancelButton:
        result.text = tr("Cancel");
        break;
    case RemoveButton:
        result.text = tr("Remove");
        result.icon = QIcon(QStringLiteral(":/marble/edit-delete.png"));
        result.iconSize = QSize(m_iconSize, m_iconSize);
        break;
    default:
        // ignored
        break;
    }

    return result;
}

QRect MapItemDelegate::position(Element element, const QStyleOptionViewItem &option) const
{
    int const width = buttonWidth(option);
    QPoint const topLeftCol1 = option.rect.topLeft() + QPoint(0, 2);
    QPoint const topLeftCol2 = topLeftCol1 + QPoint(option.decorationSize.width(), 0);
    QPoint const topLeftCol3 = topLeftCol2 + QPoint(option.rect.width() - 3 * m_margin - width - option.decorationSize.width(), 0);
    switch (element) {
    case Icon:
        return QRect(topLeftCol1, option.decorationSize);
    case Text:
        return QRect(topLeftCol2, QSize(topLeftCol3.x() - topLeftCol2.x(), option.rect.height()));
    case InstallButton:
    case UpgradeButton:
    case OpenButton: {
        QStyleOptionButton optionButton = button(element, option);
        QSize size = option.fontMetrics.size(0, optionButton.text) + QSize(4, 4);
        QSize buttonSize = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &optionButton, size);
        buttonSize.setWidth(width);
        return QRect(topLeftCol3, buttonSize);
    }
    case RemoveButton:
    case CancelButton: {
        QStyleOptionButton optionButton = button(element, option);
        QSize size = option.fontMetrics.size(0, optionButton.text) + QSize(4, 4);
        QSize buttonSize = QApplication::style()->sizeFromContents(QStyle::CT_PushButton, &optionButton, size);
        buttonSize.setWidth(width);
        return QRect(topLeftCol3 + QPoint(0, option.fontMetrics.height() + 8 + m_margin), buttonSize);
    }
    case ProgressReport: {
        QSize const progressSize = QSize(width, option.fontMetrics.height() + 4);
        return QRect(topLeftCol3 + QPoint(0, m_margin), progressSize);
    }
    }

    Q_ASSERT(false);
    return {};
}

QString MapItemDelegate::text(const QModelIndex &index)
{
    qreal const size = index.data(NewstuffModel::PayloadSize).toLongLong() / 1024.0 / 1024.0;
    // Fields are typically not longer than 200 characters. Prevent excessive long text here anyway
    // due to bug 319542
    int const maxEntrySize = 4096;
    return QStringLiteral("<p><b>%1</b><br />%2</p><p>Author: %3<br />License: %4<br />Version %5 (%6) %7</p>")
        .arg(index.data().toString())
        .arg(index.data(NewstuffModel::Summary).toString().left(maxEntrySize))
        .arg(index.data(NewstuffModel::Author).toString().left(maxEntrySize))
        .arg(index.data(NewstuffModel::License).toString().left(maxEntrySize))
        .arg(index.data(NewstuffModel::Version).toString().left(maxEntrySize))
        .arg(index.data(NewstuffModel::ReleaseDate).toString().left(maxEntrySize))
        .arg(size > 0 ? QStringLiteral("%1 MB").arg(size, 0, 'f', 1) : QString());
}

}

#include "MapThemeDownloadDialog.moc" // needed for Q_OBJECT here in source

#include "moc_MapThemeDownloadDialog.cpp"
