/*
 * Copyright 2022-2023 Soren Stoutner <soren@stoutner.com>.
 *
 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc>.
 *
 * Privacy Browser PC is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Privacy Browser PC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Privacy Browser PC.  If not, see <http://www.gnu.org/licenses/>.
 */

// Application headers.
#include "PrivacyWebEngineView.h"
#include "Settings.h"
#include "databases/CookiesDatabase.h"
#include "databases/DomainsDatabase.h"
#include "interceptors/UrlRequestInterceptor.h"
#include "windows/BrowserWindow.h"

// Qt toolkit headers.
#include <QContextMenuEvent>
#include <QMenu>

// Construct the class.
PrivacyWebEngineView::PrivacyWebEngineView() : QWebEngineView(nullptr)
{
    // Create an off-the-record profile (the default when no profile name is specified).
    webEngineProfilePointer = new QWebEngineProfile(QLatin1String(""));

    // Create a WebEngine page.
    QWebEnginePage *webEnginePagePointer = new QWebEnginePage(webEngineProfilePointer);

    // Set the WebEngine page.
    setPage(webEnginePagePointer);

    // Get handles for the various aspects of the WebEngine.
    webEngineSettingsPointer = webEnginePagePointer->settings();

    // Instantiate the URL request interceptor.
    UrlRequestInterceptor *urlRequestInterceptorPointer = new UrlRequestInterceptor();

    // Set the URL request interceptor.
    webEngineProfilePointer->setUrlRequestInterceptor(urlRequestInterceptorPointer);

    // Reapply the domain settings when the host changes.
    connect(urlRequestInterceptorPointer, SIGNAL(applyDomainSettings(const QString&)), this, SLOT(applyDomainSettingsWithoutReloading(const QString&)));

    // Display HTTP Ping blocked dialogs.
    connect(urlRequestInterceptorPointer, SIGNAL(displayHttpPingDialog(const QString&)), this, SLOT(displayHttpPingDialog(const QString&)));
}

void PrivacyWebEngineView::addCookieToList(const QNetworkCookie &cookie) const
{
    //qDebug() << "Add cookie:  " << cookie.toRawForm();

    // Add the new cookie to the list.
    cookieListPointer->push_front(cookie);

    // Update the cookie if it is durable and has new data.
    if (CookiesDatabase::isUpdate(cookie))
        CookiesDatabase::updateCookie(cookie);

    // Update the cookies action.
    emit updateCookiesAction(cookieListPointer->size());
}

void PrivacyWebEngineView::applyDomainSettingsWithoutReloading(const QString &hostname)
{
    // Apply the domain settings  `false` does not reload the website.
    applyDomainSettings(hostname, false);
}

void PrivacyWebEngineView::applyDomainSettings(const QString &hostname, const bool reloadWebsite)
{
    // Get the record for the hostname.
    QSqlQuery domainQuery = DomainsDatabase::getDomainQuery(hostname);

    // Check if the hostname has domain settings.
    if (domainQuery.isValid())  // The hostname has domain settings.
    {
        // Get the domain record.
        QSqlRecord domainRecord = domainQuery.record();

        // Store the domain settings name.
        domainSettingsName = domainRecord.field(DomainsDatabase::DOMAIN_NAME).value().toString();

        // Set the JavaScript status.
        switch (domainRecord.field(DomainsDatabase::JAVASCRIPT).value().toInt())
        {
            // Set the default JavaScript status.
            case (DomainsDatabase::SYSTEM_DEFAULT):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());

                break;
            }

            // Disable JavaScript.
            case (DomainsDatabase::DISABLED):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, false);

                break;
            }

            // Enable JavaScript.
            case (DomainsDatabase::ENABLED):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, true);

                break;
            }
        }

        // Set the local storage status.
        switch (domainRecord.field(DomainsDatabase::LOCAL_STORAGE).value().toInt())
        {
            // Set the default local storage status.
            case (DomainsDatabase::SYSTEM_DEFAULT):
            {
                localStorageEnabled = Settings::localStorageEnabled();

                break;
            }

            // Disable local storage.
            case (DomainsDatabase::DISABLED):
            {
                localStorageEnabled = false;

                break;
            }

            // Enable local storage.
            case (DomainsDatabase::ENABLED):
            {
                localStorageEnabled = true;

                break;
            }
        }

        // Set the DOM storage status.
        switch (domainRecord.field(DomainsDatabase::DOM_STORAGE).value().toInt())
        {
            // Set the default DOM storage status.  QWebEngineSettings confusingly calls this local storage.
            case (DomainsDatabase::SYSTEM_DEFAULT):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());

                break;
            }

            // Disable DOM storage.  QWebEngineSettings confusingly calls this local storage.
            case (DomainsDatabase::DISABLED):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, false);

                break;
            }

            // Enable DOM storage.  QWebEngineSettings confusingly calls this local storage.
            case (DomainsDatabase::ENABLED):
            {
                webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, true);

                break;
            }
        }

        // Set the user agent.
        webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getResultingDomainSettingsUserAgent(domainRecord.field(DomainsDatabase::USER_AGENT).value().toString()));

        // Check if a custom zoom factor is set.
        if (domainRecord.field(DomainsDatabase::ZOOM_FACTOR).value().toInt())
        {
            // Store the current zoom factor.
            setZoomFactor(domainRecord.field(DomainsDatabase::CUSTOM_ZOOM_FACTOR).value().toDouble());
        }
        else
        {
            // Reset the current zoom factor.
            setZoomFactor(Settings::zoomFactor());
        }
    }
    else  // The hostname does not have domain settings.
    {
        // Reset the domain settings name.
        domainSettingsName = QLatin1String("");

        // Set the JavaScript status.
        webEngineSettingsPointer->setAttribute(QWebEngineSettings::JavascriptEnabled, Settings::javaScriptEnabled());

        // Set the local storage status.
        localStorageEnabled = Settings::localStorageEnabled();

        // Set DOM storage.  In QWebEngineSettings it is called Local Storage.
        webEngineSettingsPointer->setAttribute(QWebEngineSettings::LocalStorageEnabled, Settings::domStorageEnabled());

        // Set the user agent.
        webEngineProfilePointer->setHttpUserAgent(UserAgentHelper::getUserAgentFromDatabaseName(Settings::userAgent()));

        // Set the zoom factor.
        setZoomFactor(Settings::zoomFactor());
    }

    // Reload the website if requested.
    if (reloadWebsite)
        reload();

    // Update the UI.
    emit updateUi(this);
}

void PrivacyWebEngineView::contextMenuEvent(QContextMenuEvent *contextMenuEvent) {
    // Get a handle for the
    QWebEnginePage *webEnginePagePointer = page();

    // Get a handle for the menu.
    QMenu *contextMenu = webEnginePagePointer->createStandardContextMenu();

    // Get the list of context menu actions.
    const QList<QAction *> contextMenuActionsList = contextMenu->actions();

    // Add the open link in new background tab action if the context menu already contains the open link in new window action.
    if (contextMenuActionsList.contains(webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewWindow)))
    {
        // Move the open in new tab action to the top of the list.
        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewTab));

        // Add the open link in background tab action below the open in new tab action.
        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewBackgroundTab));

        // Move the open in new window action below the open in background tab action.
        contextMenu->insertAction(webEnginePagePointer->action(QWebEnginePage::Back), webEnginePagePointer->action(QWebEnginePage::OpenLinkInNewWindow));

        // Add a separator below the open in new window action.
        contextMenu->insertSeparator(webEnginePagePointer->action(QWebEnginePage::Back));
    }

    // Display the menu using the location in the context menu event.
    contextMenu->popup(contextMenuEvent->globalPos());
}

QWebEngineView* PrivacyWebEngineView::createWindow(QWebEnginePage::WebWindowType webWindowType) {
    // Get a handle for the browser window.
    BrowserWindow *browserWindowPointer = qobject_cast<BrowserWindow*>(window());

    // Create the requested window type.
    switch (webWindowType)
    {
        case QWebEnginePage::WebBrowserTab:
        {
            // Create the new tab and return the privacy WebEngine view pointer.  `true` removes the focus from the blank URL line edit.
            // The new privacy WebEngine view pointer is returned so it can be populated with the link from the context menu.
            return browserWindowPointer->tabWidgetPointer->addTab(true);
        }

        case QWebEnginePage::WebBrowserWindow:
        {
            // Create a new browser window.
            BrowserWindow *newBrowserWindowPointer = new BrowserWindow();

            // Show the new browser window.
            newBrowserWindowPointer->show();

            // The new privacy WebEngine view pointer is returned so it can be populated with the link from the context menu.
            return newBrowserWindowPointer->tabWidgetPointer->loadBlankInitialWebsite();
        }

        case QWebEnginePage::WebBrowserBackgroundTab:
        {
            // Create the new tab and return the privacy WebEngine view pointer.  `false` does not clear the URL line edit.  `true` creates a background tab.
            // The new privacy WebEngine view pointer is returned so it can be populated with the link from the context menu.
            return browserWindowPointer->tabWidgetPointer->addTab(false, true);
        }

        default:
        {
            // Return a null pointer for opening a web dialog.
            return nullptr;
        }
    }
}

void PrivacyWebEngineView::displayHttpPingDialog(const QString &httpPingUrl) const
{
    // Display the HTTP Ping blocked dialog.
    emit displayHttpPingBlockedDialog(httpPingUrl);
}

void PrivacyWebEngineView::removeCookieFromList(const QNetworkCookie &cookie) const
{
    //qDebug() << "Remove cookie:  " << cookie.toRawForm();

    // Remove the cookie from the list.
    cookieListPointer->remove(cookie);

    // Update the cookies action.
    emit updateCookiesAction(cookieListPointer->size());
}
