/*
 * 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/smolbote.hg
 *
 * SPDX-License-Identifier: GPL-3.0
 */

#include "browser.h"
#include "addressbar/addressbar.h"
#include "mainwindow/mainwindow.h"
#include "mainwindow/subwindow.h"
#include "webengine/urlinterceptor.h"
#include "webengine/webprofile.h"
#include <QAction>
#include <bookmarks/bookmarkswidget.h>
#include <configuration/configuration.h>
#include <downloads/downloadswidget.h>
#include <version.h>
#include <QDir>
#include <QFileInfo>
#include <QFileInfoList>
#include <QPluginLoader>

inline QVector<Plugin> loadPlugins(const QString &location)
{
    QDir pluginsDir(location);
    QVector<Plugin> list;

    if(pluginsDir.exists()) {
        const QStringList entries = pluginsDir.entryList(QDir::Files | QDir::Readable);

        for(const QString &name : entries) {
            QPluginLoader loader(pluginsDir.absoluteFilePath(name));

            if(loader.load()) {
#ifdef QT_DEBUG
                qDebug("Loading plugin: %s [ok]", qUtf8Printable(pluginsDir.absoluteFilePath(name)));
#endif
                Plugin p;
                p.instance = std::shared_ptr<QObject>(loader.instance());
                list.append(p);
            } else {
#ifdef QT_DEBUG
                qDebug("Loading plugin: %s [failed]", qUtf8Printable(name));
#endif
            }
        }
    }

    return list;
}

inline QHash<QString, WebProfile *> loadProfiles(const QHash<QString, QString> &defaults, const QString &location)
{
    QDir profilesDir(location);
    QHash<QString, WebProfile *> list;

    if(profilesDir.exists()) {
        const QFileInfoList entries = profilesDir.entryInfoList({ "*.profile" }, QDir::Files | QDir::Readable);
        for(const auto &entry : entries) {
            auto *profile = new WebProfile(entry.baseName());
            loadProfile(profile, defaults, entry.absoluteFilePath());
            list.insert(entry.baseName(), profile);
        }
    }

    return list;
}

Browser::Browser(int &argc, char *argv[])
    : SingleApplication(argc, argv)
{
    setApplicationName("smolbote");
    setWindowIcon(QIcon(":/icon.svg"));
    setApplicationVersion(SMOLBOTE_VERSION);
}

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

    qDeleteAll(m_windows);
    m_windows.clear();

    //qDeleteAll(m_plugins);
    m_plugins.clear();
    qDeleteAll(m_profiles);
}

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

void Browser::setup(const QString &defaultProfile)
{
    Q_ASSERT_X(m_config, "Browser::setup", "Configuration not set");

    // load profiles
    {
        auto *otr = new WebProfile(this);
        loadProfile(otr, m_config->section("profile"), QString::fromStdString(m_config->value<std::string>("profile.path").value()) + "/otr.ini");
        m_profiles.insert(tr("Off-the-record"), otr);
        m_profiles.unite(loadProfiles(m_config->section("profile"), QString::fromStdString(m_config->value<std::string>("profile.path").value())));

        if(defaultProfile == "") {
            WebProfile::setDefaultProfile(otr);
        } else {
            WebProfile::setDefaultProfile(m_profiles.value(defaultProfile));
        }
    }

    // plugins
    m_plugins.append(loadPlugins(QString::fromStdString(m_config->value<std::string>("plugins.path").value())));

    // register commands
    for(const Plugin &p : qAsConst(m_plugins)) {

        if(p.instance->inherits("ProfileInterface")) {
            auto *profileEditor = qobject_cast<ProfileInterface *>(p.instance.get());
            Q_ASSERT_X(profileEditor != nullptr, "Browser::setup", "profile interface cast failed");

            for(auto i = m_profiles.constBegin(); i != m_profiles.constEnd(); ++i) {
                profileEditor->addProfile(i.key(), qobject_cast<QWebEngineProfile*>(i.value()));
            }
        }

        auto *plugin = qobject_cast<PluginInterface *>(p.instance.get());
        if(plugin) {
            m_commands.unite(plugin->commands());
        }
    }

    // url request filter
    m_urlFilter = std::make_shared<UrlRequestInterceptor>(QString::fromStdString(m_config->value<std::string>("filter.path").value()));
    WebProfile::defaultProfile()->setRequestInterceptor(m_urlFilter.get());

    // cookie request filter

    // 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);
    });
    connect(WebProfile::defaultProfile(), &WebProfile::addBookmarkRequested, m_bookmarks.get(), &BookmarksWidget::addBookmark);

    // downloads
    m_downloads = std::make_shared<DownloadsWidget>(QString::fromStdString(m_config->value<std::string>("downloads.path").value()));
    connect(WebProfile::defaultProfile(), &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload);
}

WebProfile *Browser::profile(const QString &name) const
{
    if(m_profiles.contains(name))
        return m_profiles.value(name);
    else
        return nullptr;
}

int Browser::command(const QString &command)
{
    if(m_commands.contains(command)) {
        return m_commands.value(command)();
    } else {
        qWarning("No such command: %s", qUtf8Printable(command));
        return -1;
    }
}

void Browser::createSession(const QString &profileName, bool newWindow, const QStringList &urls)
{
    if(m_windows.isEmpty()) {
        createWindow();
    }

    auto *mainwindow = m_windows.last();
    if(newWindow) {
        QString firstUrl;// = WebProfile::defaultProfile()->homepage();
        if(!urls.isEmpty())
            firstUrl = urls.at(0);
        auto *w = mainwindow->createSubWindow(firstUrl);
        for(int i = 1; i < urls.count() - 1; i++) {
            w->addTab(QUrl::fromUserInput(urls.at(i)));
        }
    } else {
        if(urls.isEmpty())
            mainwindow->createTab(WebProfile::defaultProfile()->homepage());
        else {
            for(const QString &url : urls) {
                mainwindow->createTab(QUrl::fromUserInput(url));
            }
        }
    }
}

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

    auto *bookmarksAction = new QAction(tr("Bookmarks"), window);
    bookmarksAction->setShortcut(QKeySequence(QString::fromStdString(m_config->value<std::string>("bookmarks.shortcut").value())));
    connect(bookmarksAction, &QAction::triggered, window, [this, window]() {
        bool wasVisible = m_bookmarks->isVisible();
        for(MainWindow *w : m_windows) {
            w->removeDockWidget(m_bookmarks.get());
        }
        if(!wasVisible) {
            window->addDockWidget(Qt::RightDockWidgetArea, m_bookmarks.get());
        }
    });
    window->addAction(MainWindow::ToolsMenu, bookmarksAction);

    auto *downloadsAction = new QAction(tr("Downloads"), window);
    downloadsAction->setShortcut(QKeySequence(QString::fromStdString(m_config->value<std::string>("downloads.shortcut").value())));
    connect(downloadsAction, &QAction::triggered, window, [this, window]() {
        bool wasVisible = m_downloads->isVisible();
        for(MainWindow *w : m_windows) {
            w->removeDockWidget(m_downloads.get());
        }
        if(!wasVisible) {
            window->addDockWidget(Qt::RightDockWidgetArea, m_downloads.get());
        }
    });
    window->addAction(MainWindow::ToolsMenu, downloadsAction);

    for(const Plugin &p : qAsConst(m_plugins)) {
        if(p.instance->inherits("ProfileInterface")) {
            auto *profileEditor = qobject_cast<ProfileInterface *>(p.instance.get());
            auto *profileAction = new QAction(tr("Profile"), window);
            connect(profileAction, &QAction::triggered, window, [profileEditor]() {
                profileEditor->createWidget(nullptr)->show();
            });
            window->addAction(MainWindow::ToolsMenu, profileAction);
        }
    }

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

    return window;
}