/*
 * 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 "mainwindow.h"
#include "addressbar/addressbar.h"
#include "browser.h"
#include "session.h"
#include "subwindow/subwindow.h"
#include "ui_mainwindow.h"
#include "webengine/webview.h"
#include "widgets/dockwidget.h"
#include "widgets/navigationbar.h"
#include "widgets/searchform.h"
#include <QApplication>
#include <QCloseEvent>
#include <QFileDialog>
#include <QJsonArray>
#include <QJsonObject>
#include <QLineEdit>
#include <QMdiArea>
#include <QMdiSubWindow>
#include <QMenuBar>
#include <QMessageBox>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrinterInfo>
#include <QShortcut>
#include <QStatusBar>
#include <QToolBar>
#include <QUrl>
#include <QVBoxLayout>
#include <configuration/configuration.h>
#include <web/profilemanager.h>
#include <web/webprofile.h>
#ifdef PLASMA_BLUR
#include <KWindowEffects>
#endif

inline QDialog *createDevToolsDialog(QWebEnginePage *page)
{
    Q_CHECK_PTR(page);

    auto *popup = new QDialog(nullptr);
    popup->setWindowTitle(QObject::tr("Developer Tools"));
    popup->setAttribute(Qt::WA_DeleteOnClose, true);
    popup->resize(800, 600);

    auto *view = new QWebEngineView(popup);
    auto *devPage = new QWebEnginePage(view);

    view->setPage(devPage);
    page->setDevToolsPage(devPage);

    QObject::connect(popup, &QDialog::destroyed, [page]() {
        page->setDevToolsPage(nullptr);
    });

    auto *l = new QVBoxLayout(popup);
    l->setContentsMargins(0, 0, 0, 0);
    l->addWidget(view);

    return popup;
}

MainWindow::MainWindow(const std::unique_ptr<Configuration> &config, QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
    , mdiArea(new QMdiArea(this))
{
    Q_ASSERT(config);

    ui->setupUi(this);

#ifdef PLASMA_BLUR
    setAttribute(Qt::WA_TranslucentBackground, true);
    KWindowEffects::enableBlurBehind(this->winId(), true);
#endif

    // create UI
    setWindowTitle(config->value<QString>("mainwindow.title").value());
    resize(config->value<int>("mainwindow.width").value(), config->value<int>("mainwindow.height").value());
    if(config->value<bool>("mainwindow.maximized").value()) {
        setWindowState(Qt::WindowMaximized);
    }
    show();

    // connect smolbote menu
    {
        connect(ui->actionNewSubwindow, &QAction::triggered, this, [this, &config]() {
            auto *profile = WebProfile::defaultProfile();
            auto *window = createSubWindow(config, profile);
            window->addTab(profile->newtab(), profile);
        });
        config->setShortcut(ui->actionNewSubwindow, "mainwindow.shortcuts.newGroup");

        connect(ui->actionNewWindow, &QAction::triggered, this, []() {
            auto *browser = qobject_cast<Browser *>(qApp);
            if(browser)
                browser->createWindow();
        });
        config->setShortcut(ui->actionNewWindow, "mainwindow.shortcuts.newWindow");

        connect(ui->actionAbout, &QAction::triggered, qobject_cast<Browser *>(qApp), &Browser::about);
        config->setShortcut(ui->actionAbout, "mainwindow.shortcuts.about");

        connect(ui->actionAboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);

        connect(ui->actionQuit, &QAction::triggered, qApp, &QApplication::quit);
        config->setShortcut(ui->actionQuit, "mainwindow.shortcuts.quit");
    }

    // connect session menu
    {
        connect(ui->actionSaveSession, &QAction::triggered, this, [this]() {
            const QString filename = QFileDialog::getSaveFileName(this, tr("Save Session"), QDir::homePath(), tr("JSON (*.json)"));
            QFile output(filename);
            if(output.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
                output.write(QJsonDocument(Session::window(this)).toJson());
                output.close();
            }
        });
        connect(ui->actionLoadSession, &QAction::triggered, this, [this]() {
            const QString filename = QFileDialog::getOpenFileName(this, tr("Load Session"), QDir::homePath(), tr("JSON (*.json)"));
            QFile json(filename);
            if(json.open(QIODevice::ReadOnly | QIODevice::Text)) {
                auto *browser = qobject_cast<Browser *>(qApp);
                auto doc = QJsonDocument::fromJson(json.readAll());
                browser->createSession(doc.object());
                json.close();
            }
        });
    }

    // connect window menu
    {
        connect(ui->actionTileSubwindows, &QAction::triggered, mdiArea, &QMdiArea::tileSubWindows);
        config->setShortcut(ui->actionTileSubwindows, "mainwindow.shortcuts.tileWindows");

        connect(ui->actionCascadeSubwindows, &QAction::triggered, mdiArea, &QMdiArea::cascadeSubWindows);
        config->setShortcut(ui->actionCascadeSubwindows, "mainwindow.shortcuts.cascadeWindows");
    }

    // connect page menu
    {
        connect(ui->actionBookmarkPage, &QAction::triggered, this, [this]() {
            if(currentView != nullptr)
                emit createBookmark(currentView->title(), currentView->url().toString());
        });
        connect(ui->actionSavePage, &QAction::triggered, this, [this]() {
            if(currentView != nullptr)
                currentView->triggerPageAction(QWebEnginePage::SavePage);
        });
        connect(ui->actionPrintPage, &QAction::triggered, this, [this]() {
            if(currentView != nullptr) {
                auto *printer = new QPrinter(QPrinterInfo::defaultPrinter());
                QPrintDialog dlg(printer, this);
                if(dlg.exec() == QDialog::Accepted) {
                    currentView->page()->print(printer, [printer](bool success) {
                        Q_UNUSED(success);
                        delete printer;
                    });
                }
            }
        });
        connect(ui->actionPrintPageToPdf, &QAction::triggered, this, [this]() {
            if(currentView != nullptr) {
                const QString path = QFileDialog::getSaveFileName(this, tr("Print to PDF"), QDir::homePath(), tr("PDF files (*.pdf)"));
                currentView->page()->printToPdf(path);
            }
        });

        connect(ui->actionDeveloperTools, &QAction::triggered, this, [this]() {
            if(currentView != nullptr)
                createDevToolsDialog(currentView->page())->show();
        });

        pageLoadProfileMenu = ui->menuPage->addMenu(tr("Load Profile"));
        connect(pageLoadProfileMenu, &QMenu::aboutToShow, this, &MainWindow::updatePageLoadProfileMenu);
    }

    // current subwindow shortcut
    {
        QAction *subwindowMenuAction = new QAction(this);
        QMainWindow::addAction(subwindowMenuAction);
        config->setShortcut(subwindowMenuAction, "subwindow.shortcuts.menu");
        connect(subwindowMenuAction, &QAction::triggered, this, [this]() {
            QMdiSubWindow *window = mdiArea->currentSubWindow();
            if(window) {
                // show the menu at the subwindow position
                // position has to be global, and mapped by the mdiArea (parentWidget() of the subwindow)
                const auto position = mdiArea->mapToGlobal(window->pos());
                window->systemMenu()->exec(position);
            }
        });
    }

    navigationToolBar = new NavigationBar(config->section("navigation"), this);
    navigationToolBar->setMovable(config->value<bool>("navigation.movable").value());
    addToolBar(Qt::TopToolBarArea, navigationToolBar);
    navigationToolBar->connectWebView(nullptr);

    addressBar = new AddressBar(config->section("addressbar"), this);
    navigationToolBar->addWidget(addressBar);

    mdiArea->setBackground(Qt::NoBrush);
    setCentralWidget(mdiArea);
    mdiArea->setFocus();

    // status bar
    searchBox = new SearchForm(this);
    statusBar()->addPermanentWidget(searchBox);
    searchBox->setVisible(false);

    // connect signlas
    connect(mdiArea, &QMdiArea::subWindowActivated, this, [this](QMdiSubWindow *window) {
        disconnect(viewChangedConnection);
        disconnect(searchBoxConnection);
        disconnect(statusBarConnection);
        ui->actionCurrentSubwindow->setMenu(nullptr);

        auto *w = qobject_cast<SubWindow *>(window);
        if(w == nullptr) {
            // no current subwindow, clear everything
            setView(nullptr);
            ui->actionCurrentSubwindow->setMenu(nullptr);
        } else {
            setView(w->currentView());
            ui->actionCurrentSubwindow->setMenu(w->systemMenu());
            viewChangedConnection = connect(w, &SubWindow::currentViewChanged, this, &MainWindow::setView);
            statusBarConnection = connect(w, &SubWindow::showStatusMessage, statusBar(), &QStatusBar::showMessage);
        }
    });

    // address bar
    connect(addressBar, &AddressBar::search, this, [this](const QString &term) {
        if(this->currentView) {
            currentView->search(term);
            currentView->setFocus();
        }
    });
    connect(addressBar, &AddressBar::load, this, [this](const QUrl &url) {
        if(this->currentView) {
            currentView->load(url);
            currentView->setFocus();
        }
    });

    connect(addressBar, &AddressBar::giveFocus, this, [this]() {
        if(this->currentView) {
            currentView->setFocus();
        }
    });

    // search box
    auto *searchAction = new QAction(this);
    config->setShortcut(searchAction, "mainwindow.shortcuts.search");
    connect(searchAction, &QAction::triggered, this, [=]() {
        /* QTBUG-18665
         * When focusing out of the search box and hiding it, the first
         * (or earlier?) subwindow gets activated for some reason.
         */
        if(searchBox->isVisible()) {
            auto *w = mdiArea->currentSubWindow();
            searchBox->hide();
            mdiArea->setActiveSubWindow(w);
        } else {
            searchBox->show();
        }
    });
    QMainWindow::addAction(searchAction);
}

MainWindow::~MainWindow()
{
    disconnect(viewChangedConnection);
    disconnect(searchBoxConnection);
    disconnect(statusBarConnection);

    disconnect(addressBar);
}

void MainWindow::addAction(ActionLocation where, QAction *action)
{
    switch(where) {
    case ToolsMenu:
        ui->menuTools->addAction(action);
        break;
    default:
        QMainWindow::addAction(action);
        break;
    }
}

void MainWindow::addDockWidget(Qt::DockWidgetArea area, QWidget *widget)
{
    QDockWidget *lastDock = nullptr;
    const auto docks = findChildren<QDockWidget *>();
    for(QDockWidget *dock : docks) {
        if(dockWidgetArea(dock) == area)
            lastDock = dock;
    }

    DockWidget *dock = new DockWidget(widget->windowTitle(), this);
    dock->setMinimumWidth(460);
    dock->setWidget(widget);

    if(lastDock == nullptr)
        QMainWindow::addDockWidget(area, dock);
    else
        tabifyDockWidget(lastDock, dock);
}

void MainWindow::removeDockWidget(QWidget *widget)
{
    const auto docks = this->findChildren<QDockWidget *>();
    for(QDockWidget *dock : docks) {
        if(dock->widget() == widget) {
            dock->widget()->setParent(nullptr);
            dock->close();
        }
    }
}

void MainWindow::createTab(const QUrl &url)
{
    auto *w = qobject_cast<SubWindow *>(mdiArea->currentSubWindow());
    if(w != nullptr) {
        w->addTab(url);
    }
}

const QVector<SubWindow *> MainWindow::subWindows() const
{
    QVector<SubWindow *> list;
    const auto subwindows = mdiArea->subWindowList();
    for(auto *w : subwindows) {
        auto *subwindow = qobject_cast<SubWindow *>(w);
        if(subwindow != nullptr)
            list.append(subwindow);
    }

    return list;
}

SubWindow *MainWindow::currentSubWindow() const
{
    return qobject_cast<SubWindow *>(mdiArea->currentSubWindow());
}

SubWindow *MainWindow::createSubWindow(const std::unique_ptr<Configuration> &config, WebProfile *profile)
{
    bool shouldMaximize = true;
    // if there is a current window, use its maximize state
    if(auto *currentWindow = qobject_cast<SubWindow *>(mdiArea->currentSubWindow()); currentWindow != nullptr) {
        shouldMaximize = currentWindow->isMaximized();
    }

    auto *w = new SubWindow(config, this);
    w->setProfile(profile);
    mdiArea->addSubWindow(w);
    if(shouldMaximize)
        w->showMaximized();
    else
        w->show();

    w->setFocus();
    return w;
}

void MainWindow::setView(WebView *view)
{
    if(currentView) {
        // disconnect old view
        disconnect(currentView, 0, addressBar, 0);
    }
    currentView = view;

    if(view) {
        connect(view, &WebView::urlChanged, addressBar, &AddressBar::setUrl);
        addressBar->setUrl(view->url());

        connect(view, &WebView::loadProgress, addressBar, &AddressBar::setProgress);
        addressBar->setProgress(100);

    } else {
        addressBar->setUrl(QUrl());
        addressBar->setProgress(100);
    }

    navigationToolBar->connectWebView(view);
    searchBox->setView(view);
}

void MainWindow::updatePageLoadProfileMenu()
{
    Q_CHECK_PTR(pageLoadProfileMenu);
    pageLoadProfileMenu->clear();

    if(currentView == nullptr)
        return;

    auto *profileManager = dynamic_cast<Browser *>(qApp)->getProfileManager();
    pageLoadProfileMenu->addActions(profileManager->createProfileMenu([this](WebProfile *profile) {
                                                      this->currentView->setProfile(profile);
                                                  },
                                                      this)
                                        ->actions());
}

void MainWindow::closeEvent(QCloseEvent *event)
{
    if(mdiArea->subWindowList().count() > 1) {
        int choice = QMessageBox::question(this, tr("Close multiple subwindows?"), tr("Do you want to close all subwindows?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
        if(choice == QMessageBox::No) {
            event->ignore();
            return;
        }
    }

    mdiArea->closeAllSubWindows();
    if(mdiArea->currentSubWindow())
        event->ignore();
    else
        event->accept();
}