/*
 * Copyright © 2022 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 "DurableCookiesDialog.h"
#include "ui_DurableCookiesDialog.h"
#include "databases/CookiesDatabase.h"
#include "delegates/ViewOnlyDelegate.h"

// KDE Frameworks headers.
#include <KLocalizedString>

DurableCookiesDialog::DurableCookiesDialog() : QDialog(nullptr)
{
    // Set the dialog window title.
    setWindowTitle(i18nc("The durable cookies dialog window title", "Durable Cookies"));

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

    // Instantiate the durable cookies dialog UI.
    Ui::DurableCookiesDialog durableCookiesDialogUi;

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

    // Get a handle for the table view.
    QTableView *tableViewPointer = durableCookiesDialogUi.tableView;

    // Create the durable cookies table model.
    durableCookiesTableModelPointer = new QSqlTableModel(nullptr, QSqlDatabase::database(CookiesDatabase::CONNECTION_NAME));

    // Set the table.
    durableCookiesTableModelPointer->setTable(CookiesDatabase::COOKIES_TABLE);

    // Set the edit strategy.
    durableCookiesTableModelPointer->setEditStrategy(QSqlTableModel::OnManualSubmit);

    // Populate the model.
    durableCookiesTableModelPointer->select();

    // Set the model.
    tableViewPointer->setModel(durableCookiesTableModelPointer);

    // Instantiate the view only delegate.
    ViewOnlyDelegate *viewOnlyDelegatePointer = new ViewOnlyDelegate();

    // Disable editing the first column.
    tableViewPointer->setItemDelegateForColumn(0, viewOnlyDelegatePointer);

    // Optimize the width of the columns.
    tableViewPointer->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);

    // Get the seleciton model.
    tableSelectionModelPointer = tableViewPointer->selectionModel();

    // Update the UI when the selection changes.
    connect(tableSelectionModelPointer, SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(updateUi()));

    // Update the UI when the table model data changes.
    connect(durableCookiesTableModelPointer, SIGNAL(dataChanged(QModelIndex, QModelIndex)), this, SLOT(updateUi()));

    // Get handles for the buttons.
    QPushButton *addCookieButtonPointer = durableCookiesDialogUi.addCookieButton;
    deleteCookieButtonPointer = durableCookiesDialogUi.deleteCookieButton;
    deleteAllCookiesButtonPointer = durableCookiesDialogUi.deleteAllCookiesButton;
    QDialogButtonBox *dialogButtonBoxPointer = durableCookiesDialogUi.dialogButtonBox;
    resetButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Reset);
    applyButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Apply);
    QPushButton *okButtonPointer = dialogButtonBoxPointer->button(QDialogButtonBox::Ok);

    // Set the OK button to be the default.
    okButtonPointer->setDefault(true);

    // Connect the buttons.
    connect(addCookieButtonPointer, SIGNAL(clicked()), this, SLOT(addCookie()));
    connect(deleteCookieButtonPointer, SIGNAL(clicked()), this, SLOT(deleteCookie()));
    connect(deleteAllCookiesButtonPointer, SIGNAL(clicked()), this, SLOT(deleteAllCookies()));
    connect(resetButtonPointer, SIGNAL(clicked()), this, SLOT(reset()));
    connect(dialogButtonBoxPointer, SIGNAL(accepted()), this, SLOT(ok()));
    connect(applyButtonPointer, SIGNAL(clicked()), this, SLOT(apply()));
    connect(dialogButtonBoxPointer, SIGNAL(rejected()), this, SLOT(reject()));

    // Connect the table model signals.
    connect(durableCookiesTableModelPointer, SIGNAL(beforeDelete(int)), this, SLOT(beforeDelete(int)));
    connect(durableCookiesTableModelPointer, SIGNAL(beforeUpdate(int, QSqlRecord &)), this, SLOT(beforeUpdate(int, QSqlRecord &)));
    connect(durableCookiesTableModelPointer, SIGNAL(beforeInsert(QSqlRecord &)), this, SLOT(newCookie(QSqlRecord &)));

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::addCookie() const
{
    // Add a new row to the bottom of the table.
    durableCookiesTableModelPointer->insertRow(durableCookiesTableModelPointer->rowCount());

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::apply() const
{
    // Submit all pending changes.
    durableCookiesTableModelPointer->submitAll();

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::beforeDelete(int row) const
{
    // Get the SQL record for the row to be deleted.
    QSqlRecord sqlRecord = durableCookiesTableModelPointer->record(row);

    // Create a network cookie.
    QNetworkCookie cookie;

    // Populate the network cookie.
    cookie.setDomain(sqlRecord.value(CookiesDatabase::DOMAIN).toString());
    cookie.setName(sqlRecord.value(CookiesDatabase::NAME).toByteArray());
    cookie.setPath(sqlRecord.value(CookiesDatabase::PATH).toString());

    // Delete the cookie.
    emit deletingCookie(cookie);
}

void DurableCookiesDialog::beforeUpdate(int row, QSqlRecord &sqlRecord) const
{
    // Tell the compiler to ignore the unused row parameter.
    (void) row;

    // Delete the old cookie if the core attributes are changing.
    if (sqlRecord.isGenerated(CookiesDatabase::DOMAIN) || sqlRecord.isGenerated(CookiesDatabase::NAME) || sqlRecord.isGenerated(CookiesDatabase::PATH))
    {
        // Get the ID of the cookie
        int id = sqlRecord.value(CookiesDatabase::_ID).toInt();

        // Get the cookie.
        QNetworkCookie *cookiePointer = CookiesDatabase::getCookieById(id);

        // Delete the cookie.
        emit deletingCookie(*cookiePointer);
    }

    // Add the new cookie, which modifies any existing cookies with the same core attributes.
    newCookie(sqlRecord);
}

void DurableCookiesDialog::deleteAllCookies() const
{
    // Mark all the cookies for deletion.
    durableCookiesTableModelPointer->removeRows(0, durableCookiesTableModelPointer->rowCount());

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::deleteCookie() const
{
    // Delete the currently selected row.
    durableCookiesTableModelPointer->removeRow(tableSelectionModelPointer->selectedRows()[0].row());

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::newCookie(QSqlRecord &sqlRecord) const
{
    // Create a network cookie.
    QNetworkCookie cookie;

    // Populate the network cookie from the SQL record.
    cookie.setDomain(sqlRecord.value(CookiesDatabase::DOMAIN).toString());
    cookie.setName(sqlRecord.value(CookiesDatabase::NAME).toByteArray());
    cookie.setPath(sqlRecord.value(CookiesDatabase::PATH).toString());
    cookie.setExpirationDate(QDateTime::fromString(sqlRecord.value(CookiesDatabase::EXPIRATION_DATE).toString(), Qt::ISODate));
    cookie.setHttpOnly(sqlRecord.value(CookiesDatabase::HTTP_ONLY).toBool());
    cookie.setSecure(sqlRecord.value(CookiesDatabase::SECURE).toBool());
    cookie.setValue(sqlRecord.value(CookiesDatabase::VALUE).toByteArray());

    // Update the cookie in the cookies dialog tree, cookies list, and cookie store.
    emit addingCookie(cookie, false);
}

void DurableCookiesDialog::ok()
{
    // Submit all pending changes.
    durableCookiesTableModelPointer->submitAll();

    // Update the parent UI.
    updateParentUi();

    // Close the dialog.
    accept();
}

void DurableCookiesDialog::reset() const
{
    // Cancel all pending changes.
    durableCookiesTableModelPointer->revertAll();

    // Update the UI.
    updateUi();
}

void DurableCookiesDialog::updateUi() const
{
    // Update the delete button status.
    deleteCookieButtonPointer->setEnabled(tableSelectionModelPointer->hasSelection());

    // Update the delete all button status.
    deleteAllCookiesButtonPointer->setEnabled(durableCookiesTableModelPointer->rowCount() > 0);

    // Update the reset button status.
    resetButtonPointer->setEnabled(durableCookiesTableModelPointer->isDirty());

    // Update the apply button status.
    applyButtonPointer->setEnabled(durableCookiesTableModelPointer->isDirty());

    // Update the parent UI.
    emit updateParentUi();
}
