/*
 * 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 "menubar.h"
#include "bookmarkswidget.h"
#include "browser.h"
#include "configuration.h"
#include "downloadswidget.h"
#include "mainwindow.h"
#include "webprofilemanager.h"
#include "session/savesessiondialog.h"
#include "session/sessiondialog.h"
#include "subwindow/subwindow.h"
#include "webengine/webview.h"
#include "widgets/menusearch.h"
#include <QApplication>
#include <QDir>
#include <QFileDialog>
#include <QLineEdit>
#include <QMdiArea>
#include <QPrintDialog>
#include <QPrinter>
#include <QPrinterInfo>
#include <QWidgetAction>
#include <functional>
#include <QVBoxLayout>

inline void run_if(SubWindow *_subwindow, const std::function<void(SubWindow*, int)> &f)
{
    if(_subwindow != nullptr)
        f(_subwindow, _subwindow->currentTabIndex());
}

inline void trigger_if(WebView *_view, QWebEnginePage::WebAction action)
{
    if(_view != nullptr)
        _view->triggerPageAction(action);
}

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

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

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

    return popup;
}

MenuBar::MenuBar(const Configuration *config, MainWindow *parent)
    : QMenuBar(parent)
{
    auto *browser = qobject_cast<Browser *>(qApp);
    Q_CHECK_PTR(browser);

    smolbote = this->addMenu(qApp->applicationName());
    {
        auto *findMenu = smolbote->addMenu(tr("Find in menus"));

        auto *findWidget = new QWidgetAction(this);
        auto *find_lineEdit = new MenuSearch(this);
        findWidget->setDefaultWidget(find_lineEdit);
        findMenu->addAction(findWidget);

        connect(findMenu, &QMenu::aboutToShow, [findMenu, find_lineEdit]() {
            find_lineEdit->clear();
            const auto actions = findMenu->actions();
            for(int i = 1; i < actions.length(); i++)
                findMenu->removeAction(actions.at(i));
            find_lineEdit->setFocus();
        });

        connect(find_lineEdit, &QLineEdit::textEdited, [this, findMenu](const QString &text) {
            // clear menu
            const auto actions = findMenu->actions();
            for(int i = 1; i < actions.length(); i++)
                findMenu->removeAction(actions.at(i));

            if(text.isEmpty())
                return;

            // findChildren
            for(QAction *a : this->findChildren<QAction *>()) {
                if(a->text().contains(text))
                    findMenu->addAction(a);
            }
        });

        smolbote->addSeparator();

        const QString sessionPath = config->value<QString>("browser.session.path").value();
        auto *actionSaveSession = smolbote->addAction(tr("Save Session"), parent, [sessionPath]() {
            auto *sessionDialog = new SaveSessionDialog(nullptr);
            if(sessionDialog->exec() == QDialog::Accepted)
                sessionDialog->save(sessionPath);
        });
        config->setShortcut(actionSaveSession, "mainwindow.shortcuts.saveSession");

        auto *actionOpenSession = smolbote->addAction(tr("Open Session"), parent, [parent]() {
            auto *sessionDialog = new SessionDialog(parent);
            sessionDialog->exec();
        });
        config->setShortcut(actionOpenSession, "mainwindow.shortcuts.openSession");

        smolbote->addSeparator();
        auto *actionBookmarks = smolbote->addAction(tr("Bookmarks"), browser, [browser, parent]() {
            browser->showWidget(browser->bookmarks(), parent);
        });
        config->setShortcut(actionBookmarks, "bookmarks.shortcut");

        auto *actionDownloads = smolbote->addAction(tr("Downloads"), browser, [browser, parent]() {
            browser->showWidget(browser->downloads(), parent);
        });
        config->setShortcut(actionDownloads, "downloads.shortcut");

        smolbote->addSeparator();
        smolbote->addAction(tr("Load Plugin"), browser, [browser]() {
            const QString path = QFileDialog::getOpenFileName(nullptr, tr("Select Plugin"), QDir::homePath(), tr("Plugins (*.so)"));
            browser->addPlugin(path);
        });

        pluginInsertLocation = smolbote->addSeparator();

        auto *actionAbout = smolbote->addAction(tr("About"), browser, &Browser::about);
        config->setShortcut(actionAbout, "mainwindow.shortcuts.about");

//        smolbote->addAction(tr("Help"));
//        smolbote->addAction(tr("Check for updates"));

        smolbote->addSeparator();

        auto *actionQuit = smolbote->addAction(tr("Quit"), qApp, &QApplication::quit);
        config->setShortcut(actionQuit, "mainwindow.shortcuts.quit");
    }

    window = this->addMenu(tr("&Window"));
    {
        auto *actionNewWindow = window->addAction(tr("New Window"), browser, &Browser::createWindow);
        config->setShortcut(actionNewWindow, "mainwindow.shortcuts.newWindow");

        auto *actionNewSubwindow = window->addAction(tr("New Subwindow"), parent, [parent]() {
            parent->createSubWindow(nullptr, true);
        });
        config->setShortcut(actionNewSubwindow, "mainwindow.shortcuts.newGroup");

        window->addSeparator();

        auto *actionTileSubwindows = window->addAction(tr("Tile Subwindows"), parent->mdiArea, &QMdiArea::tileSubWindows);
        config->setShortcut(actionTileSubwindows, "mainwindow.shortcuts.tileWindows");

        auto *actionCascadeSubwindows = window->addAction(tr("Cascade Subwindows"), parent->mdiArea, &QMdiArea::cascadeSubWindows);
        config->setShortcut(actionCascadeSubwindows, "mainwindow.shortcuts.cascadeWindows");

        window->addSeparator()->setText(tr("Subwindows"));
    }

    auto *subwindow = this->addMenu(tr("&Subwindow"));
    {
        auto *actionNewTab = subwindow->addAction(tr("New Tab"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int) {
                const int index = _subwindow->addTab();
                _subwindow->setCurrentTab(index);
            });
        });
        config->setShortcut(actionNewTab, "subwindow.shortcuts.new");

        subwindow->addSeparator();

        auto *actionRestoreTab = subwindow->addAction(tr("Restore last tab"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int) {
                const int index = _subwindow->restoreLastTab();
                _subwindow->setCurrentTab(index);
            });
        });
        config->setShortcut(actionRestoreTab, "subwindow.shortcuts.restoreTab");

        auto *restoreTabsMenu = subwindow->addMenu(tr("Restore previous tab"));
        connect(restoreTabsMenu, &QMenu::aboutToShow, parent, [parent, restoreTabsMenu]() {
            restoreTabsMenu->clear();
            auto *_subwindow = parent->currentSubWindow();
            if(_subwindow != nullptr) {
                _subwindow->restoreTabMenu(restoreTabsMenu);
            }
        });

        subwindow->addSeparator()->setText(tr("Tab Settings"));

        auto *actionPinTab = subwindow->addAction(tr("Pin tab"));
        actionPinTab->setCheckable(true);
        actionPinTab->setEnabled(false);

        auto *actionLockClose = subwindow->addAction(tr("Prevent tab from closing"), parent, [parent](bool checked) {
            run_if(parent->currentSubWindow(), [checked](SubWindow *_subwindow, int currentIdx) {
                auto data = _subwindow->tabData(currentIdx);
                data.closeLocked = checked;
                _subwindow->setTabData(data, currentIdx);
            });
        });
        actionLockClose->setCheckable(true);

        auto *actionLockRefresh = subwindow->addAction(tr("Prevent tab from refreshing"), parent, [parent](bool checked) {
            run_if(parent->currentSubWindow(), [checked](SubWindow *_subwindow, int currentIdx) {
                auto data = _subwindow->tabData(currentIdx);
                data.refreshLocked = checked;
                _subwindow->setTabData(data, currentIdx);
            });
        });
        actionLockRefresh->setCheckable(true);

        connect(subwindow, &QMenu::aboutToShow, subwindow, [=]() {
            run_if(parent->currentSubWindow(), [=](SubWindow *_subwindow, int currentIdx) {
                auto data = _subwindow->tabData(currentIdx);
                actionLockClose->setChecked(data.closeLocked);
                actionLockRefresh->setChecked(data.refreshLocked);
            });
        });

        subwindow->addSeparator()->setText(tr("Tab Actions"));

        auto *leftTab = subwindow->addAction(tr("Switch to tab on the left"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                _subwindow->setCurrentTab(qMax(0, currentIdx - 1));
            });
        });
        config->setShortcut(leftTab, "subwindow.shortcuts.left");

        auto *moveTabLeft = subwindow->addAction(tr("Move tab left"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                _subwindow->moveTab(currentIdx, currentIdx - 1);
            });
        });
        config->setShortcut(moveTabLeft, "subwindow.shortcuts.moveLeft");

        auto *rightTab = subwindow->addAction(tr("Switch to tab on the right"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                _subwindow->setCurrentTab(qMin(currentIdx + 1, _subwindow->tabCount() - 1));
            });
        });
        config->setShortcut(rightTab, "subwindow.shortcuts.right");

        auto *moveTabRight = subwindow->addAction(tr("Move tab right"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                _subwindow->moveTab(currentIdx, currentIdx + 1);
            });
        });
        config->setShortcut(moveTabRight, "subwindow.shortcuts.moveRight");

        subwindow->addSeparator();

        auto *closeTab = subwindow->addAction(tr("Close tab"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                _subwindow->closeTab(currentIdx);
            });
        });
        config->setShortcut(closeTab, "subwindow.shortcuts.close");

        subwindow->addAction(tr("Close tabs to the left"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = currentIdx - 1; i >= 0; i--) {
                    const auto data = _subwindow->tabData(i);
                    if(!data.closeLocked)
                        _subwindow->closeTab(i);
                }
            });
        });
        subwindow->addAction(tr("Close tabs to the right"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = _subwindow->tabCount() - 1; i > currentIdx; i--) {
                    const auto data = _subwindow->tabData(i);
                    if(!data.closeLocked)
                        _subwindow->closeTab(i);
                }
            });
        });
        subwindow->addAction(tr("Close all other tabs"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = _subwindow->tabCount() -1; i >= 0; i--) {
                    if(i != currentIdx) {
                        const auto data = _subwindow->tabData(i);
                        if(!data.closeLocked)
                            _subwindow->closeTab(i);
                    }
                }
            });
        });

        subwindow->addSeparator();

        subwindow->addAction(tr("Refresh tabs to the left"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = 0; i < currentIdx; i++) {
                    const auto data = _subwindow->tabData(i);
                    if(!data.refreshLocked)
                        _subwindow->view(i)->triggerPageAction(QWebEnginePage::Reload);
                }
            });
        });
        subwindow->addAction(tr("Refresh tabs to the right"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = currentIdx + 1; i < _subwindow->tabCount(); i++) {
                    const auto data = _subwindow->tabData(i);
                    if(!data.refreshLocked)
                        _subwindow->view(i)->triggerPageAction(QWebEnginePage::Reload);
                }
            });
        });
        subwindow->addAction(tr("Refresh all other tabs"), parent, [parent]() {
            run_if(parent->currentSubWindow(), [](SubWindow *_subwindow, int currentIdx) {
                for(int i = 0; i < _subwindow->tabCount(); i++) {
                    if(i != currentIdx) {
                        const auto data = _subwindow->tabData(i);
                        if(!data.refreshLocked)
                            _subwindow->view(i)->triggerPageAction(QWebEnginePage::Reload);
                    }
                }
            });
        });

        subwindow->addSeparator();

        auto *subwindowProfile = subwindow->addMenu(tr("Subwindow Profile"));
        connect(subwindowProfile, &QMenu::aboutToShow, subwindowProfile, [=]() {
            subwindowProfile->clear();

            auto *_subwindow = parent->currentSubWindow();
            if(_subwindow != nullptr) {
                browser->getProfileManager()->profileMenu(subwindowProfile, [_subwindow](WebProfile *profile) {
                    _subwindow->setProfile(profile);
                }, _subwindow->profile(), true);
            }
        });
    }

    auto *page = this->addMenu(tr("&Page"));
    {
        page->addAction(tr("Create Bookmark"), parent, [browser, parent]() {
            if(parent->currentView != nullptr)
                browser->bookmarks()->addBookmark(parent->currentView->title(), parent->currentView->url().toString());
        });

        page->addSeparator();

        page->addAction(tr("Save"), parent, [parent]() {
            if(parent->currentView != nullptr)
                parent->currentView->triggerPageAction(QWebEnginePage::SavePage);
        });
        page->addAction(tr("Print"), parent, [parent]() {
            if(parent->currentView != nullptr) {
                auto *printer = new QPrinter(QPrinterInfo::defaultPrinter());
                QPrintDialog dlg(printer, parent);
                if(dlg.exec() == QDialog::Accepted) {
                    parent->currentView->page()->print(printer, [printer](bool success) {
                        Q_UNUSED(success);
                        delete printer;
                    });
                }
            }
        });

        page->addAction(tr("Print to PDF"), parent, [parent]() {
            if(parent->currentView != nullptr) {
                const QString path = QFileDialog::getSaveFileName(parent, tr("Print to PDF"), QDir::homePath(), tr("PDF files (*.pdf)"));
                parent->currentView->page()->printToPdf(path);
            }
        });

        page->addSeparator();

        auto pageProfile = page->addMenu(tr("Page Profile"));
        connect(pageProfile, &QMenu::aboutToShow, pageProfile, [=]() {
            pageProfile->clear();

            if(parent->currentView != nullptr) {
                browser->getProfileManager()->profileMenu(pageProfile, [parent](WebProfile *profile) {
                    parent->currentView->setProfile(profile);
                }, parent->currentView->profile(), true);
            }
        });

        page->addSeparator();
        page->addAction(tr("Developer Tools"), parent, [parent]() {
            if(parent->currentView != nullptr) {
                auto *dlg = createDevToolsDialog(parent->currentView->page());
                dlg->show();
            }
        });
        page->addAction(tr("View Source"), parent, [parent]() {
            if(parent->currentView != nullptr) {
                parent->currentView->triggerPageAction(QWebEnginePage::ViewSource);
            }
        });
#ifdef QT_DEBUG
        // doesn't seem to do anything?
        page->addAction(tr("Inspect Element"), parent, [parent]() {
            if(parent->currentView != nullptr) {
                parent->currentView->triggerPageAction(QWebEnginePage::InspectElement);
            }
        });
#endif
    }

    this->addSeparator();

    auto *edit = this->addMenu(tr("Edit"));
    {
        edit->addAction(tr("Undo"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Undo);
        });
        edit->addAction(tr("Redo"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Redo);
        });

        edit->addSeparator();

        edit->addAction(tr("Cut"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Cut);
        });
        edit->addAction(tr("Copy"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Copy);
        });
        edit->addAction(tr("Paste"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Paste);
        });
        edit->addAction(tr("Paste and match Style"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::PasteAndMatchStyle);
        });

        edit->addSeparator();

        edit->addAction(tr("Select all"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::SelectAll);
        });
        edit->addAction(tr("Clear selection"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Unselect);
        });

        edit->addSeparator()->setText(tr("Editing"));

        edit->addAction(tr("Make editable"), parent, [parent]() {
            if(parent->currentView != nullptr)
                parent->currentView->page()->runJavaScript("document.documentElement.contentEditable = true");
        });
        edit->addAction(tr("Bold"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::ToggleBold);
        });
        edit->addAction(tr("Italic"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::ToggleItalic);
        });
        edit->addAction(tr("Underline"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::ToggleUnderline);
        });
        edit->addAction(tr("Strikethrough"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::ToggleStrikethrough);
        });
        edit->addAction(tr("Align left"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::AlignLeft);
        });
        edit->addAction(tr("Align right"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::AlignRight);
        });
        edit->addAction(tr("Align center"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::AlignCenter);
        });
        edit->addAction(tr("Align justified"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::AlignJustified);
        });
        edit->addAction(tr("Indent"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Indent);
        });
        edit->addAction(tr("Outdent"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::Outdent);
        });
        edit->addAction(tr("Insert ordered list"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::InsertOrderedList);
        });
        edit->addAction(tr("Insert unordered list"), parent, [parent]() {
            trigger_if(parent->currentView, QWebEnginePage::InsertUnorderedList);
        });
    }
}

QAction *MenuBar::insertPlugin(QMenu *menu)
{
    return smolbote->insertMenu(pluginInsertLocation, menu);
}

void MenuBar::insertSubWindow(SubWindow *subwindow)
{
    auto *action = window->addAction(subwindow->windowTitle(), subwindow, [subwindow]() {
        if(!subwindow->windowState().testFlag(Qt::WindowMaximized))
            subwindow->showMaximized();
        else
            subwindow->showNormal();
    });

    action->setCheckable(true);
    connect(subwindow, &SubWindow::windowStateChanged, action, [action](Qt::WindowStates, Qt::WindowStates state) {
        action->setChecked(state.testFlag(Qt::WindowMaximized) | state.testFlag(Qt::WindowActive));
    });

    connect(subwindow, &SubWindow::windowTitleChanged, action, &QAction::setText);
    connect(subwindow, &SubWindow::destroyed, action, &QAction::deleteLater);
}