/* SPDX-License-Identifier: GPL-3.0-or-later
 * SPDX-FileCopyrightText: 2023-2024 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 "BookmarksDatabase.h"

// Define the private static schema constants.
const int BookmarksDatabase::SCHEMA_VERSION = 0;

// Define the public static constants.
const QString BookmarksDatabase::CONNECTION_NAME = QLatin1String("bookmarks_database");
const QString BookmarksDatabase::BOOKMARK_NAME = QLatin1String("bookmark_name");
const QString BookmarksDatabase::BOOKMARKS_TABLE = QLatin1String("bookmarks");
const QString BookmarksDatabase::BOOKMARK_URL = QLatin1String("bookmark_url");
const QString BookmarksDatabase::DISPLAY_ORDER = QLatin1String("display_order");
const QString BookmarksDatabase::FAVORITE_ICON = QLatin1String("favorite_icon");
const QString BookmarksDatabase::FOLDER_ID = QLatin1String("folder_id");
const QString BookmarksDatabase::ID = QLatin1String("_id");
const QString BookmarksDatabase::IS_FOLDER = QLatin1String("is_folder");
const QString BookmarksDatabase::PARENT_FOLDER_ID = QLatin1String("parent_folder_id");

// Construct the class.
BookmarksDatabase::BookmarksDatabase() {}

void BookmarksDatabase::addDatabase()
{
    // Add the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), CONNECTION_NAME);

    // Set the database name.
    bookmarksDatabase.setDatabaseName(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/bookmarks.db"));

    // Open the database.
    if (bookmarksDatabase.open())  // Opening the database succeeded.
    {
        // Check to see if the bookmarks table already exists.
        if (bookmarksDatabase.tables().contains(BOOKMARKS_TABLE))  // The bookmarks table already exists.
        {
            // Create a schema version query.
            QSqlQuery schemaVersionQuery(bookmarksDatabase);

            // Query the database schema version.
            schemaVersionQuery.exec(QLatin1String("PRAGMA user_version"));

            // Move to the first record.
            schemaVersionQuery.first();

            // Get the current schema version.
            int currentSchemaVersion = schemaVersionQuery.value(0).toInt();

            // Check to see if the schema has been updated.
            if (currentSchemaVersion < SCHEMA_VERSION)
            {
                // Run the schema update code.

                // Create an update schema version query.
                QSqlQuery updateSchemaVersionQuery(bookmarksDatabase);

                // Update the schema version.
                updateSchemaVersionQuery.exec(QLatin1String("PRAGMA user_version = ") + QString::number(SCHEMA_VERSION));
            }
        }
        else  // The bookmarks table does not exist.
        {
            // Instantiate a create table query.
            QSqlQuery createTableQuery(bookmarksDatabase);

            // Populate the create table query.
            createTableQuery.prepare(QLatin1String("CREATE TABLE ") + BOOKMARKS_TABLE + QLatin1String("(") +
                                      ID + QLatin1String(" INTEGER PRIMARY KEY, ") +
                                      BOOKMARK_NAME + QLatin1String(" TEXT, ") +
                                      BOOKMARK_URL + QLatin1String(" TEXT, ") +
                                      PARENT_FOLDER_ID + QLatin1String(" INTEGER DEFAULT 0, ") +
                                      DISPLAY_ORDER + QLatin1String(" INTEGER DEFAULT 0, ") +
                                      IS_FOLDER + QLatin1String(" BOOLEAN DEFAULT FALSE, ") +
                                      FOLDER_ID + QLatin1String(" INTEGER DEFAULT 0, ") +
                                      FAVORITE_ICON + QLatin1String(" BLOB)"));

            // Execute the query.
            if (!createTableQuery.exec())
            {
                // Log any errors.
                qDebug().noquote().nospace() << "Error creating table:  " << bookmarksDatabase.lastError();
            }

            // Create an update schema version query.
            QSqlQuery updateSchemaVersionQuery(bookmarksDatabase);

            // Set the schema version.
            updateSchemaVersionQuery.exec(QLatin1String("PRAGMA user_version = ") + QString::number(SCHEMA_VERSION));
        }
    }
    else  // Opening the database failed.
    {
        // Write the last database error message to the debug output.
        qDebug().noquote().nospace() << "Error opening database:  " << bookmarksDatabase.lastError();
    }
};

void BookmarksDatabase::addBookmark(const BookmarkStruct *bookmarkStructPointer)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Get the folder item count.
    int folderItemCount = getFolderItemCount(bookmarkStructPointer->parentFolderId);

    // Instantiate an add bookmark query.
    QSqlQuery addBookmarkQuery(bookmarksDatabase);

    // Prepare the add bookmark query.
    addBookmarkQuery.prepare(QLatin1String("INSERT INTO ") + BOOKMARKS_TABLE + QLatin1String(" (") +
                              BOOKMARK_NAME + QLatin1String(", ") +
                              BOOKMARK_URL + QLatin1String(", ") +
                              PARENT_FOLDER_ID + QLatin1String(", ") +
                              DISPLAY_ORDER + QLatin1String(", ") +
                              FAVORITE_ICON + QLatin1String(") ") +
                              QLatin1String("VALUES (:bookmark_name, :bookmark_url, :parent_folder_id, :display_order, :favorite_icon)"));

    // Bind the query values.
    addBookmarkQuery.bindValue(QLatin1String(":bookmark_name"), bookmarkStructPointer->name);
    addBookmarkQuery.bindValue(QLatin1String(":bookmark_url"), bookmarkStructPointer->url);
    addBookmarkQuery.bindValue(QLatin1String(":parent_folder_id"), bookmarkStructPointer->parentFolderId);
    addBookmarkQuery.bindValue(QLatin1String(":display_order"), folderItemCount);
    addBookmarkQuery.bindValue(QLatin1String(":favorite_icon"), getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));

    // Execute the add bookmark query.
    addBookmarkQuery.exec();
}

void BookmarksDatabase::addFolder(const BookmarkStruct *bookmarkStructPointer)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Get the folder item count.
    int folderItemCount = getFolderItemCount(bookmarkStructPointer->parentFolderId);

    // Instantiate an add folder query.
    QSqlQuery addFolderQuery(bookmarksDatabase);

    // Prepare the add folder query.
    addFolderQuery.prepare(QLatin1String("INSERT INTO ") + BOOKMARKS_TABLE + QLatin1String(" (") +
                              BOOKMARK_NAME + QLatin1String(", ") +
                              PARENT_FOLDER_ID + QLatin1String(", ") +
                              DISPLAY_ORDER + QLatin1String(", ") +
                              IS_FOLDER + QLatin1String(", ") +
                              FOLDER_ID + QLatin1String(", ") +
                              FAVORITE_ICON + QLatin1String(") ") +
                              QLatin1String("VALUES (:bookmark_name, :parent_folder_id, :display_order, :is_folder, :folder_id, :favorite_icon)"));

    // Bind the query values.
    addFolderQuery.bindValue(QLatin1String(":bookmark_name"), bookmarkStructPointer->name);
    addFolderQuery.bindValue(QLatin1String(":parent_folder_id"), bookmarkStructPointer->parentFolderId);
    addFolderQuery.bindValue(QLatin1String(":display_order"), folderItemCount);
    addFolderQuery.bindValue(QLatin1String(":is_folder"), 1);
    addFolderQuery.bindValue(QLatin1String(":folder_id"), generateFolderId());
    addFolderQuery.bindValue(QLatin1String(":favorite_icon"), getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));

    // Execute the add folder query.
    addFolderQuery.exec();
}

void BookmarksDatabase::deleteBookmark(const int databaseId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a delete bookmark query.
    QSqlQuery deleteBookmarkQuery(bookmarksDatabase);

    // Prepare the delete bookmark query.
    deleteBookmarkQuery.prepare(QLatin1String("DELETE FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    deleteBookmarkQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    deleteBookmarkQuery.exec();
}

void BookmarksDatabase::deleteBookmarks(const QString url)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a parent folder IDs query.
    QSqlQuery parentFolderIdsQuery(bookmarksDatabase);

    // Prepare the parent folder IDs query.
    parentFolderIdsQuery.prepare(QLatin1String("SELECT ") + PARENT_FOLDER_ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + IS_FOLDER + QLatin1String(" = 0 AND ") +
                                 BOOKMARK_URL + QLatin1String(" = :url"));

    // Bind the query values.
    parentFolderIdsQuery.bindValue(QLatin1String(":url"), url);

    // Execute the query.
    parentFolderIdsQuery.exec();

    // Instantiate a delete bookmarks query.
    QSqlQuery deleteBookmarksQuery(bookmarksDatabase);

    // Prepare the delete bookmark query.
    deleteBookmarksQuery.prepare(QLatin1String("DELETE FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + IS_FOLDER + QLatin1String(" = 0 AND ") + BOOKMARK_URL + QLatin1String(" = :url"));

    // Bind the query values.
    deleteBookmarksQuery.bindValue(QLatin1String(":url"), url);

    // Execute the query.
    deleteBookmarksQuery.exec();

    // Create a parent folder IDs list.  A standard list can be sorted and deduplicated.
    std::list<double> parentFolderIdsList;

    // Populate the parent folder IDs list.
    while (parentFolderIdsQuery.next())
    {
        // Add the parent folder ID to the list.
        parentFolderIdsList.push_back(parentFolderIdsQuery.value(PARENT_FOLDER_ID).toDouble());
    }

    // Sort the parent folder IDs list.
    parentFolderIdsList.sort();

    // Remove duplicate entries from the parent folder IDs list.
    parentFolderIdsList.unique();

    // Update the display order of each folder where a bookmark was deleted.
    for (const double parentFolderId : parentFolderIdsList)
        updateFolderContentsDisplayOrder(parentFolderId);
}

double BookmarksDatabase::generateFolderId()
{
    // Get the current time in epoch format (milliseconds).
    double possibleFolderId = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a existing folder query.
    QSqlQuery existingFolderQuery(bookmarksDatabase);

    // Prepare the existing folder query.
    existingFolderQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + FOLDER_ID + QLatin1String(" = :possible_folder_id"));

    // Bind the query values.
    existingFolderQuery.bindValue(QLatin1String(":possible_folder_id"), possibleFolderId);

    // Execute the query.
    existingFolderQuery.exec();

    // Generate a new folder ID if this one is not unique.  The existing folder query will only be valid if there is at least one item.
    if (existingFolderQuery.isValid())
        possibleFolderId = generateFolderId();

    return possibleFolderId;
}

QList<QString>* BookmarksDatabase::getAllFolderUrls(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder URLs query.
    QSqlQuery folderUrlsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderUrlsQuery.setForwardOnly(true);

    // Prepare the folder URLs query.
    folderUrlsQuery.prepare(QLatin1String("SELECT ") + BOOKMARK_URL + QLatin1String(", ") + IS_FOLDER + QLatin1String(", ") + FOLDER_ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE +
                            QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id"));

    // Bind the query values.
    folderUrlsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderUrlsQuery.exec();

    // Create a folder URLs list.
    QList<QString> *folderUrlsListPointer = new QList<QString>;

    // Populate the folder URLs list.
    while (folderUrlsQuery.next())
    {
        // Process the entry according to the type.
        if (folderUrlsQuery.value(IS_FOLDER).toBool())  // This is a folder.
        {
            // Get the subfolder URLs to the list.
            folderUrlsListPointer->append(*getAllFolderUrls(folderUrlsQuery.value(FOLDER_ID).toDouble()));
        }
        else  // This is a bookmark.
        {
            // Add the URL to the list.
            folderUrlsListPointer->append(folderUrlsQuery.value(BOOKMARK_URL).toString());
        }
    }

    // Return the folder URLs list.
    return folderUrlsListPointer;
}

BookmarkStruct* BookmarksDatabase::getBookmark(const int databaseId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a bookmark query.
    QSqlQuery bookmarkQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    bookmarkQuery.setForwardOnly(true);

    // Prepare the bookmark query.
    bookmarkQuery.prepare(QLatin1String("SELECT * FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    bookmarkQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    bookmarkQuery.exec();

    // Move to the first entry.
    bookmarkQuery.first();

    // Create a bookmark struct.
    struct BookmarkStruct *bookmarkStructPointer = new BookmarkStruct();

    // Get the favorite icon base 64 byte array.
    QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarkQuery.value(FAVORITE_ICON).toByteArray());

    // Create a favorite icon pixmap.
    QPixmap favoriteIconPixmap;

    // Load the pixmap from byte array.
    favoriteIconPixmap.loadFromData(favoriteIconByteArray);

    // Populate the bookmark struct.
    bookmarkStructPointer->databaseId = bookmarkQuery.value(ID).toInt();
    bookmarkStructPointer->name = bookmarkQuery.value(BOOKMARK_NAME).toString();
    bookmarkStructPointer->url = bookmarkQuery.value(BOOKMARK_URL).toString();
    bookmarkStructPointer->parentFolderId = bookmarkQuery.value(PARENT_FOLDER_ID).toDouble();
    bookmarkStructPointer->displayOrder = bookmarkQuery.value(DISPLAY_ORDER).toInt();
    bookmarkStructPointer->isFolder = bookmarkQuery.value(IS_FOLDER).toBool();
    bookmarkStructPointer->folderId = bookmarkQuery.value(FOLDER_ID).toDouble();
    bookmarkStructPointer->favoriteIcon = QIcon(favoriteIconPixmap);

    // Return the bookmark struct pointer.
    return bookmarkStructPointer;
}

std::list<BookmarkStruct>* BookmarksDatabase::getBookmarks()
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a bookmarks query.
    QSqlQuery bookmarksQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    bookmarksQuery.setForwardOnly(true);

    // Prepare the bookmarks query.
    bookmarksQuery.prepare(QLatin1String("SELECT * FROM ") + BOOKMARKS_TABLE + QLatin1String(" ORDER BY ") + DISPLAY_ORDER + QLatin1String(" ASC"));

    // Execute the query.
    bookmarksQuery.exec();

    // Create a bookmark list.
    std::list<BookmarkStruct> *bookmarkListPointer = new std::list<BookmarkStruct>;

    // Populate the bookmark list.
    while (bookmarksQuery.next())
    {
        // Create a bookmark struct.
        struct BookmarkStruct bookmarkStruct;

        // Get the favorite icon base 64 byte array.
        QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarksQuery.value(FAVORITE_ICON).toByteArray());

        // Create a favorite icon pixmap.
        QPixmap favoriteIconPixmap;

        // Load the pixmap from byte array.
        favoriteIconPixmap.loadFromData(favoriteIconByteArray);

        // Populate the bookmark struct.
        bookmarkStruct.databaseId = bookmarksQuery.value(ID).toInt();
        bookmarkStruct.name = bookmarksQuery.value(BOOKMARK_NAME).toString();
        bookmarkStruct.url = bookmarksQuery.value(BOOKMARK_URL).toString();
        bookmarkStruct.parentFolderId = bookmarksQuery.value(PARENT_FOLDER_ID).toDouble();
        bookmarkStruct.displayOrder = bookmarksQuery.value(DISPLAY_ORDER).toInt();
        bookmarkStruct.isFolder = bookmarksQuery.value(IS_FOLDER).toBool();
        bookmarkStruct.folderId = bookmarksQuery.value(FOLDER_ID).toDouble();
        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);

        // Add the bookmark to the list.
        bookmarkListPointer->push_back(bookmarkStruct);
    }

    // Return the bookmark list.
    return bookmarkListPointer;
}

QList<BookmarkStruct>* BookmarksDatabase::getBookmarksInFolderExcept(const double folderId, QList<int> *exceptDatabaseIdsListPointer)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a bookmarks query.
    QSqlQuery bookmarksQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    bookmarksQuery.setForwardOnly(true);

    // Create an IDs not to get string.
    QString idsNotToGetString;

    for (const int databaseId : *exceptDatabaseIdsListPointer)
    {
        // Check to see if there the string already has at least one number.
        if (!idsNotToGetString.isEmpty())
        {
            // This is not the first number, so add a `,`.
            idsNotToGetString.append(QLatin1Char(','));
        }

        // Append the database ID.
        idsNotToGetString.append(QString::number(databaseId));
    }

    // Prepare the bookmarks query.
    bookmarksQuery.prepare(QLatin1String("SELECT * FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id AND ") + ID +
                           QLatin1String(" NOT IN (") + idsNotToGetString + QLatin1String(") ORDER BY ") + DISPLAY_ORDER + QLatin1String(" ASC"));

    // Bind the query values.
    bookmarksQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    bookmarksQuery.exec();

    // Create a bookmark list.
    QList<BookmarkStruct> *bookmarkListPointer = new QList<BookmarkStruct>;

    // Populate the bookmark list.
    while (bookmarksQuery.next())
    {
        // Create a bookmark struct.
        struct BookmarkStruct bookmarkStruct;

        // Get the favorite icon base 64 byte array.
        QByteArray favoriteIconByteArray = QByteArray::fromBase64(bookmarksQuery.value(FAVORITE_ICON).toByteArray());

        // Create a favorite icon pixmap.
        QPixmap favoriteIconPixmap;

        // Load the pixmap from byte array.
        favoriteIconPixmap.loadFromData(favoriteIconByteArray);

        // Populate the bookmark struct.
        bookmarkStruct.databaseId = bookmarksQuery.value(ID).toInt();
        bookmarkStruct.name = bookmarksQuery.value(BOOKMARK_NAME).toString();
        bookmarkStruct.url = bookmarksQuery.value(BOOKMARK_URL).toString();
        bookmarkStruct.parentFolderId = bookmarksQuery.value(PARENT_FOLDER_ID).toDouble();
        bookmarkStruct.displayOrder = bookmarksQuery.value(DISPLAY_ORDER).toInt();
        bookmarkStruct.isFolder = bookmarksQuery.value(IS_FOLDER).toBool();
        bookmarkStruct.folderId = bookmarksQuery.value(FOLDER_ID).toDouble();
        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);

        // Add the bookmark to the list.
        bookmarkListPointer->push_back(bookmarkStruct);
    }

    // Return the bookmark list.
    return bookmarkListPointer;
}

QString BookmarksDatabase::getFavoriteIconBase64String(const QIcon &favoriteIcon)
{
    // Get a favorite icon pixmap.
    QPixmap favoriteIconPixmap = favoriteIcon.pixmap(32, 32);

    // Create a favorite icon byte array.
    QByteArray favoriteIconByteArray;

    // Create a favorite icon buffer.
    QBuffer favoriteIconBuffer(&favoriteIconByteArray);

    // Open the buffer.
    favoriteIconBuffer.open(QIODevice::WriteOnly);

    // Convert the favorite icon pixmap into a byte array in PNG format.
    favoriteIconPixmap.save(&favoriteIconBuffer, "PNG");

    // Close the buffer.
    favoriteIconBuffer.close();

    // Convert the favorite icon byte array to a base 64 string.
    QString favoriteIconBase64String = QLatin1String(favoriteIconByteArray.toBase64());

    // Return the favorite icon base 64 string.
    return favoriteIconBase64String;
}

QList<BookmarkStruct>* BookmarksDatabase::getFolderContents(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder contents query.
    QSqlQuery folderContentsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderContentsQuery.setForwardOnly(true);

    // Prepare the folder contents query.
    folderContentsQuery.prepare(QLatin1String("SELECT * FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id ORDER BY ") + DISPLAY_ORDER +
                                QLatin1String(" ASC"));

    // Bind the query values.
    folderContentsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderContentsQuery.exec();

    // Create a folder contents list.
    QList<BookmarkStruct> *folderContentsListPointer = new QList<BookmarkStruct>;

    // Populate the folder contents list.
    while (folderContentsQuery.next())
    {
        // Create a bookmark struct.
        struct BookmarkStruct bookmarkStruct;

        // Get the favorite icon base 64 byte array.
        QByteArray favoriteIconByteArray = QByteArray::fromBase64(folderContentsQuery.value(FAVORITE_ICON).toByteArray());

        // Create a favorite icon pixmap.
        QPixmap favoriteIconPixmap;

        // Load the pixmap from byte array.
        favoriteIconPixmap.loadFromData(favoriteIconByteArray);

        // Populate the bookmark struct.
        bookmarkStruct.databaseId = folderContentsQuery.value(ID).toInt();
        bookmarkStruct.name = folderContentsQuery.value(BOOKMARK_NAME).toString();
        bookmarkStruct.url = folderContentsQuery.value(BOOKMARK_URL).toString();
        bookmarkStruct.parentFolderId = folderContentsQuery.value(PARENT_FOLDER_ID).toDouble();
        bookmarkStruct.displayOrder = folderContentsQuery.value(DISPLAY_ORDER).toInt();
        bookmarkStruct.isFolder = folderContentsQuery.value(IS_FOLDER).toBool();
        bookmarkStruct.folderId = folderContentsQuery.value(FOLDER_ID).toDouble();
        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);

        // Add the item to the list.
        folderContentsListPointer->append(bookmarkStruct);
    }

    // Return the folder contents list.
    return folderContentsListPointer;
}

QList<int>* BookmarksDatabase::getFolderContentsDatabaseIds(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder contents query.
    QSqlQuery folderContentsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderContentsQuery.setForwardOnly(true);

    // Prepare the folder contents query.
    folderContentsQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id"));

    // Bind the query values.
    folderContentsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderContentsQuery.exec();

    // Create a folder contents database ID list.
    QList<int> *folderContentsDatabaseIdsListPointer = new QList<int>;

    // Populate the folder contents list.
    while (folderContentsQuery.next())
    {
        // Add the database ID to the list.
        folderContentsDatabaseIdsListPointer->append(folderContentsQuery.value(ID).toInt());
    }

    // Return the folder contents database ID list.
    return folderContentsDatabaseIdsListPointer;
}

QList<int> *BookmarksDatabase::getFolderContentsDatabaseIdsRecursively(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder contents query.
    QSqlQuery folderContentsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderContentsQuery.setForwardOnly(true);

    // Prepare the folder contents query.
    folderContentsQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(", ") + IS_FOLDER + QLatin1String(", ") + FOLDER_ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE +
                                QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id"));

    // Bind the query values.
    folderContentsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderContentsQuery.exec();

    // Create a folder contents database ID list.
    QList<int> *folderContentsDatabaseIdsListPointer = new QList<int>;

    // Populate the folder contents list.
    while (folderContentsQuery.next())
    {
        // Add the database ID to the list.
        folderContentsDatabaseIdsListPointer->append(folderContentsQuery.value(ID).toInt());

        // Recursively get the contents if this is a subfolder.
        if (folderContentsQuery.value(IS_FOLDER).toBool())
            folderContentsDatabaseIdsListPointer->append(*getFolderContentsDatabaseIdsRecursively(folderContentsQuery.value(FOLDER_ID).toDouble()));
    }

    // Return the folder contents database ID list.
    return folderContentsDatabaseIdsListPointer;
}

int BookmarksDatabase::getFolderDatabaseId(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder database ID query.
    QSqlQuery folderDatabaseIdQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderDatabaseIdQuery.setForwardOnly(true);

    // Prepare the folder database ID query.
    folderDatabaseIdQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + FOLDER_ID + QLatin1String(" = :folder_id"));

    // Bind the query values.
    folderDatabaseIdQuery.bindValue(QLatin1String(":folder_id"), folderId);

    // Execute the query.
    folderDatabaseIdQuery.exec();

    // Move to the first entry.
    folderDatabaseIdQuery.first();

    // Return the folder database ID.
    return folderDatabaseIdQuery.value(ID).toInt();
}

double BookmarksDatabase::getFolderId(const int databaseId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder ID query.
    QSqlQuery folderIdQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderIdQuery.setForwardOnly(true);

    // Prepare the folder ID query.
    folderIdQuery.prepare(QLatin1String("SELECT ") + FOLDER_ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + ID + QLatin1String(" = :database_id"));

    // Bind the query values.
    folderIdQuery.bindValue(QLatin1String(":database_id"), databaseId);

    // Execute the query.
    folderIdQuery.exec();

    // Move to the first entry.
    folderIdQuery.first();

    // Return the folder ID.
    return folderIdQuery.value(FOLDER_ID).toDouble();
}

int BookmarksDatabase::getFolderItemCount(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder contents query.
    QSqlQuery folderContentsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderContentsQuery.setForwardOnly(true);

    // Prepare the folder contents query.
    folderContentsQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id"));

    // Bind the query values.
    folderContentsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderContentsQuery.exec();

    // Move to the last row.
    folderContentsQuery.last();

    // Initialize an item count variable.
    int itemCount = 0;

    // Check to see if the query is valid (there is at least one item).
    if (folderContentsQuery.isValid())
    {
        // Get the number of rows (which is zero based) and add one to calculate the number of bookmarks.
        itemCount = folderContentsQuery.at() + 1;
    }

    // Return the item count.
    return itemCount;
}

double BookmarksDatabase::getParentFolderId(const int databaseId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a parent folder ID query.
    QSqlQuery parentFolderIdQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    parentFolderIdQuery.setForwardOnly(true);

    // Prepare the parent folder ID query.
    parentFolderIdQuery.prepare(QLatin1String("SELECT ") + PARENT_FOLDER_ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + ID + QLatin1String(" = :database_id"));

    // Bind the query values.
    parentFolderIdQuery.bindValue(QLatin1String(":database_id"), databaseId);

    // Execute the query.
    parentFolderIdQuery.exec();

    // Move to the first entry.
    parentFolderIdQuery.first();

    // Return the parent folder ID.
    return parentFolderIdQuery.value(PARENT_FOLDER_ID).toDouble();
}

QList<BookmarkStruct>* BookmarksDatabase::getSubfolders(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a subfolders query.
    QSqlQuery subfoldersQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    subfoldersQuery.setForwardOnly(true);

    // Prepare the subfolders query.
    subfoldersQuery.prepare(QLatin1String("SELECT * FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + IS_FOLDER + QLatin1String(" = 1 AND ") + PARENT_FOLDER_ID +
                            QLatin1String(" = :parent_folder_id ORDER BY ") + DISPLAY_ORDER + QLatin1String(" ASC"));

    // Bind the query values.
    subfoldersQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    subfoldersQuery.exec();

    // Create a subfolder list.
    QList<BookmarkStruct> *subfoldersListPointer = new QList<BookmarkStruct>;

    // Populate the subfolder list.
    while (subfoldersQuery.next())
    {
        // Create a bookmark struct.
        struct BookmarkStruct bookmarkStruct;

        // Get the favorite icon base 64 byte array.
        QByteArray favoriteIconByteArray = QByteArray::fromBase64(subfoldersQuery.value(FAVORITE_ICON).toByteArray());

        // Create a favorite icon pixmap.
        QPixmap favoriteIconPixmap;

        // Load the pixmap from byte array.
        favoriteIconPixmap.loadFromData(favoriteIconByteArray);

        // Populate the bookmark struct.
        bookmarkStruct.databaseId = subfoldersQuery.value(ID).toInt();
        bookmarkStruct.name = subfoldersQuery.value(BOOKMARK_NAME).toString();
        bookmarkStruct.parentFolderId = subfoldersQuery.value(PARENT_FOLDER_ID).toDouble();
        bookmarkStruct.displayOrder = subfoldersQuery.value(DISPLAY_ORDER).toInt();
        bookmarkStruct.isFolder = subfoldersQuery.value(IS_FOLDER).toBool();
        bookmarkStruct.folderId = subfoldersQuery.value(FOLDER_ID).toDouble();
        bookmarkStruct.favoriteIcon = QIcon(favoriteIconPixmap);

        // Add the subfolder to the list.
        subfoldersListPointer->append(bookmarkStruct);
    }

    // Return the subfolders list.
    return subfoldersListPointer;
}

bool BookmarksDatabase::isBookmarked(const QString url)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an is bookmarked query.
    QSqlQuery isBookmarkedQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    isBookmarkedQuery.setForwardOnly(true);

    // Prepare the is bookmarked query.
    isBookmarkedQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + IS_FOLDER + QLatin1String(" = 0 AND ") + BOOKMARK_URL +
                              QLatin1String(" = :url"));

    // Bind the query values.
    isBookmarkedQuery.bindValue(QLatin1String(":url"), url);

    // Execute the query.
    isBookmarkedQuery.exec();

    // Move to the first entry.
    isBookmarkedQuery.first();

    // Return true if the query is valid (there is at least one item).
    return isBookmarkedQuery.isValid();
}

bool BookmarksDatabase::isFolder(const int databaseId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an is folder query.
    QSqlQuery isFolderQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    isFolderQuery.setForwardOnly(true);

    // Prepare the is folder query.
    isFolderQuery.prepare(QLatin1String("SELECT ") + IS_FOLDER + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    isFolderQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    isFolderQuery.exec();

    // Move to the first entry.
    isFolderQuery.first();

    // Return the folder status.
    return isFolderQuery.value(IS_FOLDER).toBool();
}

void BookmarksDatabase::updateBookmark(const BookmarkStruct *bookmarkStructPointer)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an update bookmark query.
    QSqlQuery updateBookmarkQuery(bookmarksDatabase);

    // Prepare the update bookmark query.
    updateBookmarkQuery.prepare(QLatin1String("UPDATE ") + BOOKMARKS_TABLE + QLatin1String(" SET ") +
                                BOOKMARK_NAME + QLatin1String(" = :bookmark_name, ") +
                                BOOKMARK_URL + QLatin1String(" = :bookmark_url, ") +
                                PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id, ") +
                                DISPLAY_ORDER + QLatin1String(" = :display_order, ") +
                                FAVORITE_ICON + QLatin1String("= :favorite_icon ") +
                                QLatin1String("WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    updateBookmarkQuery.bindValue(QLatin1String(":bookmark_name"), bookmarkStructPointer->name);
    updateBookmarkQuery.bindValue(QLatin1String(":bookmark_url"), bookmarkStructPointer->url);
    updateBookmarkQuery.bindValue(QLatin1String(":parent_folder_id"), bookmarkStructPointer->parentFolderId);
    updateBookmarkQuery.bindValue(QLatin1String(":display_order"), bookmarkStructPointer->displayOrder);
    updateBookmarkQuery.bindValue(QLatin1String(":favorite_icon"), getFavoriteIconBase64String(bookmarkStructPointer->favoriteIcon));
    updateBookmarkQuery.bindValue(QLatin1String(":id"), bookmarkStructPointer->databaseId);

    // Execute the query.
    updateBookmarkQuery.exec();
}

void BookmarksDatabase::updateBookmarkName(const int databaseId, const QString &bookmarkName)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an update bookmark name query.
    QSqlQuery updateBookmarkNameQuery(bookmarksDatabase);

    // Prepare the update bookmark name query.
    updateBookmarkNameQuery.prepare(QLatin1String("UPDATE ") + BOOKMARKS_TABLE +
                                    QLatin1String(" SET ") + BOOKMARK_NAME + QLatin1String(" = :bookmark_name ") +
                                    QLatin1String("WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    updateBookmarkNameQuery.bindValue(QLatin1String(":bookmark_name"), bookmarkName);
    updateBookmarkNameQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    updateBookmarkNameQuery.exec();
}

void BookmarksDatabase::updateBookmarkUrl(const int databaseId, const QString &bookmarkUrl)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an update bookmark URL query.
    QSqlQuery updateBookmarkUrlQuery(bookmarksDatabase);

    // Prepare the update bookmark URL query.
    updateBookmarkUrlQuery.prepare(QLatin1String("UPDATE ") + BOOKMARKS_TABLE +
                                   QLatin1String(" SET ") + BOOKMARK_URL + QLatin1String(" = :bookmark_url ") +
                                   QLatin1String("WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    updateBookmarkUrlQuery.bindValue(QLatin1String(":bookmark_url"), bookmarkUrl);
    updateBookmarkUrlQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    updateBookmarkUrlQuery.exec();
}

void BookmarksDatabase::updateDisplayOrder(const int databaseId, const int displayOrder)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an update bookmark display order query.
    QSqlQuery updateBookmarkDisplayOrderQuery(bookmarksDatabase);

    // Prepare the update bookmark display order query.
    updateBookmarkDisplayOrderQuery.prepare(QLatin1String("UPDATE ") + BOOKMARKS_TABLE +
                                            QLatin1String(" SET ") + DISPLAY_ORDER + QLatin1String(" = :display_order ") +
                                            QLatin1String("WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    updateBookmarkDisplayOrderQuery.bindValue(QLatin1String(":display_order"), displayOrder);
    updateBookmarkDisplayOrderQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    updateBookmarkDisplayOrderQuery.exec();
}

void BookmarksDatabase::updateFolderContentsDisplayOrder(const double folderId)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate a folder contents query.
    QSqlQuery folderContentsQuery(bookmarksDatabase);

    // Set the query to be forward only, which is more performant.
    folderContentsQuery.setForwardOnly(true);

    // Prepare the folder contents query.
    folderContentsQuery.prepare(QLatin1String("SELECT ") + ID + QLatin1String(", ") + DISPLAY_ORDER + QLatin1String(" FROM ") + BOOKMARKS_TABLE + QLatin1String(" WHERE ") + PARENT_FOLDER_ID +
                                QLatin1String(" = :parent_folder_id ORDER BY ") + DISPLAY_ORDER + QLatin1String(" ASC"));

    // Bind the query values.
    folderContentsQuery.bindValue(QLatin1String(":parent_folder_id"), folderId);

    // Execute the query.
    folderContentsQuery.exec();

    // Define a new display order int.
    int newDisplayOrder = 0;

    // Populate the subfolder list.
    while (folderContentsQuery.next())
    {
        // Update the display order if it has changed.
        if (folderContentsQuery.value(DISPLAY_ORDER).toInt() != newDisplayOrder)
            updateDisplayOrder(folderContentsQuery.value(ID).toInt(), newDisplayOrder);

        // Increment the new display order.
        ++newDisplayOrder;
    }
}

void BookmarksDatabase::updateParentFolderAndDisplayOrder(const int databaseId, const double parentFolderId, const int displayOrder)
{
    // Get a handle for the bookmarks database.
    QSqlDatabase bookmarksDatabase = QSqlDatabase::database(CONNECTION_NAME);

    // Instantiate an update bookmark display order query.
    QSqlQuery updateBookmarkDisplayOrderQuery(bookmarksDatabase);

    // Prepare the update bookmark display order query.
    updateBookmarkDisplayOrderQuery.prepare(QLatin1String("UPDATE ") + BOOKMARKS_TABLE +
                                            QLatin1String(" SET ") + PARENT_FOLDER_ID + QLatin1String(" = :parent_folder_id ,") +
                                            DISPLAY_ORDER + QLatin1String(" = :display_order ") +
                                            QLatin1String("WHERE ") + ID + QLatin1String(" = :id"));

    // Bind the query values.
    updateBookmarkDisplayOrderQuery.bindValue(QLatin1String(":parent_folder_id"), parentFolderId);
    updateBookmarkDisplayOrderQuery.bindValue(QLatin1String(":display_order"), displayOrder);
    updateBookmarkDisplayOrderQuery.bindValue(QLatin1String(":id"), databaseId);

    // Execute the query.
    updateBookmarkDisplayOrderQuery.exec();
}
