/*
 * Copyright 2015 Luca Beltrame <lbeltrame@kde.org>
 *
 * This file is part of Danbooru Client.
 *
 * Danbooru Client 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 3 of the License, or
 * (at your option) any later version.
 *
 * Danbooru Client 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 Danbooru Client. If not, see <http://www.gnu.org/licenses/>.
 */

// Qt

#include <QAction>
#include <QQuickWidget>
#include <QQmlContext>
#include <QApplication>
#include <QGuiApplication>
#include <QStandardPaths>
#include <QUrl>
#include "danbooru_client_debug.h"
#include <QDockWidget>
#include <QQuickItem>
#include <QFileDialog>
#include <QPointer>
#include <QSortFilterProxyModel>
#include <QStatusBar>

// KF5

#include <kactioncollection.h>
#include <KStandardAction>
#include <KLocalizedString>
#include <KDeclarative/KDeclarative>
#include <KConfigDialog>
#include <KToggleAction>
#include <KDualAction>
#include <KIO/FileCopyJob>
#include <KIO/MimetypeJob>
#include <KIOFileWidgets/KFileWidget>
#include <KIOFileWidgets/KRecentDirs>

#ifdef WITH_KFILEMETADATA
#include <KFileMetaData/kfilemetadata/usermetadata.h>
#endif

// Own

#include "libdanbooru/danbooruservice.h"
#include "libdanbooru/danboorupost.h"
#include "libdanbooru/danboorupool.h"
#include "model/danboorupostmodel.h"
#include "model/danboorupoolmodel.h"
#include "model/danboorutagmodel.h"
#include "mainwindow.h"
#include "danbooruconnectwidget.h"
#include "danboorusearchwidget.h"
#include "danboorutagwidget.h"
#include "danboorusettings.h"
#include "generalpage.h"
#include "blacklistpage.h"

namespace Danbooru
{

QHash<int, DanbooruPost::Rating> DanbooruMainWindow::ratingMap = {
    {0, DanbooruPost::Safe},
    {1, DanbooruPost::Questionable},
    {2, DanbooruPost::Explicit}
};

DanbooruMainWindow::DanbooruMainWindow(QWidget *parent)
    : KXmlGuiWindow(parent),
      m_view(new QQuickWidget(this)),
      m_model(new DanbooruPostModel(this)),
      m_poolModel(new DanbooruPoolModel(this)),
      m_tagModel(new DanbooruTagModel(this)),
      m_service(new DanbooruService()),
      m_connectWidget(Q_NULLPTR),
      m_searchWidget(new DanbooruSearchWidget(this)),
      m_tagWidget(new DanbooruTagWidget(this)),
      m_proxyModel(new QSortFilterProxyModel(this)),
      m_tableView(new QTableView(this)),
      m_cache(Q_NULLPTR)
{

    m_service->setParent(this);
    setCentralWidget(m_view);

    qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);

    qmlRegisterType<Danbooru::DanbooruPost>("DanbooruClient", 1, 0, "DanbooruPost");
    qmlRegisterType<Danbooru::DanbooruService>("DanbooruClient", 1, 0, "DanbooruService");
    qRegisterMetaType<DanbooruPost::Rating>();

    loadSettings();

    m_cache = new KImageCache(qApp->applicationName(), DanbooruSettings::self()->cacheSize());
    m_service->setImageCache(m_cache);

    m_proxyModel->setSortRole(DanbooruTagModel::TagCountRole);
    m_proxyModel->setSourceModel(m_tagModel);
    m_proxyModel->setDynamicSortFilter(true);

    m_tableView->setModel(m_poolModel);
    m_tagWidget->setModel(m_proxyModel);

    // Set up declarative bindings for the QQuickWidget

    m_view->setResizeMode(QQuickWidget::SizeRootObjectToView);
    KDeclarative::KDeclarative declarative;
    declarative.setDeclarativeEngine(m_view->engine());
    declarative.setupBindings();

    auto qmlViewPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
                       qApp->applicationName() +                                             QLatin1String("/danbooruimageview.qml"));

    QQmlContext *ctxt = m_view->rootContext();
    ctxt->setContextProperty("danbooruModel", m_model);
    ctxt->setContextProperty("danbooruService", m_service);
    m_view->setSource(QUrl::fromLocalFile(qmlViewPath));
    m_view->rootObject()->setProperty("poolMode", QVariant(false));
    ctxt->setContextProperty("infiniteScroll", DanbooruSettings::self()->infiniteScrolling());

    auto rootObj = m_view->rootObject();
    connect(m_service, SIGNAL(postDownloadFinished()), rootObj, SIGNAL(downloadFinished()));

    statusBar()->addPermanentWidget(m_connectWidget);
    statusBar()->hide();

    setupDockWidgets();

    // then, setup our actions
    setupActions();
    setupGUI(KXmlGuiWindow::ToolBar | Keys | Save | Create, "danbooru-clientui.rc");

    // connections

    connect(m_connectWidget, &DanbooruConnectWidget::accepted, [this]() {

        m_service->setBoardUrl(m_connectWidget->boardUrl());
        if (!m_connectWidget->isAnonymous() && !m_connectWidget->username().isEmpty()
                && !m_connectWidget->password().isEmpty()) {

            m_service->setUserName(m_connectWidget->username());
            m_service->setPassword(m_connectWidget->password());
        }

        actionCollection()->action(QLatin1String("fetch"))->setEnabled(true);
        actionCollection()->action(QLatin1String("find"))->setEnabled(true);
        actionCollection()->action(QLatin1String("poolDownload"))->setEnabled(true);
        actionCollection()->action(QLatin1String("tags"))->setEnabled(true);
        actionCollection()->action(QLatin1String("morePosts"))->setEnabled(true);

        if (DanbooruSettings::self()->autoDownload()) {

            handlePostDownload(QStringList(), false);

        }
        m_connectWidget->hide();
        statusBar()->hide();

    });

    connect(m_connectWidget, &DanbooruConnectWidget::rejected, [this]() {
        m_connectWidget->hide();
    });

    connect(m_service, &Danbooru::DanbooruService::postDownloaded, m_model,
            &Danbooru::DanbooruPostModel::addPost);
    connect(m_service, &Danbooru::DanbooruService::poolDownloaded, m_poolModel,
            &DanbooruPoolModel::addPool);
    connect(m_service, &Danbooru::DanbooruService::tagDownloaded, m_tagModel,
            &DanbooruTagModel::addTag);
    connect(m_service, &Danbooru::DanbooruService::poolDownloadFinished, [this]() {
        m_tableView->resizeColumnsToContents();
    }
           );

    connect(m_tableView, &QTableView::doubleClicked, [this](QModelIndex index) {
        auto pool = m_poolModel->poolAt(index.row());
        clearModels();
        m_view->rootObject()->setProperty("poolMode", QVariant(true));
        QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted");
        m_service->getPool(pool->id());

    });

    connect(m_searchWidget, &DanbooruSearchWidget::accepted, [this]() {

        QDockWidget *searchDockWidget = findChild<QDockWidget *>(QLatin1String("SearchView"));
        searchDockWidget->hide();

        handlePostDownload(m_searchWidget->selectedTags(), true /* relatedTags */);

    });

    connect(m_searchWidget, &DanbooruSearchWidget::rejected, [this]() {
        QDockWidget *searchDockWidget = findChild<QDockWidget *>(QLatin1String("SearchView"));
        searchDockWidget->hide();
    });

    connect(m_service, &DanbooruService::postDownloadFinished, [this]() {

        if (m_tagModel->rowCount() == 0) {
            // Only get tags if we don't have any already
            for (auto tag : m_model->postTags()) {
                m_service->getTagList(1, tag);
            }
        }
        m_proxyModel->sort(0, Qt::DescendingOrder);

    });

    connect(m_view->rootObject(), SIGNAL(downloadRequested(QUrl,QVariant)), this,
            SLOT(slotHandleDownload(QUrl,QVariant)));

    connect(m_tagWidget, &QListView::doubleClicked, this, &DanbooruMainWindow::searchTag);

}

DanbooruMainWindow::~DanbooruMainWindow()
{
}

void DanbooruMainWindow::loadSettings()
{
    m_service->setBlacklist(DanbooruSettings::self()->tagBlacklist());
    m_service->setMaxPosts(DanbooruSettings::self()->maxPosts());

    m_service->setMaximumAllowedRating(ratingMap.value(DanbooruSettings::self()->maxRating()));

    m_service->setMaxPosts(DanbooruSettings::self()->maxPosts());

    QVector<QUrl> boardsList;

    QStringList::const_iterator it;

    for (it = DanbooruSettings::self()->boards().constBegin();
            it != DanbooruSettings::self()->boards().constEnd();
            ++it) {

        boardsList.append(QUrl::fromUserInput(*it));

    }

    if (!m_connectWidget) {
        m_connectWidget = new DanbooruConnectWidget(boardsList, this);
    } else {
        m_connectWidget->setBoards(boardsList);
    }

    m_tagWidget->setBlackList(DanbooruSettings::self()->tagBlacklist());

    m_view->rootContext()->setContextProperty("infiniteScroll",
            DanbooruSettings::self()->infiniteScrolling());

}

void DanbooruMainWindow::setupActions()
{

    QAction *connectAction = new QAction(
        QIcon::fromTheme(QLatin1String("document-open-remote")),
        i18n("Connect..."),
        this);

    QAction *fetchAction = new QAction(QIcon::fromTheme(QLatin1String("download")),
                                       i18n("Download"), this);
    KToggleAction *findAction = new KToggleAction(QIcon::fromTheme(QLatin1String("edit-find")),
            i18n("Search"), this);
    KToggleAction *poolAction = new KToggleAction(QIcon::fromTheme(QLatin1String("image-x-generic")),
            i18n("Pools"), this);
    QAction *nextPageAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")),
                                          i18n("More posts"), this);
    QAction *nextPoolAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")),
                                          i18n("More pools"), this);

    KDualAction *tagAction = new KDualAction(i18n("Show tags"), i18n("Hide tags"), this);
    tagAction->setIconForStates(QIcon::fromTheme(QLatin1String("tag")));

    fetchAction->setEnabled(false);
    findAction->setEnabled(false);
    poolAction->setEnabled(false);
    nextPageAction->setEnabled(false);
    nextPoolAction->setEnabled(false);

    poolAction->setChecked(false);
    findAction->setChecked(false);
    tagAction->setEnabled(false);

    actionCollection()->addAction(QLatin1String("connect"), connectAction);
    actionCollection()->addAction(QLatin1String("fetch"), fetchAction);
    actionCollection()->addAction(QLatin1String("find"), findAction);
    actionCollection()->addAction(QLatin1String("poolDownload"), poolAction);
    actionCollection()->addAction(QLatin1String("tags"), tagAction);
    actionCollection()->addAction(QLatin1String("morePosts"), nextPageAction);
    actionCollection()->addAction(QLatin1String("morePools"), nextPoolAction);

    actionCollection()->setDefaultShortcut(connectAction, KStandardShortcut::Open);
    actionCollection()->setDefaultShortcut(findAction, KStandardShortcut::Find);

    KStandardAction::quit(qApp, SLOT(quit()), actionCollection());
    KStandardAction::preferences(this, SLOT(optionsPreferences()), actionCollection());

    connect(connectAction, &QAction::triggered, this, &DanbooruMainWindow::connectToBoard);
    connect(fetchAction, &QAction::triggered, this, &DanbooruMainWindow::downloadPosts);

    connect(poolAction, &KToggleAction::toggled, [this](bool checked) {
        if (!m_service) {
            return;
        }

        QDockWidget *poolDockWidget = findChild<QDockWidget *>(QLatin1String("PoolView"));

        if (checked) {

            if (m_poolModel->rowCount() == 0) {
                m_service->getPoolList();

            }

            poolDockWidget->show();
            actionCollection()->action(QLatin1String("morePools"))->setEnabled(true);
            m_tableView->show();

        } else {
            poolDockWidget->hide();
            actionCollection()->action(QLatin1String("morePools"))->setEnabled(false);
            m_tableView->hide();
        }
    });

    connect(findAction, &KToggleAction::toggled, [this](bool checked) {

        QDockWidget *searchDockWidget = findChild<QDockWidget *>(QLatin1String("SearchView"));

        if (checked) {
            searchDockWidget->show();
            m_searchWidget->show();
        } else {
            searchDockWidget->hide();
            m_searchWidget->hide();
        }

    });

    connect(tagAction, &KDualAction::activeChanged, [this](bool checked) {

        QDockWidget *tagDockWidget = findChild<QDockWidget *>(QLatin1String("TagView"));

        if (checked) {
            tagDockWidget->show();
            m_tagWidget->show();
        } else {
            tagDockWidget->hide();
            m_tagWidget->hide();
        }

    });

    connect(nextPageAction, &QAction::triggered, [this]() {

        if (m_model->rowCount() == 0) {
            return;
        }

        QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted");
        m_service->nextPostPage();
    }
           );

    connect(nextPoolAction, &QAction::triggered, [this]() {
        m_service->nextPoolPage();
    }
           );
}

void DanbooruMainWindow::setupDockWidgets()
{

    // Set up PoolWidget

    QDockWidget *poolDockWidget = new QDockWidget(i18n("Pools"), this);
    poolDockWidget->setAllowedAreas(Qt::BottomDockWidgetArea);
    poolDockWidget->setWidget(m_tableView);
    poolDockWidget->setObjectName("PoolView");

    // Prevent the use of winId() when detached, leads to QQuickWidget bugs
    poolDockWidget->setFeatures(QDockWidget::DockWidgetClosable);
    addDockWidget(Qt::BottomDockWidgetArea, poolDockWidget);
    m_tableView->hide();
    poolDockWidget->hide();

    // Find widget
    QDockWidget *searchDockWidget = new QDockWidget(QLatin1String(""), this);
    searchDockWidget->setAllowedAreas(Qt::TopDockWidgetArea);
    searchDockWidget->setWidget(m_searchWidget);
    searchDockWidget->setObjectName("SearchView");

    searchDockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures);
    addDockWidget(Qt::TopDockWidgetArea, searchDockWidget);
    // No title wanted
    searchDockWidget->setTitleBarWidget(new QWidget(this));

    searchDockWidget->hide();
    m_searchWidget->hide();

    QDockWidget *tagDockWidget = new QDockWidget(QLatin1String("Tags"), this);
    tagDockWidget->setAllowedAreas(Qt::RightDockWidgetArea);
    tagDockWidget->setWidget(m_tagWidget);
    tagDockWidget->setObjectName("TagView");
    tagDockWidget->setFeatures(QDockWidget::DockWidgetClosable);
    addDockWidget(Qt::RightDockWidgetArea, tagDockWidget);

//     tagDockWidget->hide();
//     m_tagWidget->hide();

    // Connections

    connect(poolDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) {
        actionCollection()->action(QLatin1String("poolDownload"))->setChecked(visible);
    });

    connect(searchDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) {
        actionCollection()->action(QLatin1String("find"))->setChecked(visible);
    });

    connect(tagDockWidget, &QDockWidget::visibilityChanged, [this](bool visible) {
        qobject_cast<KDualAction *>(actionCollection()->action(QLatin1String("tags")))->setActive(visible);
    });

}

void DanbooruMainWindow::connectToBoard()
{

    if (!m_view) {
        return;
    }

    clearModels();
    m_poolModel->clear();
    m_service->reset();
    statusBar()->show();
    m_connectWidget->show();

}

void DanbooruMainWindow::downloadPosts()
{
    if (!m_service) {
        return;
    }

    handlePostDownload(QStringList(), false);

}

void DanbooruMainWindow::optionsPreferences()
{
    KConfigDialog *dialog = new KConfigDialog(this, "danboorusettings",
            DanbooruSettings::self());

    dialog->addPage(new GeneralPage(DanbooruSettings::self(), this), i18n("General"),
                    "table");
    dialog->addPage(new BlacklistPage(DanbooruSettings::self(), this), i18n("Tag blacklist"),
                    "configure");
    connect(dialog, &KConfigDialog::settingsChanged, this, &DanbooruMainWindow::loadSettings);
    dialog->show();
}

void DanbooruMainWindow::slotHandleDownload(const QUrl &url, const QVariant tags)
{

    QStringList tagList = tags.toStringList();


    KIO::MimetypeJob *mimeJob = KIO::mimetype(url, KIO::HideProgressInfo);

    QString remoteFile = url.fileName();

    QPointer<QFileDialog> saveDialog = new QFileDialog(this, i18n("Save image"));
    QString recentClass;

    saveDialog->setAcceptMode(QFileDialog::AcceptSave);
    saveDialog->setFileMode(QFileDialog::AnyFile);
    saveDialog->setDirectoryUrl(KFileWidget::getStartUrl(
        QUrl(QStringLiteral("kfiledialog:///danbooru-client")).toLocalFile(),
                                                         recentClass));

    saveDialog->setOption(QFileDialog::DontConfirmOverwrite, false);

    QStringList filters;

    if (mimeJob->exec()) {
        filters << mimeJob->mimetype();
        saveDialog->setMimeTypeFilters(filters);
    }  else {
        filters.reserve(2);
        filters << "Images (*.png *.gif *.jpg)" << "All files (*.*)";
        saveDialog->setNameFilters(filters);
    }

    // Prevent invalid characters (":" can be a tag in Danbooru)
    if (remoteFile.contains(":")) {
        remoteFile.replace(":", "_");
    }

    saveDialog->selectFile(remoteFile);
    saveDialog->open();

    connect(saveDialog, &QFileDialog::finished, [this, tagList, saveDialog, url, recentClass](int result) {

       if (result) {

        // TODO: KRecentDirs

        QUrl localFile = saveDialog->selectedUrls().at(0);

            if (!recentClass.isEmpty()) {
                KRecentDirs::add(recentClass, QFileInfo(localFile.toLocalFile()).absoluteFilePath());
            }

        if (!localFile.isEmpty()) {

            KIO::FileCopyJob *job = KIO::file_copy(url, localFile, -1, KIO::DefaultFlags);

            connect(job, &KIO::Job::result, [this, localFile, tagList](KJob  * job) {

                if (job->error()) {
                    qCDebug(DANBOORU_CLIENT) << "Error while downloading " << job->errorString();
                    return;
                }

#ifdef WITH_KFILEMETADATA
//             qCDebug(DANBOORU_CLIENT) << "Local file" << localFile.toLocalFile();
            KFileMetaData::UserMetaData meta(localFile.toLocalFile());
            meta.setTags(tagList);
#endif
            });

        }
       }
       saveDialog->deleteLater();
    });

}

void DanbooruMainWindow::searchTag(const QModelIndex &index)
{

    if (!index.isValid()) {
        return;
    }

    QString tagName = m_tagModel->itemAt(index.row())->name();
    QStringList tags = {tagName};

    handlePostDownload(tags, true /* relatedTags */);

}

void DanbooruMainWindow::clearModels()
{
    m_model->clear();
    m_tagModel->clear();
//     m_poolModel->clear();
    m_service->reset();

}

void DanbooruMainWindow::handlePostDownload(const QStringList &tags, bool relatedTags)
{
    clearModels();
    m_view->rootObject()->setProperty("poolMode", QVariant(false));
    m_service->setPostTags(tags);

    if (relatedTags) {
        m_service->getRelatedTags(tags);
    }

    // Call the downloadStarted signal from QML
    QMetaObject::invokeMethod(m_view->rootObject(), "downloadStarted");
    m_service->getPostList();
}

} // namespace Danbooru
