/*
 * This file is part of smolbote. It's copyrighted by the contributors recorded
 * in the version control history of the file, available from its original
 * location: https://neueland.iserlohn-fortress.net/gitea/aqua/smolbote
 *
 * SPDX-License-Identifier: GPL-3.0
 */

#include "browser.h"
#include "aboutdialog.h"
#include "aboutplugin.h"
#include "addressbar.h"
#include "bookmarkswidget.h"
#include "config.h"
#include "configuration.h"
#include "downloadswidget.h"
#include "mainwindow/mainwindow.h"
#include "profilemanager.h"
#include "subwindow/subwindow.h"
#include "util.h"
#include "webengine/filter.h"
#include "webengine/urlinterceptor.h"
#include "webprofile.h"
#include <QAction>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QFileInfoList>
#include <QJsonArray>
#include <QJsonDocument>
#include <QPluginLoader>
#include <QTimer>
#include <QVersionNumber>
#include <plugininterface.h>
#include <version.h>
#include "mainwindow/menubar.h"

Browser::Browser(int &argc, char *argv[], bool allowSecondary)
    : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion)
{
    setApplicationName(CONFIG_POI_NAME);
    setWindowIcon(QIcon(CONFIG_POI_ICON));
    setApplicationVersion(QVersionNumber::fromString(QLatin1String(poi_Version)).toString());
}

Browser::~Browser()
{
    if(m_bookmarks)
        m_bookmarks->save();

    for(auto *info : m_plugins)
        delete info;

    qDeleteAll(m_windows);
    m_windows.clear();
}

void Browser::about()
{
    auto *dlg = new AboutDialog;
    dlg->exec();
}

const QStringList Browser::configurationOptions() const
{
    QStringList options;
    for(const auto &option : m_config->description().options()) {
        options.append(QString::fromStdString(option->long_name()));
    }
    return options;
}

const QString Browser::configuration(const QString &key) const
{
    return m_config->value<QString>(qUtf8Printable(key)).value_or(QString());
}

void Browser::setConfiguration(const QString &key, const QString &value)
{
    m_config->setValue(qUtf8Printable(key), value);
}

const QList<QPair<QString, Profile *>> Browser::profileList() const
{
    QList<QPair<QString, Profile *>> profiles;
    for(const QString &id : m_profileManager->idList()) {
        profiles.append(qMakePair(id, m_profileManager->profile(id)));
    }
    return profiles;
}

QPair<QString, Profile *> Browser::loadProfile(const QString &id, bool isOffTheRecord)
{
    WebProfile *profile = nullptr;
    if(QFile::exists(id)) {
        profile = m_profileManager->loadProfile(id);
    } else {
        profile = m_profileManager->createProfile(id, isOffTheRecord);
    }
    connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload);
    auto *interceptor = new UrlRequestInterceptor(m_urlFilter.get(), profile, profile);
    profile->setRequestInterceptor(interceptor);

    return QPair<QString, WebProfile *>(m_profileManager->id(profile), profile);
}

void Browser::removeProfile(const QString &id)
{
    m_profileManager->deleteProfile(id);
}

void Browser::setConfiguration(std::unique_ptr<Configuration> &config)
{
    Q_ASSERT(config);
    m_config = std::move(config);
}

Configuration *Browser::getConfiguration() const
{
    Q_ASSERT(m_config);
    return m_config.get();
}

ProfileManager *Browser::getProfileManager()
{
    return m_profileManager;
}

QPluginLoader *Browser::addPlugin(const QString &path)
{
    if(path.isEmpty())
        return nullptr;

    auto *loader = new QPluginLoader(path, this);
    loader->load();

    auto *info = new PluginInfo(loader);
    m_plugins.append(info);

    for(MainWindow *window : m_windows) {
        addPluginTo(info, window);
    }

    return loader;
}

void Browser::setup(QVector<QPluginLoader *> plugins)
{
    Q_ASSERT(m_config);
    for(QPluginLoader *loader : plugins) {
        m_plugins.append(new PluginInfo(loader));
    }

    auto stylesheet = m_config->value<QString>("browser.stylesheet");
    if(stylesheet) {
        QFile f(stylesheet.value());
        if(f.open(QIODevice::ReadOnly)) {
            setStyleSheet(f.readAll());
            f.close();
        }
    }

    // downloads
    m_downloads = std::make_unique<DownloadsWidget>(m_config->value<QString>("downloads.path").value());
    // url request filter
    m_urlFilter = std::make_unique<Filter>(m_config);
    // cookie request filter

    // load profiles
    m_profileManager = new ProfileManager(m_config->section("profile"), this);
    for(const QString &profilePath : Util::files(m_config->value<QString>("profile.path").value(), { "*.profile" })) {
        this->loadProfile(profilePath);
    }

    // set default profile
    {
        const QString id = m_config->value<QString>("profile.default").value();
        auto *profile = m_profileManager->profile(id);
        if(profile == nullptr) {
            profile = qobject_cast<WebProfile *>(loadProfile(id).second);
        }

        WebProfile::setDefaultProfile(profile);
    }

    // bookmarks
    m_bookmarks = std::make_shared<BookmarksWidget>(QString::fromStdString(m_config->value<std::string>("bookmarks.path").value()));
    connect(m_bookmarks.get(), &BookmarksWidget::openUrl, this, [this](const QUrl &url) {
        m_windows.last()->createTab(url);
    });

    auto *timer = new QTimer(this);
    connect(timer, &QTimer::timeout, m_bookmarks.get(), &BookmarksWidget::save);
    // 5min * 60sec * 1000ms
    timer->start(5 * 60 * 1000);
}

void Browser::showWidget(QWidget *widget, MainWindow *where) const
{
    bool wasVisible = widget->isVisible();
    for(MainWindow *w : qAsConst(m_windows))
        w->removeDockWidget(widget);

    if(!wasVisible)
        where->addDockWidget(Qt::RightDockWidgetArea, widget);
}

MainWindow *Browser::createWindow()
{
    // the window will delete itself when it closes, so we don't need to delete it
    auto *window = new MainWindow(m_config);
    connect(window->addressBar, &AddressBar::complete, m_bookmarks.get(), &BookmarksWidget::search);

    for(auto *info : m_plugins) {
        addPluginTo(info, window);
    }

    m_windows.append(window);
    connect(window, &MainWindow::destroyed, this, [this, window]() {
        m_windows.removeOne(window);
    });

    return window;
}

void Browser::addPluginTo(PluginInfo *info, MainWindow *window)
{
    QPluginLoader *loader = info->loader;
    auto *pluginMenu = new QMenu(loader->metaData().value("MetaData").toObject().value("name").toString());
    window->m_menuBar->insertPlugin(pluginMenu);
    info->menus.append(pluginMenu);

    auto *aboutAction = pluginMenu->addAction(tr("About"));
    connect(aboutAction, &QAction::triggered, this, [loader, window]() {
        auto *dlg = new AboutPluginDialog(loader, window);
        dlg->exec();
    });

    auto *runAction = pluginMenu->addAction(tr("Run"));
    runAction->setShortcut(QKeySequence::fromString(loader->metaData().value("MetaData").toObject().value("shortcut").toString()));

    connect(runAction, &QAction::triggered, window, [loader, window]() {
        if(loader->isLoaded()) {
            const auto *interface = qobject_cast<PluginInterface *>(loader->instance());
            Q_CHECK_PTR(interface);
            interface->createWidget(window)->exec();
        }
    });

    auto *removeAction = pluginMenu->addAction(tr("Remove"));
    connect(removeAction, &QAction::triggered, this, [this, info]() {;
        m_plugins.removeOne(info);
        delete info;
    });
}