#include "DuplicatedNodeInfo.h"

#include "DuplicatedUploadChecker.h"
#include "EventUpdater.h"
#include "MegaApplication.h"
#include "Utilities.h"

#include <QFileInfo>
#include <QHash>

DuplicatedNodeInfo::DuplicatedNodeInfo(DuplicatedUploadBase* checker, PiTagTrigger piTagTrigger):
    mSolution(NodeItemType::UPLOAD),
    mSourceItemIsFile(false),
    mHasConflict(false),
    mIsNameConflict(false),
    mChecker(checker),
    mPiTagTrigger(piTagTrigger)
{
}

//UPLOAD INFO
const std::shared_ptr<mega::MegaNode> &DuplicatedNodeInfo::getParentNode() const
{
    return mParentNode;
}

void DuplicatedNodeInfo::setParentNode(const std::shared_ptr<mega::MegaNode> &newParentNode)
{
    mParentNode = newParentNode;
}

PiTagTrigger DuplicatedNodeInfo::getPiTagTrigger() const
{
    return mPiTagTrigger;
}

const std::shared_ptr<mega::MegaNode> &DuplicatedNodeInfo::getConflictNode() const
{
    return mConflictNode;
}

void DuplicatedNodeInfo::setConflictNode(std::shared_ptr<mega::MegaNode> newRemoteConflictNode)
{
    mConflictNode = std::move(newRemoteConflictNode);

    auto time = mConflictNode->isFile() ? mConflictNode->getModificationTime() :
                                          mConflictNode->getCreationTime();
    mConflictNodeModifiedTime = QDateTime::fromSecsSinceEpoch(time);
}

const QString &DuplicatedNodeInfo::getSourceItemPath() const
{
    return mSourcePath;
}

void DuplicatedNodeInfo::setSourceItemPath(const QString &newLocalPath)
{
    mSourcePath = newLocalPath;

    QFileInfo localNode(mSourcePath);
    mSourceItemIsFile = localNode.exists() && localNode.isFile();
}

NodeItemType DuplicatedNodeInfo::getSolution() const
{
    return mSolution;
}

void DuplicatedNodeInfo::setSolution(NodeItemType newSolution)
{
    mSolution = newSolution;
}

const QString &DuplicatedNodeInfo::getNewName()
{
    if(mSolution == NodeItemType::UPLOAD_AND_RENAME)
    {
        if(mNewName.isEmpty() && mConflictNode)
        {
            const auto parentHandle = mParentNode ? mParentNode->getHandle() : mega::INVALID_HANDLE;
            mNewName = Utilities::getNonDuplicatedNodeName(mConflictNode.get(),
                                                           mParentNode.get(),
                                                           mName,
                                                           false,
                                                           getUsedNamesForRename());
            auto& checkedNames = mChecker->getCheckedNames(parentHandle);
            checkedNames.removeOne(mName);
            checkedNames.append(mNewName);
        }
    }

    return mNewName;
}

const QString &DuplicatedNodeInfo::getDisplayNewName()
{
    if(mDisplayNewName.isEmpty())
    {
        mDisplayNewName = Utilities::getNonDuplicatedNodeName(mConflictNode.get(),
                                                              mParentNode.get(),
                                                              mName,
                                                              false,
                                                              getUsedNamesForRename());
    }

    return mDisplayNewName;
}

const QString &DuplicatedNodeInfo::getName() const
{
    return mName;
}

bool DuplicatedNodeInfo::hasConflict() const
{
    return mHasConflict;
}

void DuplicatedNodeInfo::setHasConflict(bool newHasConflict)
{
    mHasConflict = newHasConflict;
}

bool DuplicatedNodeInfo::sourceItemIsFile() const
{
    return mSourceItemIsFile;
}

bool DuplicatedNodeInfo::conflictNodeIsFile() const
{
    return mConflictNode->isFile();
}

const QDateTime &DuplicatedNodeInfo::getNodeModifiedTime() const
{
    return mConflictNodeModifiedTime;
}

const QDateTime &DuplicatedNodeInfo::getSourceItemModifiedTime() const
{
    return mSourceItemModifiedTime;
}

void DuplicatedNodeInfo::setSourceItemModifiedTime(const QDateTime &newSourceItemModifiedTime)
{
    mSourceItemModifiedTime = newSourceItemModifiedTime;
}

bool DuplicatedNodeInfo::haveDifferentType() const
{
    return sourceItemIsFile() != conflictNodeIsFile();
}

bool DuplicatedNodeInfo::isNameConflict() const
{
    return mIsNameConflict;
}

void DuplicatedNodeInfo::setIsNameConflict(bool newIsNameConflict)
{
    mIsNameConflict = newIsNameConflict;
}

DuplicatedUploadBase* DuplicatedNodeInfo::checker() const
{
    return mChecker;
}

void DuplicatedNodeInfo::setNewName(const QString &newNewName)
{
    mNewName = newNewName;
}

void DuplicatedNodeInfo::setReservedNames(const QStringList& reservedNames)
{
    mReservedNames = reservedNames;
    mNewName.clear();
    mDisplayNewName.clear();
}

void DuplicatedNodeInfo::setName(const QString &newName)
{
    mName = newName;
}

QStringList DuplicatedNodeInfo::getUsedNamesForRename() const
{
    auto usedNames = mReservedNames;
    const auto parentHandle = mParentNode ? mParentNode->getHandle() : mega::INVALID_HANDLE;
    usedNames.append(mChecker->getCheckedNames(parentHandle));

    return usedNames;
}

mega::MegaHandle DuplicatedMoveNodeInfo::getSourceItemHandle() const
{
    return mSourceItemNode->getHandle();
}

void DuplicatedMoveNodeInfo::setSourceItemHandle(const mega::MegaHandle& sourceItemHandle)
{
    mSourceItemNode = std::shared_ptr<mega::MegaNode>(MegaSyncApp->getMegaApi()->getNodeByHandle(sourceItemHandle));
}

bool DuplicatedMoveNodeInfo::sourceItemIsFile() const
{
    return mSourceItemNode->isFile();
}

std::shared_ptr<mega::MegaNode> DuplicatedMoveNodeInfo::getSourceItemNode() const
{
    return mSourceItemNode;
}

std::shared_ptr<ConflictTypes> CheckDuplicatedNodes::checkMoves(
    QList<QPair<mega::MegaHandle, std::shared_ptr<mega::MegaNode>>> handlesAndTargetNode,
    std::shared_ptr<mega::MegaNode> sourceNode)
{
    auto conflicts = std::make_shared<ConflictTypes>();
    conflicts->mFolderCheck = new DuplicatedMoveFolder();
    conflicts->mFileCheck = new DuplicatedMoveFile();
    conflicts->mSourceNode = sourceNode;

    QHash<mega::MegaHandle, QHash<QString, std::shared_ptr<mega::MegaNode>>>
        nodesOnCloudDriveByParentHandle;

    auto counter(0);
    EventUpdater checkUpdater(handlesAndTargetNode.size());

    while (!handlesAndTargetNode.isEmpty())
    {
        auto moveHandleAndTarget(handlesAndTargetNode.takeFirst());

        auto moveHandle(moveHandleAndTarget.first);

        std::shared_ptr<mega::MegaNode> foundNode;

        std::shared_ptr<mega::MegaNode> moveNode(
            MegaSyncApp->getMegaApi()->getNodeByHandle(moveHandle));
        if (!moveNode)
        {
            continue;
        }

        auto moveNodeName(QString::fromUtf8(moveNode->getName()));

        DuplicatedUploadBase* checker(nullptr);
        if (moveNode->isFile())
        {
            checker = conflicts->mFileCheck;
        }
        else
        {
            checker = conflicts->mFolderCheck;
        }

        auto info = std::make_shared<DuplicatedMoveNodeInfo>(checker);
        info->setSourceItemHandle(moveHandle);

        auto targetNode(moveHandleAndTarget.second);
        info->setParentNode(targetNode);

        if (targetNode)
        {
            auto& reservedNodeNamesByTargetParent =
                conflicts->mReservedNodeNamesByTargetParent[targetNode->getHandle()];
            reservedNodeNamesByTargetParent.append(moveNodeName);
        }

        // The rubbish accepts duplicate nodes
        if (targetNode && (targetNode->getHandle() != MegaSyncApp->getRubbishNode()->getHandle() &&
                           !MegaSyncApp->getMegaApi()->isInRubbish(targetNode.get())))
        {
            QHash<QString, std::shared_ptr<mega::MegaNode>> nodesOnCloudDrive;

            if (nodesOnCloudDriveByParentHandle.contains(targetNode->getHandle()))
            {
                nodesOnCloudDrive = nodesOnCloudDriveByParentHandle.value(targetNode->getHandle());
            }
            else
            {
                std::unique_ptr<mega::MegaNodeList> childNodes(
                    MegaSyncApp->getMegaApi()->getChildren(targetNode.get()));

                for (int index = 0; index < childNodes->size(); ++index)
                {
                    std::shared_ptr<mega::MegaNode> node(childNodes->get(index)->copy());
                    QString nodeName(QString::fromUtf8(node->getName()));
                    nodesOnCloudDrive.insert(nodeName, node);
                }

                nodesOnCloudDriveByParentHandle.insert(targetNode->getHandle(), nodesOnCloudDrive);
            }

            foundNode = nodesOnCloudDrive.value(moveNodeName);
        }

        if (foundNode)
        {
            info->setConflictNode(std::move(foundNode));
            info->setHasConflict(true);
            info->setName(moveNodeName);
            info->setIsNameConflict(true);
            moveNode->isFile() ? conflicts->mFileNameConflicts.append(info) :
                                 conflicts->mFolderNameConflicts.append(info);
        }
        else
        {
            if (targetNode)
            {
                nodesOnCloudDriveByParentHandle[targetNode->getHandle()].insert(moveNodeName,
                                                                                moveNode);
            }
            conflicts->mResolvedConflicts.append(info);
        }

        checkUpdater.update(counter);
        counter++;
    }

    return conflicts;
}

std::unique_ptr<ConflictTypes>
    CheckDuplicatedNodes::checkUploads(QQueue<QPair<QString, PiTagTrigger>>& nodePaths,
                                       std::shared_ptr<mega::MegaNode> targetNode)
{
    auto conflicts = std::make_unique<ConflictTypes>();
    conflicts->mFolderCheck = new DuplicatedUploadFolder();
    conflicts->mFileCheck = new DuplicatedUploadFile();
    conflicts->mTargetNode = targetNode;

    std::unique_ptr<mega::MegaNodeList> nodes(
        MegaSyncApp->getMegaApi()->getChildren(targetNode.get()));
    QHash<QString, mega::MegaNode*> nodesOnCloudDrive;

    for (int index = 0; index < nodes->size(); ++index)
    {
        QString nodeName(QString::fromUtf8(nodes->get(index)->getName()));
        nodesOnCloudDrive.insert(nodeName.toLower(), nodes->get(index));
    }

    auto counter(0);
    EventUpdater checkUpdater(nodePaths.size());

    auto& reservedNamesForTarget =
        conflicts->mReservedNodeNamesByTargetParent[targetNode->getHandle()];

    while (!nodePaths.isEmpty())
    {
        const auto item = nodePaths.dequeue();
        const auto localPath(item.first);
        const QFileInfo localPathInfo(localPath);
        const bool isFile(localPathInfo.isFile());
        DuplicatedUploadBase* checker = isFile ? conflicts->mFileCheck : conflicts->mFolderCheck;

        auto info = std::make_shared<DuplicatedNodeInfo>(checker, item.second);
        info->setSourceItemPath(localPathInfo.absoluteFilePath());
        info->setParentNode(targetNode);

        const auto nodeToUploadName(localPathInfo.fileName());
        reservedNamesForTarget.append(nodeToUploadName);

        auto node(nodesOnCloudDrive.value(nodeToUploadName.toLower()));
        if (node)
        {
            std::shared_ptr<mega::MegaNode> smartNode(node->copy());
            info->setConflictNode(std::move(smartNode));
            info->setHasConflict(true);
            info->setName(nodeToUploadName);

            auto nodeName(QString::fromUtf8(node->getName()));
            if (nodeName.compare(nodeToUploadName) != 0)
            {
                info->setIsNameConflict(true);
                isFile ? conflicts->mFileNameConflicts.append(info) :
                         conflicts->mFolderNameConflicts.append(info);
            }
            else
            {
                isFile ? conflicts->mFileConflicts.append(info) :
                         conflicts->mFolderConflicts.append(info);
            }
        }
        else
        {
            conflicts->mResolvedConflicts.append(info);
        }

        checkUpdater.update(counter);
        counter++;
    }

    return conflicts;
}

bool ConflictTypes::isConflictFree() const
{
    return mFileConflicts.isEmpty() && mFolderConflicts.isEmpty() && mFileNameConflicts.isEmpty() &&
           mFolderNameConflicts.isEmpty();
}
