/*
 * 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 "adblock/adblocklist.h"
#include "bookmarks/bookmarkswidget.h"
#include "configuration.h"
#include "downloadswidget.h"
#include "hostlist/hostlist.h"
#include "mainwindow/addressbar.h"
#include "mainwindow/mainwindow.h"
#include "mainwindow/menubar.h"
#include "subwindow/subwindow.h"
#include "urlfilter.h"
#include "util.h"
#include "webengine/urlinterceptor.h"
#include "webengine/webprofile.h"
#include "webengine/webprofilemanager.h"
#include "webengine/webview.h"
#include <QAction>
#include <QDir>
#include <QFileDialog>
#include <QFileInfo>
#include <QFileInfoList>
#include <QJsonArray>
#include <QJsonDocument>
#include <QLibraryInfo>
#include <QPluginLoader>
#include <QTimer>
#include <QTranslator>
#include <QVersionNumber>
#include <plugininterface.h>
#include <pluginloader.h>
#include <spdlog/spdlog.h>
#include <version.h>

Browser::Browser(int &argc, char *argv[], bool allowSecondary)
    : SingleApplication(argc, argv, allowSecondary, SingleApplication::User | SingleApplication::SecondaryNotification | SingleApplication::ExcludeAppVersion)
{
    Configuration conf;

    setApplicationName(conf.value<QString>("poi.name").value());
    setWindowIcon(QIcon(conf.value<QString>("poi.icon").value()));
    setApplicationVersion(QVersionNumber::fromString(QLatin1String(poi_Version)).toString());

    if(const auto _translation = conf.value<QString>("browser.translation")) {
        auto *translator = new QTranslator(this);
        if(translator->load(_translation.value()))
            installTranslator(translator);
        else
            delete translator;
    }

    if(const auto _locale = conf.value<QString>("browser.locale")) {
        auto *locale = new QTranslator(this);
        if(locale->load("qt_" + _locale.value(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)))
            installTranslator(locale);
        else
            delete locale;
    }

    if(auto iconTheme = conf.value<QString>("browser.iconTheme")) {
        QIcon::setThemeName(iconTheme.value());
    }
}

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 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;
}

void Browser::loadProfiles(const QStringList &profilePaths)
{
    Configuration conf;

    for(const QString &path : profilePaths) {
        const QString id = QFileInfo(path).baseName();

        auto *profile = m_profileManager->add(id, path);
        connect(profile, &WebProfile::downloadRequested, m_downloads.get(), &DownloadsWidget::addDownload);

        auto *interceptor = new UrlRequestInterceptor(profile, profile);
        for(UrlFilter *filter : m_filters) {
            interceptor->addFilter(filter);
        }

        const auto headers = conf.value<QStringList>("filter.header").value_or(QStringList());
        for(const QString &header : headers) {
            const auto h = header.split(QLatin1Literal(":"));
            if(h.length() == 2)
                interceptor->addHttpHeader(h.at(0).toLatin1(), h.at(1).toLatin1());
        }
        profile->setUrlRequestInterceptor(interceptor);

        spdlog::info("Added profile\t{}{}\t{}", qUtf8Printable(id), profile->isOffTheRecord() ? "*" : "", qUtf8Printable(profile->name()));
    }
}

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

    auto *loader = new PluginLoader(path, PluginLoader::SignatureMatchIfAvailable, this);
    const bool loaded = loader->load();
    spdlog::info("Loading plugin [{}]: {}", qUtf8Printable(path), loaded ? "passed" : "failed");

    if(!loaded) {
        delete loader;
        return nullptr;
    }

    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)
{
    Configuration conf;

    for(QPluginLoader *loader : plugins) {
        m_plugins.append(new PluginInfo(loader));
    }

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

    // downloads
    m_downloads = std::make_unique<DownloadsWidget>(conf.value<QString>("downloads.path").value());

    // url request filter
    for(const QString &hostlist : Util::files(conf.value<QString>("filter.hosts").value_or(QString()))) {
        QFile f(hostlist);
        if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            m_filters.append(new HostList(&f));
            f.close();
        }
    }
    for(const QString &adblock : Util::files(conf.value<QString>("filter.adblock").value_or(QString()))) {
        QFile f(adblock);
        if(f.open(QIODevice::ReadOnly | QIODevice::Text)) {
            m_filters.append(new AdBlockList(&f));
            f.close();
        }
    }
    // cookie request filter

    // load profiles
    m_profileManager = new WebProfileManager(this);
    WebProfileManager::setInstance(m_profileManager);
    loadProfiles(Util::files(conf.value<QString>("profile.path").value(), { "*.profile" }));

    // set default profile
    {
        const QString id = conf.value<QString>("profile.default").value();
        auto *profile = m_profileManager->profile(id);
        if(profile == nullptr) {
            spdlog::error("Unknown default profile!");
            //profile = qobject_cast<WebProfile *>(loadProfile(id).second);
        } else {
            spdlog::info("Default profile\t{}{}\t{}", qUtf8Printable(id), profile->isOffTheRecord() ? "*" : "", qUtf8Printable(profile->name()));
            WebProfile::setDefaultProfile(profile);
        }
    }

    // bookmarks
    m_bookmarks = std::make_shared<BookmarksWidget>(QString::fromStdString(conf.value<std::string>("bookmarks.path").value()));
    connect(m_bookmarks.get(), &BookmarksWidget::showContextMenu, this, [this](const QUrl &url, const QPoint &pos) {
        auto *subwindow = m_windows.last()->currentSubWindow();
        if(subwindow == nullptr)
            return;

        auto *menu = new QMenu(m_bookmarks.get());

        menu->addAction(tr("Open link in current tab"), subwindow, [url, subwindow]() {
            subwindow->currentView()->load(url);
        });

        auto *openInCurrentTabWithProfile = menu->addMenu(tr("Open link in current tab with profile"));
        profileMenu(openInCurrentTabWithProfile, [url, subwindow](WebProfile *profile) {
            subwindow->currentView()->setProfile(profile);
            subwindow->currentView()->load(url);
        });

        menu->addAction(tr("Open link in new tab"), subwindow, [url, subwindow]() {
            subwindow->addTab(url);
        });

        auto *openInNewTabWithProfile = menu->addMenu(tr("Open link in new tab with profile"));
        profileMenu(openInNewTabWithProfile, [url, subwindow](WebProfile *profile) {
            subwindow->addTab(url, profile);
        });

        menu->exec(pos);
    });

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