/* SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: 2024-2025 Soren Stoutner <soren@stoutner.com>
 *
 * This file is part of Privacy Browser PC <https://www.stoutner.com/privacy-browser-pc/>.
 *
 * This program 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.
 *
 * This program 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
 * this program.  If not, see <https://www.gnu.org/licenses/>.
 */

// Application headers.
#include "FilterListsDialog.h"
#include "GlobalVariables.h"
#include "ui_FilterListsDialog.h"
#include "dialogs/FilterEntryDialog.h"

// Qt toolkit headers.
#include <QDebug>
#include <QFile>
#include <QTextStream>

// Construct the class.
FilterListsDialog::FilterListsDialog(QWidget *parentWidgetPointer) : QDialog(parentWidgetPointer)
{
    // Set the window title.
    setWindowTitle(i18nc("The filter lists dialog window title", "Filter Lists"));

    // Set the window modality.
    setWindowModality(Qt::WindowModality::ApplicationModal);

    // Instantiate the filter lists dialog UI.
    Ui::FilterListsDialog filterListsDialogUi;

    // Setup the UI.
    filterListsDialogUi.setupUi(this);

    // Get handles for the views.
    filterListComboBoxPointer = filterListsDialogUi.filterListComboBox;
    filterListTextEditPointer = filterListsDialogUi.filterListTextEdit;
    sublistComboBoxPointer = filterListsDialogUi.sublistComboBox;
    sublistTableWidgetPointer = filterListsDialogUi.sublistTableWidget;
    QDialogButtonBox *dialogButtonBoxPointer = filterListsDialogUi.dialogButtonBox;

    // Select an entire row at a time.
    sublistTableWidgetPointer->setSelectionBehavior(QAbstractItemView::SelectRows);

    // Open the filter entry dialog when a cell is clicked.
    connect(sublistTableWidgetPointer, SIGNAL(cellClicked(int, int)), this, SLOT(showFilterEntryDialog(int)));

    // Connect the combo boxes.
    connect(filterListComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(populateFilterListTextEdit(int)));
    connect(sublistComboBoxPointer, SIGNAL(currentIndexChanged(int)), this, SLOT(populateTableWidget(int)));

    // Connect the buttons.
    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(close()));

    // Populate the filter list combo box.
    populateFilterListComboBox(globalFilterListHelperPointer->ultraPrivacyStructPointer);
    populateFilterListComboBox(globalFilterListHelperPointer->ultraListStructPointer);
    populateFilterListComboBox(globalFilterListHelperPointer->easyPrivacyStructPointer);
    populateFilterListComboBox(globalFilterListHelperPointer->easyListStructPointer);
    populateFilterListComboBox(globalFilterListHelperPointer->fanboyAnnoyanceStructPointer);
}

std::forward_list<FilterListEntryStruct *>* FilterListsDialog::getFilterListForwardList(FilterListStruct *filterListStructPointer) const
{
    // Return the filter list forward list.
    switch (sublistComboBoxPointer->currentIndex())
    {
        case FilterListHelper::MAIN_ALLOWLIST: return filterListStructPointer->mainAllowListPointer;  // The main allow list.
        case FilterListHelper::INITIAL_DOMAIN_ALLOWLIST: return filterListStructPointer->initialDomainAllowListPointer;  // The initial domain allow list.
        case FilterListHelper::REGULAR_EXPRESSION_ALLOWLIST: return filterListStructPointer->regularExpressionAllowListPointer;  // The regular expression allow list.
        case FilterListHelper::MAIN_BLOCKLIST: return filterListStructPointer->mainBlockListPointer;  // The main block list.
        case FilterListHelper::INITIAL_DOMAIN_BLOCKLIST: return filterListStructPointer->initialDomainBlockListPointer;  // The initial domain block list.
        case FilterListHelper::REGULAR_EXPRESSION_BLOCKLIST: return filterListStructPointer->regularExpressionBlockListPointer;  // The regular expression block list.
    }

    // This return statement should never be reached.
    return new std::forward_list<FilterListEntryStruct *>;
}

FilterListStruct *FilterListsDialog::getFilterListStruct(int filterListComboBoxIndex) const
{
    // Return the filer list struct.
    switch (filterListComboBoxIndex)
    {
        case 0: return globalFilterListHelperPointer->ultraPrivacyStructPointer;  // UltraPrivacy.
        case 1: return globalFilterListHelperPointer->ultraListStructPointer;  // UltraList.
        case 2: return globalFilterListHelperPointer->easyPrivacyStructPointer;  // EasyPrivacy.
        case 3: return globalFilterListHelperPointer->easyListStructPointer;  // EasyList.
        case 4: return globalFilterListHelperPointer->fanboyAnnoyanceStructPointer;  // Fanboy's Annoyance List.
    }

    // This return statement should never be reached.
    return new FilterListStruct;
}

void FilterListsDialog::populateFilterListComboBox(const FilterListStruct *filterListStructPointer) const
{
    // Populate the filter list combo box.
    filterListComboBoxPointer->addItem(i18nc("Filter list combo box items", "%1 - %2", filterListStructPointer->title, filterListStructPointer->version));
}

void FilterListsDialog::populateFilterListTextEdit(int filterListComboBoxIndex) const
{
    // Get the filter list struct.
    FilterListStruct *filterListStructPointer = getFilterListStruct(filterListComboBoxIndex);

    // Get the filter list file.
    QFile filterListFile(filterListStructPointer->filePath);

    // Open the filter list file.
    filterListFile.open(QIODevice::ReadOnly);

    // Create a filter list text stream.
    QTextStream filterListTextStream(&filterListFile);

    // Populate the text edit.
    filterListTextEditPointer->setText(filterListTextStream.readAll());

    // Clear the sublist combo box.
    sublistComboBoxPointer->clear();

    // Calculate the size of the filter lists.
    int mainAllowListSize = distance(filterListStructPointer->mainAllowListPointer->begin(), filterListStructPointer->mainAllowListPointer->end());
    int initialDomainAllowListSize = distance(filterListStructPointer->initialDomainAllowListPointer->begin(), filterListStructPointer->initialDomainAllowListPointer->end());
    int regularExpressionAllowListSize = distance(filterListStructPointer->regularExpressionAllowListPointer->begin(), filterListStructPointer->regularExpressionAllowListPointer->end());
    int mainBlockListSize = distance(filterListStructPointer->mainBlockListPointer->begin(), filterListStructPointer->mainBlockListPointer->end());
    int initialDomainBlockListSize = distance(filterListStructPointer->initialDomainBlockListPointer->begin(), filterListStructPointer->initialDomainBlockListPointer->end());
    int regularExpressionBlockListSize = distance(filterListStructPointer->regularExpressionBlockListPointer->begin(), filterListStructPointer->regularExpressionBlockListPointer->end());

    // Get the translated filter list names.
    QString mainAllowListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::MAIN_ALLOWLIST);
    QString initialDomainAllowListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::INITIAL_DOMAIN_ALLOWLIST);
    QString regularExpressionAllowListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::REGULAR_EXPRESSION_ALLOWLIST);
    QString mainBlockListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::MAIN_BLOCKLIST);
    QString initialDomainBlockListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::INITIAL_DOMAIN_BLOCKLIST);
    QString regularExpressionBlockListName = globalFilterListHelperPointer->getSublistName(FilterListHelper::REGULAR_EXPRESSION_BLOCKLIST);

    // Populate the sublist combo box.
    sublistComboBoxPointer->addItem(i18nc("The main allow list", "%1 - %2", mainAllowListName, mainAllowListSize));
    sublistComboBoxPointer->addItem(i18nc("The initial domain allow list", "%1 - %2", initialDomainAllowListName, initialDomainAllowListSize));
    sublistComboBoxPointer->addItem(i18nc("The regular expression allow list", "%1 - %2", regularExpressionAllowListName, regularExpressionAllowListSize));
    sublistComboBoxPointer->addItem(i18nc("The main block list", "%1 - %2", mainBlockListName, mainBlockListSize));
    sublistComboBoxPointer->addItem(i18nc("The initial domain block list", "%1 - %2", initialDomainBlockListName, initialDomainBlockListSize));
    sublistComboBoxPointer->addItem(i18nc("the regular expression block list", "%1 - %2", regularExpressionBlockListName, regularExpressionBlockListSize));
}

void FilterListsDialog::populateTableWidget(int sublistComboBoxIndex) const
{
    // Populate the table widget if there is at least one item in teh sublist combo box.
    if (sublistComboBoxIndex >= 0)
    {
        // Wipe the current table.
        sublistTableWidgetPointer->clear();
        sublistTableWidgetPointer->setColumnCount(0);
        sublistTableWidgetPointer->setRowCount(0);

        // Get the filter list struct.
        FilterListStruct *filterListStructPointer = getFilterListStruct(filterListComboBoxPointer->currentIndex());

        // Create a filter list forward list.
        std::forward_list<FilterListEntryStruct *> *filterListForwardListPointer;

        // Populate the filter list forward list.
        switch (sublistComboBoxIndex)
        {
            case FilterListHelper::MAIN_ALLOWLIST:  // The main allow list.
                filterListForwardListPointer = filterListStructPointer->mainAllowListPointer;
                break;

            case FilterListHelper::INITIAL_DOMAIN_ALLOWLIST:  // The initial domain allow list.
                filterListForwardListPointer = filterListStructPointer->initialDomainAllowListPointer;
                break;

            case FilterListHelper::REGULAR_EXPRESSION_ALLOWLIST:  // The regular expression allow list.
                filterListForwardListPointer = filterListStructPointer->regularExpressionAllowListPointer;
                break;

            case FilterListHelper::MAIN_BLOCKLIST:  // The main block list.
                filterListForwardListPointer = filterListStructPointer->mainBlockListPointer;
                break;

            case FilterListHelper::INITIAL_DOMAIN_BLOCKLIST:  // The initial domain block list.
                filterListForwardListPointer = filterListStructPointer->initialDomainBlockListPointer;
                break;

            case FilterListHelper::REGULAR_EXPRESSION_BLOCKLIST:  // the regular expression block list.
                filterListForwardListPointer = filterListStructPointer->regularExpressionBlockListPointer;
                break;
        }

        // Create the columns.
        sublistTableWidgetPointer->setColumnCount(22);

        // Create the table headers.
        QTableWidgetItem *appliedEntryListHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist applied entry list header", "Applied Entry List"));
        QTableWidgetItem *initialMatchHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist initial match header", "Initial Match"));
        QTableWidgetItem *finalMatchHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist final match header", "Final Match"));
        QTableWidgetItem *domainHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist domain header", "Domain"));
        QTableWidgetItem *domainListHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist domain list Header", "Domain List"));
        QTableWidgetItem *thirdPartyHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist third-party header", "Third Party"));
        QTableWidgetItem *hasRequestOptionsHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist has request options header", "Has Request Options"));
        QTableWidgetItem *fontHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist font header", "Font"));
        QTableWidgetItem *imageHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist image header", "Image"));
        QTableWidgetItem *mainFrameHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist main frame header", "Main Frame"));
        QTableWidgetItem *mediaHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist media header", "Media"));
        QTableWidgetItem *objectHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist object header", "Object"));
        QTableWidgetItem *otherHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist object header", "Other"));
        QTableWidgetItem *pingHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist ping header", "Ping"));
        QTableWidgetItem *scriptHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist script header", "Script"));
        QTableWidgetItem *styleSheetHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist style sheet header", "Style Sheet"));
        QTableWidgetItem *subFrameHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist sub frame header", "Sub Frame"));
        QTableWidgetItem *webSocketHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist web socket header", "Web Socket"));
        QTableWidgetItem *xmlHttpRequestHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist XML HTTP request header", "XML HTTP Request"));
        QTableWidgetItem *appliedFilterOptionsHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist applied filter options header", "Applied Filter Options"));
        QTableWidgetItem *originalFilterOptionsHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist original filter options header", "Original Filter Options"));
        QTableWidgetItem *originalEntryHeaderItemPointer = new QTableWidgetItem(i18nc("Sublist original entry header", "Original Entry"));

        // Set the horizontal headers.
        sublistTableWidgetPointer->setHorizontalHeaderItem(0, appliedEntryListHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(1, initialMatchHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(2, finalMatchHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(3, domainHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(4, domainListHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(5, thirdPartyHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(6, hasRequestOptionsHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(7, fontHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(8, imageHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(9, mainFrameHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(10, mediaHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(11, objectHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(12, otherHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(13, pingHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(14, scriptHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(15, styleSheetHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(16, subFrameHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(17, webSocketHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(18, xmlHttpRequestHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(19, appliedFilterOptionsHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(20, originalFilterOptionsHeaderItemPointer);
        sublistTableWidgetPointer->setHorizontalHeaderItem(21, originalEntryHeaderItemPointer);

        // Initialize the row counter.
        int rowCounter = 0;

        // Populate the table.
        for (auto filterListEntry = filterListForwardListPointer->begin(); filterListEntry != filterListForwardListPointer->end(); ++filterListEntry) {
            // Get the entry struct.
            FilterListEntryStruct *filterListEntryStructPointer = *filterListEntry;

            // Add a new row.
            sublistTableWidgetPointer->insertRow(rowCounter);

            // Create the entry items.
            QTableWidgetItem *appliedEntryListItemPointer = new QTableWidgetItem(filterListEntryStructPointer->appliedEntryList.join(QLatin1String("    ")));
            QTableWidgetItem *initialMatchItemPointer = new QTableWidgetItem(filterListEntryStructPointer->initialMatch ? i18n("Yes") : QLatin1String());
            QTableWidgetItem *finalMatchItemPointer = new QTableWidgetItem(filterListEntryStructPointer->finalMatch ? i18n("Yes") : QLatin1String());
            QTableWidgetItem *domainItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->domain));
            QTableWidgetItem *domainListItemPointer = new QTableWidgetItem(filterListEntryStructPointer->domainList.join(QLatin1String(" | ")));
            QTableWidgetItem *thirdPartyItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->thirdParty));
            QTableWidgetItem *hasRequestOptionsItemPointer = new QTableWidgetItem(filterListEntryStructPointer->hasRequestOptions ? i18n("Yes") : QLatin1String());
            QTableWidgetItem *fontItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->font));
            QTableWidgetItem *imageItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->image));
            QTableWidgetItem *mainFrameItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->mainFrame));
            QTableWidgetItem *mediaItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->media));
            QTableWidgetItem *objectItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->object));
            QTableWidgetItem *otherItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->other));
            QTableWidgetItem *pingItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->ping));
            QTableWidgetItem *scriptItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->script));
            QTableWidgetItem *styleSheetItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->styleSheet));
            QTableWidgetItem *subFrameItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->subFrame));
            QTableWidgetItem *webSocketItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->webSocket));
            QTableWidgetItem *xmlHttpRequestItemPointer = new QTableWidgetItem(globalFilterListHelperPointer->getRequestOptionDispositionString(filterListEntryStructPointer->xmlHttpRequest));
            QTableWidgetItem *appliedFilterOptionsItemPointer = new QTableWidgetItem(filterListEntryStructPointer->appliedFilterOptionsList.join(QLatin1String(" , ")));
            QTableWidgetItem *originalFilterOptionsItemPointer = new QTableWidgetItem(filterListEntryStructPointer->originalFilterOptionsString);
            QTableWidgetItem *originalEntryItemPointer = new QTableWidgetItem(filterListEntryStructPointer->originalEntryString);

            // Add the entries to the table.
            sublistTableWidgetPointer->setItem(rowCounter, 0, appliedEntryListItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 1, initialMatchItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 2, finalMatchItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 3, domainItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 4, domainListItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 5, thirdPartyItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 6, hasRequestOptionsItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 7, fontItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 8, imageItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 9, mainFrameItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 10, mediaItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 11, objectItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 12, otherItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 13, pingItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 14, scriptItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 15, styleSheetItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 16, subFrameItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 17, webSocketItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 18, xmlHttpRequestItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 19, appliedFilterOptionsItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 20, originalFilterOptionsItemPointer);
            sublistTableWidgetPointer->setItem(rowCounter, 21, originalEntryItemPointer);

            // Increment the row counter.
            ++rowCounter;
        }

        // Get the table header view.
        QHeaderView *headerViewPointer = sublistTableWidgetPointer->horizontalHeader();

        // Resize the header to fit the contents.
        headerViewPointer->resizeSections(QHeaderView::ResizeToContents);

        // Connect changes in the sort order.
        connect(headerViewPointer, SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, SLOT(sortTable(int, Qt::SortOrder)));
    }
}

void FilterListsDialog::showFilterEntryDialog(int row)
{
    // Get the filter list struct.
    FilterListStruct *filterListStructPointer = getFilterListStruct(filterListComboBoxPointer->currentIndex());

    // Get the sublist combo box index.
    int sublistComboBoxIndex = sublistComboBoxPointer->currentIndex();

    // Create a sublist title.
    QString sublistTitle = globalFilterListHelperPointer->getSublistName(sublistComboBoxIndex);

    // Determine if this is an allow list (which have indexes of 0-2).
    bool isAllowList = (sublistComboBoxIndex <= 2);

    // Instantiate the filter entry dialog.
    FilterEntryDialog *filterEntryDialogPointer = new FilterEntryDialog(this, sublistTableWidgetPointer, row, filterListStructPointer->title, sublistTitle, isAllowList);

    // Show the dialog.
    filterEntryDialogPointer->show();
}

void FilterListsDialog::sortTable(int column, Qt::SortOrder sortOrder) const
{
    // Sort the table.
    sublistTableWidgetPointer->sortItems(column, sortOrder);
}
