// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// This file is a copy of Chromium's /src/base/containers/linked_list_unittest.cc

#include <list>
#include <mutex>
#include <utility>

#include "gtest/gtest.h"
#include "src/dawn/common/LinkedList.h"
#include "src/dawn/utils/TestUtils.h"
#include "src/utils/compiler.h"

namespace dawn {
namespace {

class Node : public LinkNode<Node> {
  public:
    explicit Node(int id) : id_(id) {}

    int id() const { return id_; }

    void set_id(int id) { id_ = id; }

  private:
    int id_;
};

class MultipleInheritanceNodeBase {
  public:
    MultipleInheritanceNodeBase() : field_taking_up_space_(0) {}
    int field_taking_up_space_;
};

class MultipleInheritanceNode : public MultipleInheritanceNodeBase,
                                public LinkNode<MultipleInheritanceNode> {
  public:
    MultipleInheritanceNode() = default;
};

class MovableNode : public LinkNode<MovableNode> {
  public:
    explicit MovableNode(int id) : id_(id) {}

    MovableNode(MovableNode&&) = default;

    int id() const { return id_; }

  private:
    int id_;
};

// Checks that when iterating |list| (either from head to tail, or from
// tail to head, as determined by |forward|), we get back |node_ids|,
// which is an array of size |num_nodes|.
void ExpectListContentsForDirection(const LinkedList<Node>& list,
                                    int num_nodes,
                                    const int* node_ids,
                                    bool forward) {
    int i = 0;
    for (const LinkNode<Node>* node = (forward ? list.head() : list.tail()); node != list.end();
         node = (forward ? node->next() : node->previous())) {
        ASSERT_LT(i, num_nodes);
        int index_of_id = forward ? i : num_nodes - i - 1;
        DAWN_UNSAFE_TODO(EXPECT_EQ(node_ids[index_of_id], node->value()->id()));
        ++i;
    }
    EXPECT_EQ(num_nodes, i);
}

void ExpectListContents(const LinkedList<Node>& list, int num_nodes, const int* node_ids) {
    {
        SCOPED_TRACE("Iterating forward (from head to tail)");
        ExpectListContentsForDirection(list, num_nodes, node_ids, true);
    }
    {
        SCOPED_TRACE("Iterating backward (from tail to head)");
        ExpectListContentsForDirection(list, num_nodes, node_ids, false);
    }
}

TEST(LinkedList, Empty) {
    LinkedList<Node> list;
    EXPECT_EQ(list.end(), list.head());
    EXPECT_EQ(list.end(), list.tail());
    ExpectListContents(list, 0, nullptr);
}

TEST(LinkedList, Append) {
    LinkedList<Node> list;
    ExpectListContents(list, 0, nullptr);

    Node n1(1);
    list.Append(&n1);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n1, list.tail());
    {
        const int expected[] = {1};
        ExpectListContents(list, 1, expected);
    }

    Node n2(2);
    list.Append(&n2);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n2, list.tail());
    {
        const int expected[] = {1, 2};
        ExpectListContents(list, 2, expected);
    }

    Node n3(3);
    list.Append(&n3);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n3, list.tail());
    {
        const int expected[] = {1, 2, 3};
        ExpectListContents(list, 3, expected);
    }
}

TEST(LinkedList, Prepend) {
    LinkedList<Node> list;
    ExpectListContents(list, 0, nullptr);

    Node n1(1);
    list.Prepend(&n1);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n1, list.tail());
    {
        const int expected[] = {1};
        ExpectListContents(list, 1, expected);
    }

    Node n2(2);
    list.Prepend(&n2);

    EXPECT_EQ(&n2, list.head());
    EXPECT_EQ(&n1, list.tail());
    {
        const int expected[] = {2, 1};
        ExpectListContents(list, 2, expected);
    }

    Node n3(3);
    list.Prepend(&n3);

    EXPECT_EQ(&n3, list.head());
    EXPECT_EQ(&n1, list.tail());
    {
        const int expected[] = {3, 2, 1};
        ExpectListContents(list, 3, expected);
    }
}

TEST(LinkedList, RemoveFromList) {
    LinkedList<Node> list;

    Node n1(1);
    Node n2(2);
    Node n3(3);
    Node n4(4);
    Node n5(5);

    list.Append(&n1);
    list.Append(&n2);
    list.Append(&n3);
    list.Append(&n4);
    list.Append(&n5);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n5, list.tail());
    {
        const int expected[] = {1, 2, 3, 4, 5};
        ExpectListContents(list, 5, expected);
    }

    // Remove from the middle.
    n3.RemoveFromList();

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n5, list.tail());
    {
        const int expected[] = {1, 2, 4, 5};
        ExpectListContents(list, 4, expected);
    }

    // Remove from the tail.
    n5.RemoveFromList();

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n4, list.tail());
    {
        const int expected[] = {1, 2, 4};
        ExpectListContents(list, 3, expected);
    }

    // Remove from the head.
    n1.RemoveFromList();

    EXPECT_EQ(&n2, list.head());
    EXPECT_EQ(&n4, list.tail());
    {
        const int expected[] = {2, 4};
        ExpectListContents(list, 2, expected);
    }

    // Empty the list.
    n2.RemoveFromList();
    n4.RemoveFromList();

    ExpectListContents(list, 0, nullptr);
    EXPECT_EQ(list.end(), list.head());
    EXPECT_EQ(list.end(), list.tail());

    // Fill the list once again.
    list.Append(&n1);
    list.Append(&n2);
    list.Append(&n3);
    list.Append(&n4);
    list.Append(&n5);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n5, list.tail());
    {
        const int expected[] = {1, 2, 3, 4, 5};
        ExpectListContents(list, 5, expected);
    }
}

TEST(LinkedList, InsertBefore) {
    LinkedList<Node> list;

    Node n1(1);
    Node n2(2);
    Node n3(3);
    Node n4(4);

    list.Append(&n1);
    list.Append(&n2);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n2, list.tail());
    {
        const int expected[] = {1, 2};
        ExpectListContents(list, 2, expected);
    }

    n3.InsertBefore(&n2);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n2, list.tail());
    {
        const int expected[] = {1, 3, 2};
        ExpectListContents(list, 3, expected);
    }

    n4.InsertBefore(&n1);

    EXPECT_EQ(&n4, list.head());
    EXPECT_EQ(&n2, list.tail());
    {
        const int expected[] = {4, 1, 3, 2};
        ExpectListContents(list, 4, expected);
    }
}

TEST(LinkedList, InsertAfter) {
    LinkedList<Node> list;

    Node n1(1);
    Node n2(2);
    Node n3(3);
    Node n4(4);

    list.Append(&n1);
    list.Append(&n2);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n2, list.tail());
    {
        const int expected[] = {1, 2};
        ExpectListContents(list, 2, expected);
    }

    n3.InsertAfter(&n2);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n3, list.tail());
    {
        const int expected[] = {1, 2, 3};
        ExpectListContents(list, 3, expected);
    }

    n4.InsertAfter(&n1);

    EXPECT_EQ(&n1, list.head());
    EXPECT_EQ(&n3, list.tail());
    {
        const int expected[] = {1, 4, 2, 3};
        ExpectListContents(list, 4, expected);
    }
}

TEST(LinkedList, MultipleInheritanceNode) {
    MultipleInheritanceNode node;
    EXPECT_EQ(&node, node.value());
}

TEST(LinkedList, EmptyListIsEmpty) {
    LinkedList<Node> list;
    EXPECT_TRUE(list.empty());
}

TEST(LinkedList, NonEmptyListIsNotEmpty) {
    LinkedList<Node> list;

    Node n(1);
    list.Append(&n);

    EXPECT_FALSE(list.empty());
}

TEST(LinkedList, EmptiedListIsEmptyAgain) {
    LinkedList<Node> list;

    Node n(1);
    list.Append(&n);
    n.RemoveFromList();

    EXPECT_TRUE(list.empty());
}

TEST(LinkedList, NodesCanBeReused) {
    LinkedList<Node> list1;
    LinkedList<Node> list2;

    Node n(1);
    list1.Append(&n);
    n.RemoveFromList();
    list2.Append(&n);

    EXPECT_EQ(list2.head()->value(), &n);
}

TEST(LinkedList, RemovedNodeHasNullNextPrevious) {
    LinkedList<Node> list;

    Node n(1);
    list.Append(&n);
    n.RemoveFromList();

    EXPECT_EQ(nullptr, n.next());
    EXPECT_EQ(nullptr, n.previous());
}

TEST(LinkedList, NodeMoveConstructor) {
    LinkedList<MovableNode> list;

    MovableNode n1(1);
    MovableNode n2(2);
    MovableNode n3(3);

    list.Append(&n1);
    list.Append(&n2);
    list.Append(&n3);

    EXPECT_EQ(&n1, n2.previous());
    EXPECT_EQ(&n2, n1.next());
    EXPECT_EQ(&n3, n2.next());
    EXPECT_EQ(&n2, n3.previous());
    EXPECT_EQ(2, n2.id());

    MovableNode n2_new(std::move(n2));

    EXPECT_EQ(&n1, n2_new.previous());
    EXPECT_EQ(&n2_new, n1.next());
    EXPECT_EQ(&n3, n2_new.next());
    EXPECT_EQ(&n2_new, n3.previous());
    EXPECT_EQ(2, n2_new.id());
}

TEST(LinkedList, IsInList) {
    LinkedList<Node> list;

    Node n(1);

    EXPECT_FALSE(n.IsInList());
    list.Append(&n);
    EXPECT_TRUE(n.IsInList());
    EXPECT_TRUE(n.RemoveFromList());
    EXPECT_FALSE(n.IsInList());
    EXPECT_FALSE(n.RemoveFromList());
}

TEST(LinkedList, MoveInto) {
    LinkedList<Node> l1;
    LinkedList<Node> l2;

    Node n1(1);
    Node n2(2);
    l1.Append(&n1);
    l2.Append(&n2);

    l2.MoveInto(&l1);
    const int expected[] = {1, 2};
    ExpectListContents(l1, 2, expected);
    EXPECT_TRUE(l2.empty());
}

TEST(LinkedList, MoveEmptyListInto) {
    LinkedList<Node> l1;
    LinkedList<Node> l2;

    Node n1(1);
    Node n2(2);
    l1.Append(&n1);
    l1.Append(&n2);

    l2.MoveInto(&l1);
    const int expected[] = {1, 2};
    ExpectListContents(l1, 2, expected);
    EXPECT_TRUE(l2.empty());
}

TEST(LinkedList, MoveIntoEmpty) {
    LinkedList<Node> l1;
    LinkedList<Node> l2;

    Node n1(1);
    Node n2(2);
    l2.Append(&n1);
    l2.Append(&n2);

    l2.MoveInto(&l1);
    const int expected[] = {1, 2};
    ExpectListContents(l1, 2, expected);
    EXPECT_TRUE(l2.empty());
}

TEST(LinkedList, RangeBasedModify) {
    LinkedList<Node> list;

    Node n1(1);
    Node n2(2);
    list.Append(&n1);
    list.Append(&n2);

    for (LinkNode<Node>* node : list) {
        node->value()->set_id(node->value()->id() + 1);
    }
    const int expected[] = {2, 3};
    ExpectListContents(list, 2, expected);
}

TEST(LinkedList, RangeBasedEndIsEnd) {
    LinkedList<Node> list;
    EXPECT_EQ(list.end(), *end(list));
}

// Verify that concurrent Insert/Remove operations require external synchronization (mutex), but
// IsInList() can be called concurrently without synchronization.
TEST(LinkedList, ConcurrentInsertRemoveAndIsInList) {
    LinkedList<Node> list;
    std::mutex listMutex;
    constexpr uint32_t kNumThreads = 10;
    constexpr uint32_t kNodesPerThread = 100;

    dawn::utils::RunInParallel(kNumThreads, [&](uint32_t threadIndex) {
        for (uint32_t i = 0; i < kNodesPerThread; i++) {
            Node node(threadIndex * kNodesPerThread + i);

            // Insert and Remove must be protected by mutex
            {
                std::lock_guard<std::mutex> lock(listMutex);
                list.Append(&node);
            }

            // IsInList() can be called without the mutex - it only needs to be ordered with
            // this node's own Insert/Remove operations
            EXPECT_TRUE(node.IsInList());

            // Remove must be protected by mutex
            {
                std::lock_guard<std::mutex> lock(listMutex);
                node.RemoveFromList();
            }

            // IsInList() can be called without the mutex
            EXPECT_FALSE(node.IsInList());
        }
    });

    // List should be empty after all threads complete
    EXPECT_TRUE(list.empty());
}

}  // anonymous namespace
}  // namespace dawn
