/**
 * (c) 2021 by Mega Limited, Wellsford, New Zealand
 *
 * This file is part of the MEGA SDK - Client Access Engine.
 *
 * Applications using the MEGA API must present a valid application key
 * and comply with the rules set forth in the Terms of Service.
 *
 * The MEGA SDK 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.
 *
 * @copyright Simplified (2-clause) BSD License.
 *
 * You should have received a copy of the license along with this
 * program.
 */

#include <memory>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "megaapi_impl.h"
#include "mega/sync.h"

#ifdef ENABLE_SYNC

namespace SyncConflictTests {

using namespace mega;

TEST(SyncStallHashTest, MegaSyncStallPrivateGetHash)
{
    // Create some SyncStallEntry objects to initialize the MegaSyncStallPrivate after
    SyncStallEntry e1{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath",                               PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink                              }
    };
    SyncStallEntry e1_same{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath",                               PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink                              }
    };
    SyncStallEntry e2{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath",                               PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::NoProblem                              }
    };

    std::hash<SyncStallEntry> hashEntryGetter;
    ASSERT_EQ(hashEntryGetter(e1), hashEntryGetter(e1));
    ASSERT_EQ(hashEntryGetter(e1), hashEntryGetter(e1_same));
    ASSERT_NE(hashEntryGetter(e1), hashEntryGetter(e2));

    MegaSyncStallPrivate s1(e1);
    MegaSyncStallPrivate s1_same(e1_same);
    MegaSyncStallPrivate s2(e2);

    ASSERT_EQ(hashEntryGetter(e1), s1.getHash());
    ASSERT_EQ(hashEntryGetter(e1_same), s1_same.getHash());
    ASSERT_EQ(hashEntryGetter(e2), s2.getHash());

    ASSERT_EQ(s1.getHash(), s1.getHash());
    ASSERT_EQ(s1.getHash(), s1_same.getHash());

    ASSERT_NE(s1.getHash(), s2.getHash());
}

TEST(SyncStallHashTest, MegaSyncNameConflictStallPrivateGetHash)
{
    // Create some NameConflict objects to initialize the MegaSyncNameConflictStallPrivate after
    std::vector<NameConflict::NameHandle> nhVec{};
    std::vector<LocalPath> clashingLNames{};
    NameConflict nc1{"cloudPath", nhVec, LocalPath(), clashingLNames};
    NameConflict nc1_same{"cloudPath", nhVec, LocalPath(), clashingLNames};

    nhVec.emplace_back("nameHandle", NodeHandle());
    clashingLNames.emplace_back(LocalPath::fromAbsolutePath("./test/local"));
    NameConflict nc2{"cloudPath", nhVec, LocalPath(), clashingLNames};

    std::hash<NameConflict> hashNCGetter;
    ASSERT_EQ(hashNCGetter(nc1), hashNCGetter(nc1));
    ASSERT_EQ(hashNCGetter(nc1), hashNCGetter(nc1_same));
    ASSERT_NE(hashNCGetter(nc1), hashNCGetter(nc2));

    MegaSyncNameConflictStallPrivate s1(nc1);
    MegaSyncNameConflictStallPrivate s1_same(nc1_same);
    MegaSyncNameConflictStallPrivate s2(nc2);

    ASSERT_EQ(hashNCGetter(nc1), s1.getHash());
    ASSERT_EQ(hashNCGetter(nc1_same), s1_same.getHash());
    ASSERT_EQ(hashNCGetter(nc2), s2.getHash());

    ASSERT_EQ(s1.getHash(), s1.getHash());
    ASSERT_EQ(s1.getHash(), s1_same.getHash());

    ASSERT_NE(s1.getHash(), s2.getHash());
}

/**
 * @class MegaSyncStallListTest
 * @brief Dummy implementation of the MegaSyncStallList for testing purpose
 *
 */
class MegaSyncStallListTest: public MegaSyncStallList
{
public:
    std::vector<std::shared_ptr<MegaSyncStall>> mStalls;

    MegaSyncStallListTest(std::vector<std::shared_ptr<MegaSyncStall>>&& stalls):
        mStalls{stalls}
    {}

    size_t size() const override
    {
        return mStalls.size();
    }

    const MegaSyncStall* get(size_t i) const override
    {
        return mStalls[i].get();
    }
};

class MegaSyncStallMapTest: public MegaSyncStallMapPrivate
{
public:
    MegaSyncStallMapTest():
        MegaSyncStallMapPrivate()
    {}

    void add(const MegaHandle backupId, const MegaSyncStallListPrivate& testList)
    {
        mStallsMap.emplace(backupId, testList);
    }
};

TEST(SyncStallHashTest, MegaSyncStallIssuesMapGetHash)
{
    std::vector<NameConflict::NameHandle> nhVec{};
    std::vector<LocalPath> clashingLNames{};
    NameConflict nc1{"cloudPath1", nhVec, LocalPath(), clashingLNames};
    std::shared_ptr<MegaSyncNameConflictStallPrivate> s1{new MegaSyncNameConflictStallPrivate(nc1)};

    nhVec.emplace_back("nameHandle", NodeHandle());
    clashingLNames.emplace_back(LocalPath::fromAbsolutePath("./test/local"));
    NameConflict nc2{"cloudPath2", nhVec, LocalPath(), clashingLNames};
    std::shared_ptr<MegaSyncNameConflictStallPrivate> s2{new MegaSyncNameConflictStallPrivate(nc2)};

    SyncStallEntry e1{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath1", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath1",                                PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink                               }
    };
    std::shared_ptr<MegaSyncStallPrivate> s3{new MegaSyncStallPrivate(e1)};

    SyncStallEntry e2{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath2", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath2",                                PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::NoProblem                               }
    };
    std::shared_ptr<MegaSyncStallPrivate> s4{new MegaSyncStallPrivate(e1)};

    MegaSyncStallListPrivate sl1;
    sl1.addStall(s1);
    sl1.addStall(s3);

    MegaSyncStallListPrivate sl2;
    sl1.addStall(s2);
    sl1.addStall(s4);

    MegaSyncStallListPrivate sl3;
    sl1.addStall(s2);
    sl1.addStall(s3);

    MegaSyncStallMapTest m1;
    m1.add(111111111111111, sl1);
    m1.add(222222222222222, sl2);

    MegaSyncStallMapTest m2;
    m2.add(111111111111111, sl1);
    m2.add(222222222222222, sl2);

    MegaSyncStallMapTest m3;
    m2.add(222222222222222, sl2);
    m2.add(111111111111111, sl1);

    MegaSyncStallMapTest m4;
    m4.add(222222222222222, sl1);
    m4.add(111111111111111, sl3);

    ASSERT_EQ(m1.getHash(), m2.getHash());
    ASSERT_NE(m1.getHash(), m3.getHash());
    ASSERT_NE(m2.getHash(), m4.getHash());
}

TEST(SyncStallHashTest, MegaSyncStallListGetHash)
{
    std::vector<NameConflict::NameHandle> nhVec{};
    std::vector<LocalPath> clashingLNames{};
    NameConflict nc1{"cloudPath", nhVec, LocalPath(), clashingLNames};
    std::shared_ptr<MegaSyncNameConflictStallPrivate> s1{new MegaSyncNameConflictStallPrivate(nc1)};

    SyncStallEntry e1{
        SyncWaitReason::FileIssue,
        true,
        false,
        {NodeHandle{},                  "currentPath", PathProblem::DetectedSymlink},
        {NodeHandle{},                             "currentPath",                               PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink},
        {LocalPath{}, PathProblem::DetectedSymlink                              }
    };
    std::shared_ptr<MegaSyncStallPrivate> s2{new MegaSyncStallPrivate(e1)};

    MegaSyncStallListTest testList1{
        std::vector<std::shared_ptr<MegaSyncStall>>{s1, s2}
    };
    MegaSyncStallListTest testList1_same{
        std::vector<std::shared_ptr<MegaSyncStall>>{s1, s2}
    };
    MegaSyncStallListTest testList2{
        std::vector<std::shared_ptr<MegaSyncStall>>{s2, s1}
    };
    MegaSyncStallListTest testList3{std::vector<std::shared_ptr<MegaSyncStall>>{s2}};

    ASSERT_EQ(testList1.getHash(), testList1.getHash());
    ASSERT_EQ(testList1.getHash(), testList1_same.getHash());

    ASSERT_NE(testList1.getHash(), testList2.getHash());
    ASSERT_NE(testList1.getHash(), testList3.getHash());
    ASSERT_NE(testList2.getHash(), testList3.getHash());
}

} // namespace

#endif // ENABLE_SYNC
