/*
    SPDX-FileCopyrightText: 2014-2023 Anne Jan Brouwer <brouwer@annejan.com>
    SPDX-FileCopyrightText: 2018 Claudio Maradonna <penguyman@stronzi.org>
    SPDX-FileCopyrightText: 2019 Maciej S. Szmigiero <mail@maciej.szmigiero.name>
    SPDX-FileCopyrightText: 2023 g10 Code GmbH
    SPDX-FileContributor: Sune Stolborg Vuorela <sune@vuorela.dk>

    SPDX-License-Identifier: GPL-3.0-or-later
*/

#include "storemodel.h"

#include "pass.h"
#include "util.h"
#include <QDebug>
#include <QFileSystemModel>
#include <QMessageBox>
#include <QMimeData>
#include <QRegularExpression>
#include <utility>
#include <KI18n/klocalizedstring.h>

static const QString mimeType = QStringLiteral("application/vnd+gnupgpass.dragAndDropInfoPasswordStore");

QDataStream &operator<<(QDataStream &out, const dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
    out << dragAndDropInfoPasswordStore.isDir << dragAndDropInfoPasswordStore.isFile << dragAndDropInfoPasswordStore.path;
    return out;
}

QDataStream &operator>>(QDataStream &in, dragAndDropInfoPasswordStore &dragAndDropInfoPasswordStore)
{
    in >> dragAndDropInfoPasswordStore.isDir >> dragAndDropInfoPasswordStore.isFile >> dragAndDropInfoPasswordStore.path;
    return in;
}

/**
 * @brief StoreModel::StoreModel
 * SubClass of QSortFilterProxyModel via
 * http://www.qtcentre.org/threads/46471-QTreeView-Filter
 */
StoreModel::StoreModel(Pass &pass)
    : m_pass(pass)
{
    setRecursiveFilteringEnabled(true);
}

/**
 * @brief StoreModel::data don't show the .gpg at the end of a file.
 * @param index
 * @param role
 * @return
 */
QVariant StoreModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return QVariant();

    auto initial_value = QSortFilterProxyModel::data(index, role);

    if (role == Qt::DisplayRole) {
        QString name = initial_value.toString();
        name.replace(Util::endsWithGpg(), QString{});
        return name;
    }
    return initial_value;
}

/**
 * @brief StoreModel::supportedDropActions enable drop.
 * @return
 */
Qt::DropActions StoreModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

/**
 * @brief StoreModel::supportedDragActions enable drag.
 * @return
 */
Qt::DropActions StoreModel::supportedDragActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

/**
 * @brief StoreModel::flags
 * @param index
 * @return
 */
Qt::ItemFlags StoreModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QSortFilterProxyModel::flags(index);

    if (index.isValid()) {
        return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
    }
    return Qt::ItemIsDropEnabled | defaultFlags;
}

/**
 * @brief StoreModel::mimeTypes
 * @return
 */
QStringList StoreModel::mimeTypes() const
{
    QStringList types;
    types << mimeType;
    return types;
}

/**
 * @brief StoreModel::mimeData
 * @param indexes
 * @return
 */
QMimeData *StoreModel::mimeData(const QModelIndexList &indexes) const
{
    dragAndDropInfoPasswordStore info;

    QByteArray encodedData;
    // only use the first, otherwise we should enable multiselection
    QModelIndex index = indexes.at(0);
    if (index.isValid()) {
        QModelIndex useIndex = mapToSource(index);

        info.isDir = fs()->fileInfo(useIndex).isDir();
        info.isFile = fs()->fileInfo(useIndex).isFile();
        info.path = fs()->fileInfo(useIndex).absoluteFilePath();
        QDataStream stream(&encodedData, QIODevice::WriteOnly);
        stream << info;
    }

    auto *mimeData = new QMimeData();
    mimeData->setData(mimeType, encodedData);
    return mimeData;
}

/**
 * @brief StoreModel::canDropMimeData
 * @param data
 * @param action
 * @param row
 * @param column
 * @param parent
 * @return
 */
bool StoreModel::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
#ifdef QT_DEBUG
    qDebug() << action << row;
#else
    Q_UNUSED(action)
    Q_UNUSED(row)
#endif
    if (!fs()) {
        return false;
    }

    QModelIndex useIndex = this->index(parent.row(), parent.column(), parent.parent());
    QByteArray encodedData = data->data(mimeType);
    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    dragAndDropInfoPasswordStore info;
    stream >> info;
    if (!data->hasFormat(mimeType))
        return false;

    if (column > 0) {
        return false;
    }

    // you can drop a folder on a folder
    if (fs()->fileInfo(mapToSource(useIndex)).isDir() && info.isDir) {
        return true;
    }
    // you can drop a file on a folder
    if (fs()->fileInfo(mapToSource(useIndex)).isDir() && info.isFile) {
        return true;
    }
    // you can drop a file on a file
    if (fs()->fileInfo(mapToSource(useIndex)).isFile() && info.isFile) {
        return true;
    }

    return false;
}

/**
 * @brief StoreModel::dropMimeData
 * @param data
 * @param action
 * @param row
 * @param column
 * @param parent
 * @return
 */
bool StoreModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
    if (!canDropMimeData(data, action, row, column, parent))
        return false;

    if (action == Qt::IgnoreAction) {
        return true;
    }
    QByteArray encodedData = data->data(mimeType);

    QDataStream stream(&encodedData, QIODevice::ReadOnly);
    dragAndDropInfoPasswordStore info;
    stream >> info;
    QModelIndex destIndex = this->index(parent.row(), parent.column(), parent.parent());
    QFileInfo destFileinfo = fs()->fileInfo(mapToSource(destIndex));
    QFileInfo srcFileInfo = QFileInfo(info.path);
    QString cleanedSrc = QDir::cleanPath(srcFileInfo.absoluteFilePath());
    QString cleanedDest = QDir::cleanPath(destFileinfo.absoluteFilePath());
    if (info.isDir) {
        // dropped dir onto dir
        if (destFileinfo.isDir()) {
            QDir destDir = QDir(cleanedDest).filePath(srcFileInfo.fileName());
            QString cleanedDestDir = QDir::cleanPath(destDir.absolutePath());
            if (action == Qt::MoveAction) {
                m_pass.Move(cleanedSrc, cleanedDestDir);
            } else if (action == Qt::CopyAction) {
                m_pass.Copy(cleanedSrc, cleanedDestDir);
            }
        }
    } else if (info.isFile) {
        // dropped file onto a directory
        if (destFileinfo.isDir()) {
            if (action == Qt::MoveAction) {
                m_pass.Move(cleanedSrc, cleanedDest);
            } else if (action == Qt::CopyAction) {
                m_pass.Copy(cleanedSrc, cleanedDest);
            }
        } else if (destFileinfo.isFile()) {
            // dropped file onto a file
            int answer = QMessageBox::question(nullptr,
                                               i18n("force overwrite?"),
                                               i18nc("overwrite DestinationFile with SourceFile","overwrite %1 with %2?", cleanedDest, cleanedSrc),
                                               QMessageBox::Yes | QMessageBox::No);
            bool force = answer == QMessageBox::Yes;
            if (action == Qt::MoveAction) {
                m_pass.Move(cleanedSrc, cleanedDest, force);
            } else if (action == Qt::CopyAction) {
                m_pass.Copy(cleanedSrc, cleanedDest, force);
            }
        }
    }
    return true;
}

/**
 * @brief StoreModel::lessThan
 * @param source_left
 * @param source_right
 * @return
 */
bool StoreModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
/* matches logic in QFileSystemModelSorter::compareNodes() */
#ifndef Q_OS_MAC
    if (fs() && (source_left.column() == 0 || source_left.column() == 1)) {
        bool leftD = fs()->isDir(source_left);
        bool rightD = fs()->isDir(source_right);

        if (leftD ^ rightD)
            return leftD;
    }
#endif

    return QSortFilterProxyModel::lessThan(source_left, source_right);
}

QFileSystemModel* StoreModel::fs() const {
    return static_cast<QFileSystemModel*>(sourceModel());
}
