/*
 * 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 "subwindow/subwindow.h"
#include "webengine/urlinterceptor.h"
#include <QAction>
#include <QDir>
#include <QFileInfo>
#include <QFileInfoList>
#include <QJsonArray>
#include <QPluginLoader>
#include <about/aboutdialog.h>
#include <bookmarks/bookmarkswidget.h>
#include <configuration/configuration.h>
#include <downloads/downloadswidget.h>
#include <version.h>
#include <web/webprofile.h>
#include <web/profilemanager.h>
#include <QJsonDocument>
#include <QTimer>

Browser::Browser(int &argc, char *argv[], bool allowSecondary)
    : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion)
{
    setApplicationName(POI_NAME);
    setWindowIcon(QIcon(":/icon.svg"));
    setApplicationVersion(SMOLBOTE_VERSION);
}

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

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

void Browser::about()
{
    auto *dlg = new AboutDialog;
    for(const Plugin &plugin : qAsConst(m_plugins)) {
        dlg->addPlugin(plugin.name, plugin.author, plugin.shortcut.toString());
    }
    dlg->exec();
}

QPair<QString, WebProfile *> Browser::loadProfile(const QString &id)
{
    WebProfile *profile = profileManager->loadProfile(id);
    connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload);
    profile->setRequestInterceptor(m_urlFilter.get());

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

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 ProfileManager::instance();
}

void Browser::registerPlugin(const Plugin &plugin)
{
    Q_ASSERT(m_config);


    auto *p = qobject_cast<PluginInterface *>(plugin.instance);
    Q_CHECK_PTR(p);
    p->setBrowserInterface(this);

    m_plugins.append(plugin);
}

void Browser::setup(const QString &defaultProfile)
{
    Q_ASSERT(m_config);

    ProfileManager::setInstance(new ProfileManager(m_config->section("profile"), this));

    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<UrlRequestInterceptor>(m_config);
    // cookie request filter

    // load profiles
    {
        const auto defaults = m_config->section("profile");
        const QDir profilesDir(m_config->value<QString>("profile.path").value());

        if(profilesDir.exists()) {
            const auto entries = profilesDir.entryInfoList({ "*.profile" }, QDir::Files | QDir::Readable, QDir::Time);

            for(const QFileInfo &f : entries) {
                loadProfile(f.absoluteFilePath());
            }
        }

        // set default profile
        if(profileManager->profile(defaultProfile) == nullptr) {
            // if this profile has not been added, it doesn't have a path
            loadProfile(QString());
        }
        WebProfile::setDefaultProfile(profileManager->profile(defaultProfile));
    }

    // 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::createSession(const QJsonObject &object)
{
    MainWindow *mainwindow = nullptr;
    if(m_windows.isEmpty())
        mainwindow = createWindow();
    else
        mainwindow = m_windows.last();

    const QJsonArray subwindows = object.value("subwindows").toArray();

    for(const QJsonValue &s : subwindows) {
        const QJsonObject subwindow = s.toObject();
        const QString profileId = subwindow.value("profile").toString();
        WebProfile *profile = profileManager->profile(profileId);
        if(profile == nullptr)
            profile = WebProfile::defaultProfile();
        Q_CHECK_PTR(profile);

        SubWindow *window = nullptr;
        for(SubWindow *w : mainwindow->subWindows()) {
            if(w->profile() == profile) {
                window = w;
                break;
            }
        }
        if(window == nullptr)
            window = mainwindow->createSubWindow(m_config, profile);

        const QJsonArray tabs = subwindow.value("tabs").toArray();
        if(tabs.isEmpty())
            window->addTab(profile->newtab());
        else {
            for(const QJsonValue &t : subwindow.value("tabs").toArray()) {
                const QJsonObject tab = t.toObject();
                const QUrl url = QUrl::fromUserInput(tab.value("url").toString());
                WebProfile *p = profileManager->profile(tab.value("profile").toString());
                window->addTab(url, p);
            }
        }
    }
}

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);
    connect(window, &MainWindow::createBookmark, m_bookmarks.get(), &BookmarksWidget::addBookmark);

    auto *bookmarksAction = new QAction(tr("Bookmarks"), window);
    m_config->setShortcut(bookmarksAction, "bookmarks.shortcut");
    connect(bookmarksAction, &QAction::triggered, window, [this, window]() {
        bool wasVisible = m_bookmarks->isVisible();
        for(MainWindow *w : qAsConst(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);
    m_config->setShortcut(downloadsAction, "downloads.shortcut");
    connect(downloadsAction, &QAction::triggered, window, [this, window]() {
        bool wasVisible = m_downloads->isVisible();
        for(MainWindow *w : qAsConst(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)) {
        auto *plugin = qobject_cast<PluginInterface *>(p.instance);
        Q_CHECK_PTR(plugin);

        auto *pluginAction = new QAction(p.name, window);
        pluginAction->setShortcut(p.shortcut);

        connect(pluginAction, &QAction::triggered, window, [=]() {
            plugin->createWidget(window)->exec();
        });
        window->addAction(MainWindow::ToolsMenu, pluginAction);

    }

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

    return window;
}