/* This file is part of the KDE project
   SPDX-FileCopyrightText: 2002 Patrick Julien <freak@codepimps.org>
   SPDX-FileCopyrightText: 2007 Jan Hambrecht <jaham@gmx.net>
   SPDX-FileCopyrightText: 2007 Sven Langkamp <sven.langkamp@gmail.com>
   SPDX-FileCopyrightText: 2011 Srikanth Tiyyagura <srikanth.tulasiram@gmail.com>
   SPDX-FileCopyrightText: 2011 José Luis Vergara <pentalis@gmail.com>
   SPDX-FileCopyrightText: 2013 Sascha Suelzer <s.suelzer@gmail.com>

   SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include "KoResourceItemChooser.h"

#include <math.h>

#include <QAbstractProxyModel>
#include <QButtonGroup>
#include <QGridLayout>
#include <QHeaderView>
#include <QImage>
#include <QLabel>
#include <QPainter>
#include <QPixmap>
#include <QPushButton>
#include <QScrollArea>
#include <QSplitter>
#include <QToolButton>
#include <QWheelEvent>

#include <KLocalizedString>

#include <KoFileDialog.h>
#include <KoIcon.h>

#include "KoResource.h"
#include "KoResourceItemChooserSync.h"
#include "KoResourceItemDelegate.h"
#include "KoResourceItemView.h"
#include "KoResourceModel.h"
#include "KoResourceServerAdapter.h"
#include "KoResourceTaggingManager.h"
#include "KoTagChooserWidget.h"
#include "KoTagFilterWidget.h"

class Q_DECL_HIDDEN KoResourceItemChooser::Private
{
public:
    Private()
        : model(nullptr)
        , view(nullptr)
        , buttonGroup(nullptr)
        , viewModeButton(nullptr)
        , usePreview(false)
        , previewScroller(nullptr)
        , previewLabel(nullptr)
        , splitter(nullptr)
        , tiledPreview(false)
        , grayscalePreview(false)
        , synced(false)
        , updatesBlocked(false)
        , savedResourceWhileReset(nullptr)
    {
    }
    KoResourceModel *model;
    KoResourceTaggingManager *tagManager;
    KoResourceItemView *view;
    QButtonGroup *buttonGroup;
    QToolButton *viewModeButton;

    bool usePreview;
    QScrollArea *previewScroller;
    QLabel *previewLabel;
    QSplitter *splitter;
    QGridLayout *buttonLayout;
    bool tiledPreview;
    bool grayscalePreview;
    bool synced;
    bool updatesBlocked;

    KoResource *savedResourceWhileReset;

    QList<QAbstractButton *> customButtons;
};

KoResourceItemChooser::KoResourceItemChooser(QSharedPointer<KoAbstractResourceServerAdapter> resourceAdapter, QWidget *parent, bool usePreview)
    : QWidget(parent)
    , d(new Private())
{
    Q_ASSERT(resourceAdapter);

    d->splitter = new QSplitter(this);

    d->model = new KoResourceModel(resourceAdapter, this);
    connect(d->model, &KoResourceModel::beforeResourcesLayoutReset, this, &KoResourceItemChooser::slotBeforeResourcesLayoutReset);
    connect(d->model, &KoResourceModel::afterResourcesLayoutReset, this, &KoResourceItemChooser::slotAfterResourcesLayoutReset);

    d->view = new KoResourceItemView(this);
    d->view->setModel(d->model);
    d->view->setItemDelegate(new KoResourceItemDelegate(this));
    d->view->setSelectionMode(QAbstractItemView::SingleSelection);
    d->view->viewport()->installEventFilter(this);

    connect(d->view, &KoResourceItemView::currentResourceChanged, this, &KoResourceItemChooser::activated);
    connect(d->view, &KoResourceItemView::contextMenuRequested, this, &KoResourceItemChooser::contextMenuRequested);

    connect(d->view, &KoTableView::sigSizeChanged, this, &KoResourceItemChooser::updateView);

    d->splitter->addWidget(d->view);
    d->splitter->setStretchFactor(0, 2);

    d->usePreview = usePreview;
    if (d->usePreview) {
        d->previewScroller = new QScrollArea(this);
        d->previewScroller->setWidgetResizable(true);
        d->previewScroller->setBackgroundRole(QPalette::Dark);
        d->previewScroller->setVisible(true);
        d->previewScroller->setAlignment(Qt::AlignCenter);
        d->previewLabel = new QLabel(this);
        d->previewScroller->setWidget(d->previewLabel);
        d->splitter->addWidget(d->previewScroller);

        if (d->splitter->count() == 2) {
            d->splitter->setSizes(QList<int>() << 280 << 160);
        }
    }

    d->splitter->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
    connect(d->splitter, &QSplitter::splitterMoved, this, &KoResourceItemChooser::splitterMoved);

    d->buttonGroup = new QButtonGroup(this);
    d->buttonGroup->setExclusive(false);

    QGridLayout *layout = new QGridLayout(this);

    d->buttonLayout = new QGridLayout();

    QPushButton *button = new QPushButton(this);
    button->setIcon(koIcon("document-open"));
    button->setToolTip(i18nc("@info:tooltip", "Import resource"));
    button->setEnabled(true);
    d->buttonGroup->addButton(button, Button_Import);
    d->buttonLayout->addWidget(button, 0, 0);

    button = new QPushButton(this);
    button->setIcon(koIcon("trash-empty"));
    button->setToolTip(i18nc("@info:tooltip", "Delete resource"));
    button->setEnabled(false);
    d->buttonGroup->addButton(button, Button_Remove);
    d->buttonLayout->addWidget(button, 0, 1);

    connect(d->buttonGroup, &QButtonGroup::idClicked, this, &KoResourceItemChooser::slotButtonClicked);

    d->buttonLayout->setColumnStretch(0, 1);
    d->buttonLayout->setColumnStretch(1, 1);
    d->buttonLayout->setColumnStretch(2, 2);
    d->buttonLayout->setSpacing(0);
    d->buttonLayout->setContentsMargins({});

    d->viewModeButton = new QToolButton(this);
    d->viewModeButton->setIcon(koIcon("view-choose"));
    d->viewModeButton->setPopupMode(QToolButton::InstantPopup);
    d->viewModeButton->setVisible(false);

    d->tagManager = new KoResourceTaggingManager(d->model, this);

    layout->addWidget(d->tagManager->tagChooserWidget(), 0, 0);
    layout->addWidget(d->viewModeButton, 0, 1);
    layout->addWidget(d->splitter, 1, 0, 1, 2);
    layout->addWidget(d->tagManager->tagFilterWidget(), 2, 0, 1, 2);
    layout->addLayout(d->buttonLayout, 3, 0, 1, 2);
    layout->setContentsMargins({});
    layout->setSpacing(0);
    updateButtonState();
    showTaggingBar(false);
    activated(d->model->index(0, 0));
}

KoResourceItemChooser::~KoResourceItemChooser()
{
    disconnect();
    delete d;
}

void KoResourceItemChooser::slotButtonClicked(int button)
{
    if (button == Button_Import) {
        QString extensions = d->model->extensions();
        QString filter = QString("%1").arg(extensions.replace(QString(":"), QString(" ")));

        KoFileDialog dialog(nullptr, KoFileDialog::OpenFile, "OpenDocument");
        dialog.setNameFilter(filter);
        dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
        QString filename = dialog.filename();

        d->model->importResourceFile(filename);
    } else if (button == Button_Remove) {
        QModelIndex index = d->view->currentIndex();
        int row = index.row();
        int column = index.column();
        if (index.isValid()) {
            KoResource *resource = resourceFromModelIndex(index);
            if (resource) {
                d->model->removeResource(resource);
            }
        }
        if (column == 0) {
            int rowMin = --row;
            row = qBound(0, rowMin, row);
        }
        int columnMin = --column;
        column = qBound(0, columnMin, column);
        setCurrentItem(row, column);
        activated(d->model->index(row, column));
    }
    updateButtonState();
}

void KoResourceItemChooser::showButtons(bool show)
{
    foreach (QAbstractButton *button, d->buttonGroup->buttons()) {
        show ? button->show() : button->hide();
    }

    foreach (QAbstractButton *button, d->customButtons) {
        show ? button->show() : button->hide();
    }
}

void KoResourceItemChooser::addCustomButton(QAbstractButton *button, int cell)
{
    d->buttonLayout->addWidget(button, 0, cell);
    d->buttonLayout->setColumnStretch(2, 1);
    d->buttonLayout->setColumnStretch(3, 1);
}

void KoResourceItemChooser::showTaggingBar(bool show)
{
    d->tagManager->showTaggingBar(show);
}

void KoResourceItemChooser::setRowCount(int rowCount)
{
    int resourceCount = d->model->resourcesCount();
    d->model->setColumnCount(static_cast<qreal>(resourceCount) / rowCount);
    // Force an update to get the right row height (in theory)
    QRect geometry = d->view->geometry();
    d->view->setViewMode(KoResourceItemView::FIXED_ROWS);
    d->view->setGeometry(geometry.adjusted(0, 0, 0, 1));
    d->view->setGeometry(geometry);
}

void KoResourceItemChooser::setColumnCount(int columnCount)
{
    d->model->setColumnCount(columnCount);
}

void KoResourceItemChooser::setRowHeight(int rowHeight)
{
    d->view->verticalHeader()->setDefaultSectionSize(rowHeight);
}

void KoResourceItemChooser::setColumnWidth(int columnWidth)
{
    d->view->horizontalHeader()->setDefaultSectionSize(columnWidth);
}

void KoResourceItemChooser::setItemDelegate(QAbstractItemDelegate *delegate)
{
    d->view->setItemDelegate(delegate);
}

KoResource *KoResourceItemChooser::currentResource() const
{
    QModelIndex index = d->view->currentIndex();
    if (index.isValid()) {
        return resourceFromModelIndex(index);
    }
    return nullptr;
}

void KoResourceItemChooser::setCurrentResource(KoResource *resource)
{
    // don't update if the change came from the same chooser
    if (d->updatesBlocked) {
        return;
    }

    QModelIndex index = d->model->indexFromResource(resource);
    if (!index.isValid())
        return;

    d->view->setCurrentIndex(index);
    updatePreview(resource);
}

void KoResourceItemChooser::slotBeforeResourcesLayoutReset(KoResource *activateAfterReset)
{
    d->savedResourceWhileReset = activateAfterReset ? activateAfterReset : currentResource();
}

void KoResourceItemChooser::slotAfterResourcesLayoutReset()
{
    if (d->savedResourceWhileReset) {
        this->blockSignals(true);
        setCurrentResource(d->savedResourceWhileReset);
        this->blockSignals(false);
    }
}

void KoResourceItemChooser::setPreviewOrientation(Qt::Orientation orientation)
{
    d->splitter->setOrientation(orientation);
}

void KoResourceItemChooser::setPreviewTiled(bool tiled)
{
    d->tiledPreview = tiled;
}

void KoResourceItemChooser::setGrayscalePreview(bool grayscale)
{
    d->grayscalePreview = grayscale;
}

void KoResourceItemChooser::setCurrentItem(int row, int column)
{
    QModelIndex index = d->model->index(row, column);
    if (!index.isValid())
        return;

    d->view->setCurrentIndex(index);
    if (index.isValid()) {
        updatePreview(resourceFromModelIndex(index));
    }
}

void KoResourceItemChooser::setProxyModel(QAbstractProxyModel *proxyModel)
{
    proxyModel->setSourceModel(d->model);
    d->view->setModel(proxyModel);
}

void KoResourceItemChooser::activated(const QModelIndex & /*index*/)
{
    KoResource *resource = currentResource();
    if (resource) {
        d->updatesBlocked = true;
        Q_EMIT resourceSelected(resource);
        d->updatesBlocked = false;

        updatePreview(resource);
        updateButtonState();
    }
}

void KoResourceItemChooser::updateButtonState()
{
    QAbstractButton *removeButton = d->buttonGroup->button(Button_Remove);
    if (!removeButton)
        return;

    KoResource *resource = currentResource();
    if (resource) {
        removeButton->setEnabled(true);
        return;
    }

    removeButton->setEnabled(false);
}

void KoResourceItemChooser::updatePreview(KoResource *resource)
{
    if (!d->usePreview || !resource)
        return;

    QImage image = resource->image();

    if (image.format() != QImage::Format_RGB32 || image.format() != QImage::Format_ARGB32 || image.format() != QImage::Format_ARGB32_Premultiplied) {
        image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
    }

    if (d->tiledPreview) {
        int width = d->previewScroller->width() * 4;
        int height = d->previewScroller->height() * 4;
        QImage img(width, height, image.format());
        QPainter gc(&img);
        gc.fillRect(img.rect(), Qt::white);
        gc.setPen(Qt::NoPen);
        gc.setBrush(QBrush(image));
        gc.drawRect(img.rect());
        image = img;
    }

    // Only convert to grayscale if it is rgb. Otherwise, it's gray already.
    if (d->grayscalePreview && !image.isGrayscale()) {
        QRgb *pixel = reinterpret_cast<QRgb *>(image.bits());
        for (int row = 0; row < image.height(); ++row) {
            for (int col = 0; col < image.width(); ++col) {
                const QRgb currentPixel = pixel[row * image.width() + col];
                const int red = qRed(currentPixel);
                const int green = qGreen(currentPixel);
                const int blue = qBlue(currentPixel);
                const int grayValue = (red * 11 + green * 16 + blue * 5) / 32;
                pixel[row * image.width() + col] = qRgb(grayValue, grayValue, grayValue);
            }
        }
    }
    d->previewLabel->setPixmap(QPixmap::fromImage(image));
}

KoResource *KoResourceItemChooser::resourceFromModelIndex(const QModelIndex &index) const
{
    if (!index.isValid())
        return nullptr;

    const QAbstractProxyModel *proxyModel = dynamic_cast<const QAbstractProxyModel *>(index.model());
    if (proxyModel) {
        // Get original model index, because proxy models destroy the internalPointer
        QModelIndex originalIndex = proxyModel->mapToSource(index);
        return static_cast<KoResource *>(originalIndex.internalPointer());
    }

    return static_cast<KoResource *>(index.internalPointer());
}

QSize KoResourceItemChooser::viewSize() const
{
    return d->view->size();
}

KoResourceItemView *KoResourceItemChooser::itemView() const
{
    return d->view;
}

void KoResourceItemChooser::contextMenuRequested(const QPoint &pos)
{
    d->tagManager->contextMenuRequested(currentResource(), pos);
}

void KoResourceItemChooser::setViewModeButtonVisible(bool visible)
{
    d->viewModeButton->setVisible(visible);
}

QToolButton *KoResourceItemChooser::viewModeButton() const
{
    return d->viewModeButton;
}

void KoResourceItemChooser::setSynced(bool sync)
{
    if (d->synced == sync)
        return;

    d->synced = sync;
    KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
    if (sync) {
        connect(chooserSync, &KoResourceItemChooserSync::baseLenghtChanged, this, &KoResourceItemChooser::baseLengthChanged);
        baseLengthChanged(chooserSync->baseLength());
    } else {
        chooserSync->disconnect(this);
    }
}

void KoResourceItemChooser::baseLengthChanged(int length)
{
    if (d->synced) {
        int resourceCount = d->model->resourcesCount();
        int width = d->view->width();
        int maxColums = width / length;
        int cols = width / (2 * length) + 1;
        while (cols <= maxColums) {
            int size = width / cols;
            int rows = ceil(resourceCount / (double)cols);
            if (rows * size < (d->view->height() - 5)) {
                break;
            }
            cols++;
        }
        setColumnCount(cols);
    }
    d->view->updateView();
}

bool KoResourceItemChooser::eventFilter(QObject *object, QEvent *event)
{
    if (d->synced && event->type() == QEvent::Wheel) {
        KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
        QWheelEvent *qwheel = static_cast<QWheelEvent *>(event);
        if (qwheel->modifiers() & Qt::ControlModifier) {
            int degrees = qwheel->angleDelta().y() / 8;
            int newBaseLength = chooserSync->baseLength() + degrees / 15 * 10;
            chooserSync->setBaseLength(newBaseLength);
            return true;
        }
    }
    return QObject::eventFilter(object, event);
}

void KoResourceItemChooser::resizeEvent(QResizeEvent *event)
{
    QWidget::resizeEvent(event);
    updateView();
}

void KoResourceItemChooser::showEvent(QShowEvent *event)
{
    QWidget::showEvent(event);
    updateView();
}

void KoResourceItemChooser::updateView()
{
    if (d->synced) {
        KoResourceItemChooserSync *chooserSync = KoResourceItemChooserSync::instance();
        baseLengthChanged(chooserSync->baseLength());
    }
}
