/*
 *  Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
 *  Copyright (C) 2017 KeePassXC Team <team@keepassxc.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 or (at your option)
 *  version 3 of the License.
 *
 *  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, see <http://www.gnu.org/licenses/>.
 */

#include "EditWidgetIcons.h"
#include "ui_EditWidgetIcons.h"

#include "core/Clock.h"
#include "core/Config.h"
#include "core/Database.h"
#include "core/Metadata.h"
#include "core/Tools.h"
#include "gui/FileDialog.h"
#include "gui/IconModels.h"
#include "gui/Icons.h"
#include "gui/MessageBox.h"
#ifdef WITH_XC_NETWORKING
#include "gui/IconDownloader.h"
#endif

#include <QKeyEvent>

IconStruct::IconStruct()
    : uuid(QUuid())
    , number(0)
    , applyTo(ApplyIconToOptions::THIS_ONLY)
{
}

EditWidgetIcons::EditWidgetIcons(QWidget* parent)
    : QWidget(parent)
    , m_ui(new Ui::EditWidgetIcons())
    , m_db(nullptr)
    , m_applyIconTo(ApplyIconToOptions::THIS_ONLY)
    , m_defaultIconModel(new DefaultIconModel(this))
    , m_customIconModel(new CustomIconModel(this))
#ifdef WITH_XC_NETWORKING
    , m_downloader(new IconDownloader())
#endif
{
    m_ui->setupUi(this);

    m_ui->defaultIconsView->setModel(m_defaultIconModel);
    m_ui->customIconsView->setModel(m_customIconModel);

    m_ui->applyIconToPushButton->setMenu(createApplyIconToMenu());

    // clang-format off
    connect(m_ui->defaultIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonDefaultIcons()));
    connect(m_ui->customIconsView, SIGNAL(clicked(QModelIndex)), this, SLOT(updateRadioButtonCustomIcons()));
    connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SLOT(updateWidgetsDefaultIcons(bool)));
    connect(m_ui->customIconsRadio, SIGNAL(toggled(bool)), this, SLOT(updateWidgetsCustomIcons(bool)));
    connect(m_ui->addButton, SIGNAL(clicked()), SLOT(addCustomIconFromFile()));
    connect(m_ui->faviconButton, SIGNAL(clicked()), SLOT(downloadFavicon()));
    connect(m_ui->applyIconToPushButton->menu(), SIGNAL(triggered(QAction*)), SLOT(confirmApplyIconTo(QAction*)));

    connect(m_ui->defaultIconsRadio, SIGNAL(toggled(bool)), this, SIGNAL(widgetUpdated()));
    connect(m_ui->defaultIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SIGNAL(widgetUpdated()));
    connect(m_ui->customIconsView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
            this, SIGNAL(widgetUpdated()));
#ifdef WITH_XC_NETWORKING
    connect(m_downloader.data(),
            SIGNAL(finished(const QString&, const QImage&)),
            SLOT(iconReceived(const QString&, const QImage&)));
#endif
    // clang-format on

#ifndef WITH_XC_NETWORKING
    m_ui->faviconButton->setVisible(false);
    m_ui->faviconURL->setVisible(false);
#endif
}

EditWidgetIcons::~EditWidgetIcons() = default;

IconStruct EditWidgetIcons::state()
{
    Q_ASSERT(m_db);
    Q_ASSERT(!m_currentUuid.isNull());

    IconStruct iconStruct;
    if (m_ui->defaultIconsRadio->isChecked()) {
        QModelIndex index = m_ui->defaultIconsView->currentIndex();
        if (index.isValid()) {
            iconStruct.number = index.row();
        } else {
            Q_ASSERT(false);
        }
    } else {
        QModelIndex index = m_ui->customIconsView->currentIndex();
        if (index.isValid()) {
            iconStruct.uuid = m_customIconModel->uuidFromIndex(m_ui->customIconsView->currentIndex());
        } else {
            iconStruct.number = -1;
        }
    }

    iconStruct.applyTo = m_applyIconTo;

    return iconStruct;
}

void EditWidgetIcons::reset()
{
    m_db.reset();
    m_currentUuid = QUuid();
}

void EditWidgetIcons::load(const QUuid& currentUuid,
                           const QSharedPointer<Database>& database,
                           const IconStruct& iconStruct,
                           const QString& url)
{
    Q_ASSERT(database);
    Q_ASSERT(!currentUuid.isNull());

    m_db = database;
    m_currentUuid = currentUuid;
    setUrl(url);

    m_customIconModel->setIcons(Icons::customIconsPixmaps(database.data(), IconSize::Default),
                                database->metadata()->customIconsOrder());

    QUuid iconUuid = iconStruct.uuid;
    if (iconUuid.isNull()) {
        int iconNumber = iconStruct.number;
        m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(iconNumber, 0));
        m_ui->defaultIconsRadio->setChecked(true);
    } else {
        QModelIndex index = m_customIconModel->indexFromUuid(iconUuid);
        if (index.isValid()) {
            m_ui->customIconsView->setCurrentIndex(index);
            m_ui->customIconsRadio->setChecked(true);
        } else {
            m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0));
            m_ui->defaultIconsRadio->setChecked(true);
        }
    }

    m_applyIconTo = ApplyIconToOptions::THIS_ONLY;
    m_ui->applyIconToPushButton->menu()->defaultAction()->activate(QAction::Trigger);
}

void EditWidgetIcons::setShowApplyIconToButton(bool state)
{
    m_ui->applyIconToPushButton->setVisible(state);
}

QMenu* EditWidgetIcons::createApplyIconToMenu()
{
    auto* applyIconToMenu = new QMenu(this);
    QAction* defaultAction = applyIconToMenu->addAction(tr("Apply to this group only"));
    defaultAction->setData(QVariant::fromValue(ApplyIconToOptions::THIS_ONLY));
    applyIconToMenu->setDefaultAction(defaultAction);
    applyIconToMenu->addSeparator();
    applyIconToMenu->addAction(tr("Also apply to child groups"))
        ->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_GROUPS));
    applyIconToMenu->addAction(tr("Also apply to child entries"))
        ->setData(QVariant::fromValue(ApplyIconToOptions::CHILD_ENTRIES));
    applyIconToMenu->addAction(tr("Also apply to all children"))
        ->setData(QVariant::fromValue(ApplyIconToOptions::ALL_CHILDREN));
    return applyIconToMenu;
}

void EditWidgetIcons::keyPressEvent(QKeyEvent* event)
{
    if (m_ui->faviconURL->hasFocus() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) {
        m_ui->faviconButton->animateClick();
    } else {
        QWidget::keyPressEvent(event);
    }
}

void EditWidgetIcons::setUrl(const QString& url)
{
#ifdef WITH_XC_NETWORKING
    QUrl urlCheck(url);
    if (urlCheck.scheme().startsWith("http")) {
        m_ui->faviconURL->setText(urlCheck.url(QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment));
        m_ui->faviconURL->setCursorPosition(0);
    } else {
        m_ui->faviconURL->setText("");
    }
#else
    Q_UNUSED(url);
#endif
}

void EditWidgetIcons::downloadFavicon()
{
#ifdef WITH_XC_NETWORKING
    auto url = m_ui->faviconURL->text();
    if (!url.isEmpty()) {
        m_downloader->setUrl(url);
        m_downloader->download();
    }
#endif
}

void EditWidgetIcons::iconReceived(const QString& url, const QImage& icon)
{
#ifdef WITH_XC_NETWORKING
    Q_UNUSED(url);
    if (icon.isNull()) {
        QString message(tr("Unable to fetch favicon."));
        if (!config()->get(Config::Security_IconDownloadFallback).toBool()) {
            message.append("\n").append(
                tr("You can enable the DuckDuckGo website icon service under Application Settings -> Security"));
        }
        emit messageEditEntry(message, MessageWidget::Error);
        return;
    }

    if (!addCustomIcon(icon)) {
        emit messageEditEntry(tr("Existing icon selected."), MessageWidget::Information);
    }
#else
    Q_UNUSED(url);
    Q_UNUSED(icon);
#endif
}

void EditWidgetIcons::abortRequests()
{
#ifdef WITH_XC_NETWORKING
    if (m_downloader) {
        m_downloader->abortDownload();
    }
#endif
}

void EditWidgetIcons::addCustomIconFromFile()
{
    if (!m_db) {
        return;
    }

    auto filter = QString("%1 (%2);;%3 (*)").arg(tr("Images"), Icons::imageFormatsFilter(), tr("All files"));
    auto filenames =
        fileDialog()->getOpenFileNames(this, tr("Select Image(s)"), FileDialog::getLastDir("icons"), filter);
    if (!filenames.empty()) {
        QStringList errornames;
        int numexisting = 0;
        for (const auto& filename : filenames) {
            if (!filename.isEmpty()) {
                auto icon = QImage(filename);
                if (icon.isNull()) {
                    errornames << filename;
                } else if (!addCustomIcon(icon, QFileInfo(filename).baseName())) {
                    // Icon already exists in database
                    ++numexisting;
                }
            }
        }

        // Save last used directory using first image path
        FileDialog::saveLastDir("icons", filenames[0]);

        int numloaded = filenames.size() - errornames.size() - numexisting;
        QString msg;

        if (numloaded > 0) {
            msg = tr("Successfully loaded %1 of %n icon(s)", "", filenames.size()).arg(numloaded);
        } else {
            msg = tr("No icons were loaded");
        }

        if (numexisting > 0) {
            msg += "\n" + tr("%n icon(s) already exist in the database", "", numexisting);
        }

        if (!errornames.empty()) {
            // Show the first 8 icons that failed to load
            errornames = errornames.mid(0, 8);
            emit messageEditEntry(msg + "\n" + tr("The following icon(s) failed:", "", errornames.size()) + "\n"
                                      + errornames.join("\n"),
                                  MessageWidget::Error);
        } else if (numloaded > 0) {
            emit messageEditEntry(msg, MessageWidget::Positive);
        } else {
            emit messageEditEntry(msg, MessageWidget::Information);
        }
    }
}

bool EditWidgetIcons::addCustomIcon(const QImage& icon, const QString& name)
{
    bool added = false;
    if (m_db) {
        // Don't add an icon larger than 128x128, but retain original size if smaller
        auto scaledIcon = icon;
        if (icon.width() > 128 || icon.height() > 128) {
            scaledIcon = icon.scaled(128, 128);
        }

        QByteArray serializedIcon = Icons::saveToBytes(scaledIcon);
        QUuid uuid = m_db->metadata()->findCustomIcon(serializedIcon);
        if (uuid.isNull()) {
            uuid = QUuid::createUuid();
            m_db->metadata()->addCustomIcon(uuid, serializedIcon, name, Clock::currentDateTimeUtc());
            m_customIconModel->setIcons(Icons::customIconsPixmaps(m_db.data(), IconSize::Default),
                                        m_db->metadata()->customIconsOrder());
            added = true;
        }

        // Select the new or existing icon
        updateRadioButtonCustomIcons();
        QModelIndex index = m_customIconModel->indexFromUuid(uuid);
        m_ui->customIconsView->setCurrentIndex(index);

        emit widgetUpdated();
    }

    return added;
}

void EditWidgetIcons::updateWidgetsDefaultIcons(bool check)
{
    if (check) {
        QModelIndex index = m_ui->defaultIconsView->currentIndex();
        if (!index.isValid()) {
            m_ui->defaultIconsView->setCurrentIndex(m_defaultIconModel->index(0, 0));
        } else {
            m_ui->defaultIconsView->setCurrentIndex(index);
        }
        m_ui->customIconsView->selectionModel()->clearSelection();
    }
}

void EditWidgetIcons::updateWidgetsCustomIcons(bool check)
{
    if (check) {
        QModelIndex index = m_ui->customIconsView->currentIndex();
        if (!index.isValid()) {
            m_ui->customIconsView->setCurrentIndex(m_customIconModel->index(0, 0));
        } else {
            m_ui->customIconsView->setCurrentIndex(index);
        }
        m_ui->defaultIconsView->selectionModel()->clearSelection();
    }
}

void EditWidgetIcons::updateRadioButtonDefaultIcons()
{
    m_ui->defaultIconsRadio->setChecked(true);
}

void EditWidgetIcons::updateRadioButtonCustomIcons()
{
    m_ui->customIconsRadio->setChecked(true);
}

void EditWidgetIcons::confirmApplyIconTo(QAction* action)
{
    m_applyIconTo = action->data().value<ApplyIconToOptions>();
    m_ui->applyIconToPushButton->setText(action->text());
}
