/* 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 "DraggableTreeView.h"
#include "databases/BookmarksDatabase.h"
#include "dialogs/BookmarksDialog.h"

// Qt toolkit headers.
#include <QDebug>
#include <QDropEvent>
#include <QList>

// Construct the class.
DraggableTreeView::DraggableTreeView(QWidget *parentWidget) : QTreeView(parentWidget) {}

// Handle drop events.
void DraggableTreeView::dropEvent(QDropEvent *dropEvent)
{
    // Get the list of currently selected items that are moving.
    QList<QModelIndex> indexesMovingList = selectedIndexes();

    // Create a list of selected database IDs.
    QList<int> *selectedDatabaseIdsListPointer = new QList<int>;

    // Populate the list of selected database IDs.
    for (const QModelIndex &modelIndex : indexesMovingList)
    {
        // Only process model indexes from the bookmark name column (by default, there will be model indexes from all the visible columns in the list).
        if (modelIndex.column() == BookmarksDialog::NAME_COLUMN)
            selectedDatabaseIdsListPointer->append(modelIndex.siblingAtColumn(BookmarksDialog::DATABASE_ID_COLUMN).data().toInt());
    }

    // Get a list of root selected database IDs.
    QList<int> *rootSelectedDatabaseIdsListPointer = getRootSelectedDatabaseIds(selectedDatabaseIdsListPointer);

    // Get the drop position.
    int dropPosition = dropIndicatorPosition();

    // Get the drop model index.
    QModelIndex dropModelIndex = indexAt(dropEvent->position().toPoint());

    // Create a previous parent folder ID standard C++ list (which can be sorted and from which duplicates can be removed).
    std::list<double> *previousParentFolderIdListPointer = new std::list<double>;

    // Process the move according to the drop position.
    switch (dropPosition)
    {
        case QAbstractItemView::OnItem:  // This will only ever be called for folders as `BookmarksDialog::populateSubfolders` creates bookmarks without `setDropEnabled`.
        {
            // Move everything to the beginning of the folder.
            moveToBeginningOfFolder(dropModelIndex, selectedDatabaseIdsListPointer, rootSelectedDatabaseIdsListPointer, previousParentFolderIdListPointer);

            break;
        }

        case QAbstractItemView::AboveItem:
        {
            // Get the drop display order.
            int dropDisplayOrder = dropModelIndex.siblingAtColumn(BookmarksDialog::DISPLAY_ORDER_COLUMN).data().toInt();

            // Get the drop parent folder ID.
            double dropParentFolderId = dropModelIndex.parent().siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();

            // Get a list of all the items in the target folder except those selected.
            QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);

            // Initialize a new display order tracker.
            int newDisplayOrder = 0;

            // Process all the items in the target folder, moving in the new ones.
            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
            {
                // Check to see if this is the drop display order.
                if (bookmarkStruct.displayOrder == dropDisplayOrder)
                {
                    // Add all of the bookmarks being moved to this point.
                    for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
                    {
                        // Get the item's current parent folder ID.
                        double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);

                        // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
                        if (currentParentFolderId != dropParentFolderId)
                            previousParentFolderIdListPointer->push_back(currentParentFolderId);

                        // Update the parent folder and display order for each bookmark.
                        BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);

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

                // Set the bookmark's display order if it has changed.
                if (bookmarkStruct.displayOrder != newDisplayOrder)
                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);

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

        case QAbstractItemView::BelowItem:
        {
            // Check to see if the drop model index is an expanded folder.
            if (dropModelIndex.siblingAtColumn(BookmarksDialog::IS_FOLDER_COLUMN).data().toBool() && isExpanded(dropModelIndex))  // The drop model index is an expanded folder.
            {
                // Move everything to the beginning of the folder.
                moveToBeginningOfFolder(dropModelIndex, selectedDatabaseIdsListPointer, rootSelectedDatabaseIdsListPointer, previousParentFolderIdListPointer);
            }
            else  // The drop model index is not an expanded folder.
            {
                // Get the drop display order.
                int dropDisplayOrder = dropModelIndex.siblingAtColumn(BookmarksDialog::DISPLAY_ORDER_COLUMN).data().toInt();

                // Get the drop parent folder ID.
                double dropParentFolderId = dropModelIndex.parent().siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();

                // Get a list of all the items in the target folder except those selected.
                QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);

                // Initialize a new display order tracker.
                int newDisplayOrder = 0;

                // Process all the items in the target folder, moving in the new ones.
                for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
                {
                    // Set the bookmark's display order if it has changed.
                    if (bookmarkStruct.displayOrder != newDisplayOrder)
                        BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);

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

                    // Check to see if this is the drop display order.
                    if (bookmarkStruct.displayOrder == dropDisplayOrder)
                    {
                        // Add all of the bookmarks being moved to this point.
                        for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
                        {
                            // Get the item's current parent folder ID.
                            double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);

                            // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
                            if (currentParentFolderId != dropParentFolderId)
                                previousParentFolderIdListPointer->push_back(currentParentFolderId);

                            // Update the parent folder and display order for each bookmark.
                            BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);

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

        case QAbstractItemView::OnViewport:
        {
            // Get the drop parent folder ID.
            double dropParentFolderId = 0;

            // Get a list of all the items in the root folder except those selected.
            QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(dropParentFolderId, selectedDatabaseIdsListPointer);

            // Initialize a new display order tracker.
            int newDisplayOrder = 0;

            // Update the display order of the existing items in the folder.
            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
            {
                // Set the bookmark's display order if it has changed.
                if (bookmarkStruct.displayOrder != newDisplayOrder)
                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);

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

            // Add all of the bookmarks being moved to the end of the list.
            for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
            {
                // Get the item's current parent folder ID.
                double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);

                // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
                if (currentParentFolderId != dropParentFolderId)
                    previousParentFolderIdListPointer->push_back(currentParentFolderId);

                // Update the parent folder and display order for each bookmark.
                BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, dropParentFolderId, newDisplayOrder);

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

    // Sort the previous parent folder ID list.
    previousParentFolderIdListPointer->sort();

    // Remove duplicates from the parent folder ID list.
    previousParentFolderIdListPointer->unique();

    // Update the folder contents display order for each previous parent folder.
    for (const double parentFolderId : *previousParentFolderIdListPointer)
        BookmarksDatabase::updateFolderContentsDisplayOrder(parentFolderId);

    // Emit the bookmarks moved signal.
    Q_EMIT bookmarksMoved();
}

QList<int>* DraggableTreeView::getRootSelectedDatabaseIds(QList<int> *selectedDatabaseIdsPointer) const
{
    // Create a list of the database IDs of the contents of each selected folder.
    QList<int> selectedFoldersContentsDatabaseIds;

    // Populate the list of the database IDs of the contents of each selected folder.
    for (const int databaseId : *selectedDatabaseIdsPointer)
    {
        // If this is not the root item and is a folder, get the database IDs of the contents.
        if ((databaseId != -1) && BookmarksDatabase::isFolder(databaseId))
            selectedFoldersContentsDatabaseIds.append(*BookmarksDatabase::getFolderContentsDatabaseIds(BookmarksDatabase::getFolderId(databaseId)));
    }

    // Create a root selected database IDs list.
    QList<int>* rootSelectedDatabaseIdsListPointer = new QList<int>;

    // Populate the root selected database IDs list.
    for (const int databaseId : *selectedDatabaseIdsPointer)
    {
        // Add the database ID to the root selected database IDs list if it isn't the root item and it isn't contained in the selected folder contents database IDs list.
        if ((databaseId != -1) && !selectedFoldersContentsDatabaseIds.contains(databaseId))
            rootSelectedDatabaseIdsListPointer->append(databaseId);
    }

    // Return the root selected database IDs list.
    return rootSelectedDatabaseIdsListPointer;
}

void DraggableTreeView::moveToBeginningOfFolder(const QModelIndex &dropModelIndex, QList<int> *selectedDatabaseIdsListPointer, QList<int> *rootSelectedDatabaseIdsListPointer,
                                                std::list<double> *previousParentFolderIdListPointer) const
{
    // Get the new parent folder ID.
    double newParentFolderId = dropModelIndex.siblingAtColumn(BookmarksDialog::FOLDER_ID_COLUMN).data().toDouble();

    // Get a list of all the items in the target folder except those selected.
    QList<BookmarkStruct> *itemsInFolderExceptSelectedListPointer = BookmarksDatabase::getBookmarksInFolderExcept(newParentFolderId, selectedDatabaseIdsListPointer);

    // Initialize a new display order tracker.
    int newDisplayOrder = 0;

    // Move all the items to the top of the target folder.
    for (const int databaseId : *rootSelectedDatabaseIdsListPointer)
    {
        // Get the item's current parent folder ID.
        double currentParentFolderId = BookmarksDatabase::getParentFolderId(databaseId);

        // Add the parent folder ID to the list of previous parent folders if the item is from a different folder.
        if (currentParentFolderId != newParentFolderId)
            previousParentFolderIdListPointer->push_back(currentParentFolderId);

                // Update the parent folder and display order for each bookmark.
                BookmarksDatabase::updateParentFolderAndDisplayOrder(databaseId, newParentFolderId, newDisplayOrder);

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

            // Update the display order of the existing items in the folder.
            for (const BookmarkStruct &bookmarkStruct : *itemsInFolderExceptSelectedListPointer)
            {
                // Set the bookmark's display order if it has changed.
                if (bookmarkStruct.displayOrder != newDisplayOrder)
                    BookmarksDatabase::updateDisplayOrder(bookmarkStruct.databaseId, newDisplayOrder);

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