/*
Gwenview: an image viewer
Copyright 2007 Aurélien Gâteau <agateau@kde.org>

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.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/
#include "infocontextmanageritem.h"

// Qt
#include <QLabel>
#include <QPointer>
#include <QPushButton>
#include <QScrollArea>
#include <QVBoxLayout>

// KF
#include <KFileItem>
#include <KLocalizedString>

// Local
#include "imagemetainfodialog.h"
#include "sidebar.h"
#include <lib/archiveutils.h>
#include <lib/contextmanager.h>
#include <lib/document/document.h>
#include <lib/document/documentfactory.h>
#include <lib/eventwatcher.h>
#include <lib/gvdebug.h>
#include <lib/gwenviewconfig.h>
#include <lib/preferredimagemetainfomodel.h>

namespace Gwenview
{
#undef ENABLE_LOG
#undef LOG
// #define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) qCDebug(GWENVIEW_APP_LOG) << x
#else
#define LOG(x) ;
#endif

/**
 * This widget is capable of showing multiple lines of key/value pairs.
 */
class KeyValueWidget : public QWidget
{
    struct Row {
        Row(QWidget *parent)
            : keyLabel(new QLabel(parent))
            , valueLabel(new QLabel(parent))
        {
            initLabel(keyLabel);
            initLabel(valueLabel);

            QPalette pal = keyLabel->palette();
            QColor color = pal.color(QPalette::WindowText);
            color.setAlphaF(0.65);
            pal.setColor(QPalette::WindowText, color);
            keyLabel->setPalette(pal);

            valueLabel->setIndent(parent->fontInfo().pixelSize());
        }

        ~Row()
        {
            delete keyLabel;
            delete valueLabel;
        }

        int setLabelGeometries(int rowY, int labelWidth)
        {
            int labelHeight = keyLabel->heightForWidth(labelWidth);
            keyLabel->setGeometry(0, rowY, labelWidth, labelHeight);
            rowY += labelHeight;
            labelHeight = valueLabel->heightForWidth(labelWidth);
            valueLabel->setGeometry(0, rowY, labelWidth, labelHeight);
            rowY += labelHeight;
            return rowY;
        }

        int heightForWidth(int width) const
        {
            return keyLabel->heightForWidth(width) + valueLabel->heightForWidth(width);
        }

        static void initLabel(QLabel *label)
        {
            label->setWordWrap(true);
            label->show();
            label->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
        }

        QLabel *const keyLabel;
        QLabel *const valueLabel;
    };

public:
    explicit KeyValueWidget(QWidget *parent = nullptr)
        : QWidget(parent)
    {
        QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Fixed);
        policy.setHeightForWidth(true);
        setSizePolicy(policy);
    }
    ~KeyValueWidget() override
    {
        qDeleteAll(mRows);
        mRows.clear();
    }

    QSize sizeHint() const override
    {
        int width = 150;
        int height = heightForWidth(width);
        return QSize(width, height);
    }

    int heightForWidth(int w) const override
    {
        int height = 0;
        for (Row *row : qAsConst(mRows)) {
            height += row->heightForWidth(w);
        }
        return height;
    }

    void clear()
    {
        qDeleteAll(mRows);
        mRows.clear();
        updateGeometry();
    }

    void addRow(const QString &key, const QString &value)
    {
        Row *row = new Row(this);
        row->keyLabel->setText(i18nc("@item:intext %1 is a key, we append a colon to it. A value is displayed after", "%1:", key));
        row->valueLabel->setText(value);
        mRows << row;
    }

    static bool rowsLessThan(const Row *row1, const Row *row2)
    {
        return row1->keyLabel->text() < row2->keyLabel->text();
    }

    void finishAddRows()
    {
        std::sort(mRows.begin(), mRows.end(), KeyValueWidget::rowsLessThan);
        updateGeometry();
    }

    void layoutRows()
    {
        // Layout labels manually: I tried to use a QVBoxLayout but for some
        // reason when using software animations the widget blinks when going
        // from one image to another
        int rowY = 0;
        const int labelWidth = width();
        for (Row *row : qAsConst(mRows)) {
            rowY = row->setLabelGeometries(rowY, labelWidth);
        }
    }

protected:
    void showEvent(QShowEvent *event) override
    {
        QWidget::showEvent(event);
        layoutRows();
    }

    void resizeEvent(QResizeEvent *event) override
    {
        QWidget::resizeEvent(event);
        layoutRows();
    }

private:
    QVector<Row *> mRows;
};

struct InfoContextManagerItemPrivate {
    InfoContextManagerItem *q;
    SideBarGroup *mGroup;

    // One selection fields
    QScrollArea *mOneFileWidget;
    KeyValueWidget *mKeyValueWidget;
    Document::Ptr mDocument;

    // Multiple selection fields
    QLabel *mMultipleFilesLabel;

    QPointer<ImageMetaInfoDialog> mImageMetaInfoDialog;

    void updateMetaInfoDialog()
    {
        if (!mImageMetaInfoDialog) {
            return;
        }
        ImageMetaInfoModel *model = mDocument ? mDocument->metaInfo() : nullptr;
        mImageMetaInfoDialog->setMetaInfo(model, GwenviewConfig::preferredMetaInfoKeyList());
    }

    void setupGroup()
    {
        mOneFileWidget = new QScrollArea();
        mOneFileWidget->setFrameStyle(QFrame::NoFrame);
        mOneFileWidget->setWidgetResizable(true);

        mKeyValueWidget = new KeyValueWidget;

        auto moreLabel = new QLabel(mOneFileWidget);
        moreLabel->setText(QStringLiteral("<a href='#'>%1</a>").arg(i18nc("@action show more image meta info", "Show more details...")));
        // for some reason, this label appears much further down the page without the following line
        moreLabel->setAlignment(Qt::AlignLeft);

        auto content = new QWidget;
        auto layout = new QVBoxLayout(content);
        layout->setContentsMargins(0, 0, 0, 0);
        layout->addWidget(mKeyValueWidget);
        layout->addWidget(moreLabel);

        mOneFileWidget->setWidget(content);

        mMultipleFilesLabel = new QLabel();

        mGroup = new SideBarGroup(i18nc("@title:group", "Image Information"));
        q->setWidget(mGroup);
        mGroup->addWidget(mOneFileWidget);
        mGroup->addWidget(mMultipleFilesLabel);

        EventWatcher::install(mGroup, QEvent::Show, q, SLOT(updateSideBarContent()));

        QObject::connect(moreLabel, &QLabel::linkActivated, q, &InfoContextManagerItem::showMetaInfoDialog);
    }

    void forgetCurrentDocument()
    {
        if (mDocument) {
            QObject::disconnect(mDocument.data(), nullptr, q, nullptr);
            // "Garbage collect" document
            mDocument = nullptr;
        }
    }
};

InfoContextManagerItem::InfoContextManagerItem(ContextManager *manager)
    : AbstractContextManagerItem(manager)
    , d(new InfoContextManagerItemPrivate)
{
    d->q = this;
    d->setupGroup();
    connect(contextManager(), &ContextManager::selectionChanged, this, &InfoContextManagerItem::updateSideBarContent);
    connect(contextManager(), &ContextManager::selectionDataChanged, this, &InfoContextManagerItem::updateSideBarContent);
}

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

void InfoContextManagerItem::updateSideBarContent()
{
    LOG("updateSideBarContent");
    if (!d->mGroup->isVisible()) {
        LOG("updateSideBarContent: not visible, not updating");
        return;
    }
    LOG("updateSideBarContent: really updating");

    KFileItemList itemList = contextManager()->selectedFileItemList();
    if (itemList.isEmpty()) {
        d->forgetCurrentDocument();
        d->mOneFileWidget->hide();
        d->mMultipleFilesLabel->hide();
        d->updateMetaInfoDialog();
        return;
    }

    KFileItem item = itemList.first();
    if (itemList.count() == 1 && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
        fillOneFileGroup(item);
    } else {
        fillMultipleItemsGroup(itemList);
    }
    d->updateMetaInfoDialog();
}

void InfoContextManagerItem::fillOneFileGroup(const KFileItem &item)
{
    d->mOneFileWidget->show();
    d->mMultipleFilesLabel->hide();

    d->forgetCurrentDocument();
    d->mDocument = DocumentFactory::instance()->load(item.url());
    connect(d->mDocument.data(), &Document::metaInfoUpdated, this, &InfoContextManagerItem::updateOneFileInfo);

    d->updateMetaInfoDialog();
    updateOneFileInfo();
}

void InfoContextManagerItem::fillMultipleItemsGroup(const KFileItemList &itemList)
{
    d->forgetCurrentDocument();

    int folderCount = 0, fileCount = 0;
    for (const KFileItem &item : itemList) {
        if (item.isDir()) {
            folderCount++;
        } else {
            fileCount++;
        }
    }

    if (folderCount == 0) {
        d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 file selected", "%1 files selected", fileCount));
    } else if (fileCount == 0) {
        d->mMultipleFilesLabel->setText(i18ncp("@label", "%1 folder selected", "%1 folders selected", folderCount));
    } else {
        d->mMultipleFilesLabel->setText(i18nc("@label. The two parameters are strings like '2 folders' and '1 file'.",
                                              "%1 and %2 selected",
                                              i18np("%1 folder", "%1 folders", folderCount),
                                              i18np("%1 file", "%1 files", fileCount)));
    }
    d->mOneFileWidget->hide();
    d->mMultipleFilesLabel->show();
}

void InfoContextManagerItem::updateOneFileInfo()
{
    if (!d->mDocument) {
        return;
    }

    ImageMetaInfoModel *metaInfoModel = d->mDocument->metaInfo();
    d->mKeyValueWidget->clear();
    const QStringList preferredMetaInfoKeyList = GwenviewConfig::preferredMetaInfoKeyList();
    for (const QString &key : preferredMetaInfoKeyList) {
        QString label;
        QString value;
        metaInfoModel->getInfoForKey(key, &label, &value);
        if (!label.isEmpty() && !value.isEmpty()) {
            d->mKeyValueWidget->addRow(label, value);
        }
    }
    d->mKeyValueWidget->finishAddRows();
    d->mKeyValueWidget->layoutRows();
}

void InfoContextManagerItem::showMetaInfoDialog()
{
    if (!d->mImageMetaInfoDialog) {
        d->mImageMetaInfoDialog = new ImageMetaInfoDialog(d->mOneFileWidget);
        d->mImageMetaInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true);
        connect(d->mImageMetaInfoDialog.data(),
                &ImageMetaInfoDialog::preferredMetaInfoKeyListChanged,
                this,
                &InfoContextManagerItem::slotPreferredMetaInfoKeyListChanged);
    }
    d->mImageMetaInfoDialog->setMetaInfo(d->mDocument ? d->mDocument->metaInfo() : nullptr, GwenviewConfig::preferredMetaInfoKeyList());
    d->mImageMetaInfoDialog->show();
}

void InfoContextManagerItem::slotPreferredMetaInfoKeyListChanged(const QStringList &list)
{
    GwenviewConfig::setPreferredMetaInfoKeyList(list);
    GwenviewConfig::self()->save();
    updateOneFileInfo();
}

} // namespace
