/*
 * Copyright 2017 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "gtest/gtest.h"

#include <memory>

#include "wabt/intrusive-list.h"

using namespace wabt;

namespace {

struct TestObject : intrusive_list_base<TestObject> {
  static int creation_count;

  TestObject(int data = 0, int data2 = 0) : data(data), data2(data2) {
    ++creation_count;
  }

  // Allow move.
  TestObject(TestObject&& other) {
    // Don't increment creation_count; we're moving from other.
    *this = std::move(other);
  }

  TestObject& operator=(TestObject&& other) {
    data = other.data;
    data2 = other.data2;
    other.moved = true;
    return *this;
  }

  // Disallow copy.
  TestObject(const TestObject&) = delete;
  TestObject& operator=(const TestObject&) = delete;

  ~TestObject() {
    if (!moved) {
      creation_count--;
    }
  }

  int data;
  int data2;
  bool moved = false;
};

// static
int TestObject::creation_count = 0;

using TestObjectList = intrusive_list<TestObject>;

class IntrusiveListTest : public ::testing::Test {
 protected:
  virtual void SetUp() {
    // Reset to 0 even if the previous test leaked objects to keep the tests
    // independent.
    TestObject::creation_count = 0;
  }

  virtual void TearDown() { ASSERT_EQ(0, TestObject::creation_count); }

  TestObjectList NewList(const std::vector<int>& data_values) {
    TestObjectList result;
    for (auto data_value : data_values)
      result.emplace_back(data_value);
    return result;
  }

  void AssertListEq(const TestObjectList& list,
                    const std::vector<int>& expected) {
    size_t count = 0;
    for (const TestObject& node : list) {
      ASSERT_EQ(expected[count++], node.data);
    }
    ASSERT_EQ(count, expected.size());
  }

  void AssertListEq(const TestObjectList& list,
                    const std::vector<int>& expected,
                    const std::vector<int>& expected2) {
    assert(expected.size() == expected2.size());
    size_t count = 0;
    for (const TestObject& node : list) {
      ASSERT_EQ(expected[count], node.data);
      ASSERT_EQ(expected2[count], node.data2);
      count++;
    }
    ASSERT_EQ(count, expected.size());
  }
};

}  // end anonymous namespace

TEST_F(IntrusiveListTest, default_constructor) {
  TestObjectList list;
}

TEST_F(IntrusiveListTest, node_constructor) {
  TestObjectList list(std::make_unique<TestObject>(1));
  AssertListEq(list, {1});
}

TEST_F(IntrusiveListTest, node_move_constructor) {
  TestObjectList list(TestObject(1));
  AssertListEq(list, {1});
}

TEST_F(IntrusiveListTest, move_constructor) {
  TestObjectList list1 = NewList({1, 2, 3});
  TestObjectList list2(std::move(list1));
  AssertListEq(list1, {});
  AssertListEq(list2, {1, 2, 3});
}

TEST_F(IntrusiveListTest, move_assignment_operator) {
  TestObjectList list1 = NewList({1, 2, 3});
  TestObjectList list2;

  list2 = std::move(list1);
  AssertListEq(list1, {});
  AssertListEq(list2, {1, 2, 3});
}

namespace {

class IntrusiveListIteratorTest : public IntrusiveListTest {
 protected:
  virtual void SetUp() {
    IntrusiveListTest::SetUp();

    list_.emplace_back(1);
    list_.emplace_back(2);
    list_.emplace_back(3);
  }

  virtual void TearDown() {
    list_.clear();

    IntrusiveListTest::TearDown();
  }

  template <typename Iter>
  void TestForward(Iter first, Iter last, const std::vector<int>& expected) {
    size_t count = 0;
    while (first != last) {
      ASSERT_EQ(expected[count], first->data);
      ++first;
      ++count;
    }
    ASSERT_EQ(count, expected.size());
  }

  template <typename Iter>
  void TestBackward(Iter first, Iter last, const std::vector<int>& expected) {
    size_t count = 0;
    while (first != last) {
      --last;
      ASSERT_EQ(expected[count], last->data);
      ++count;
    }
    ASSERT_EQ(count, expected.size());
  }

  TestObjectList list_;
  const TestObjectList& clist_ = list_;
};

}  // end of anonymous namespace

TEST_F(IntrusiveListIteratorTest, begin_end_forward) {
  TestForward(list_.begin(), list_.end(), {1, 2, 3});
  TestForward(clist_.begin(), clist_.end(), {1, 2, 3});
}

TEST_F(IntrusiveListIteratorTest, rbegin_rend_forward) {
  TestForward(list_.rbegin(), list_.rend(), {3, 2, 1});
  TestForward(clist_.rbegin(), clist_.rend(), {3, 2, 1});
}

TEST_F(IntrusiveListIteratorTest, cbegin_cend_forward) {
  TestForward(list_.cbegin(), list_.cend(), {1, 2, 3});
  TestForward(clist_.cbegin(), clist_.cend(), {1, 2, 3});
}

TEST_F(IntrusiveListIteratorTest, crbegin_crend_forward) {
  TestForward(list_.crbegin(), list_.crend(), {3, 2, 1});
  TestForward(clist_.crbegin(), clist_.crend(), {3, 2, 1});
}

TEST_F(IntrusiveListIteratorTest, begin_end_backward) {
  TestBackward(list_.begin(), list_.end(), {3, 2, 1});
  TestBackward(clist_.begin(), clist_.end(), {3, 2, 1});
}

TEST_F(IntrusiveListIteratorTest, rbegin_rend_backward) {
  TestBackward(list_.rbegin(), list_.rend(), {1, 2, 3});
  TestBackward(clist_.rbegin(), clist_.rend(), {1, 2, 3});
}

TEST_F(IntrusiveListIteratorTest, cbegin_cend_backward) {
  TestBackward(list_.cbegin(), list_.cend(), {3, 2, 1});
  TestBackward(clist_.cbegin(), clist_.cend(), {3, 2, 1});
}

TEST_F(IntrusiveListIteratorTest, crbegin_crend_backward) {
  TestBackward(list_.crbegin(), list_.crend(), {1, 2, 3});
  TestBackward(clist_.crbegin(), clist_.crend(), {1, 2, 3});
}

TEST_F(IntrusiveListTest, size_empty) {
  TestObjectList list;
  ASSERT_EQ(0U, list.size());
  ASSERT_TRUE(list.empty());

  list.emplace_back(1);
  ASSERT_EQ(1U, list.size());
  ASSERT_FALSE(list.empty());
}

TEST_F(IntrusiveListTest, front_back) {
  TestObjectList list;

  list.emplace_back(1);
  ASSERT_EQ(1, list.front().data);
  ASSERT_EQ(1, list.back().data);

  list.emplace_back(2);
  ASSERT_EQ(1, list.front().data);
  ASSERT_EQ(2, list.back().data);

  const TestObjectList& clist = list;
  ASSERT_EQ(1, clist.front().data);
  ASSERT_EQ(2, clist.back().data);
}

TEST_F(IntrusiveListTest, emplace_front) {
  TestObjectList list;

  // Pass an additional arg to show that forwarding works properly.
  list.emplace_front(1, 100);
  list.emplace_front(2, 200);
  list.emplace_front(3, 300);
  list.emplace_front(4, 400);

  AssertListEq(list, {4, 3, 2, 1}, {400, 300, 200, 100});
}

TEST_F(IntrusiveListTest, emplace_back) {
  TestObjectList list;

  // Pass an additional arg to show that forwarding works properly.
  list.emplace_back(1, 100);
  list.emplace_back(2, 200);
  list.emplace_back(3, 300);
  list.emplace_back(4, 400);

  AssertListEq(list, {1, 2, 3, 4}, {100, 200, 300, 400});
}

TEST_F(IntrusiveListTest, push_front_pointer) {
  TestObjectList list;

  list.push_front(std::make_unique<TestObject>(1));
  list.push_front(std::make_unique<TestObject>(2));
  list.push_front(std::make_unique<TestObject>(3));

  AssertListEq(list, {3, 2, 1});
}

TEST_F(IntrusiveListTest, push_back_pointer) {
  TestObjectList list;

  list.push_back(std::make_unique<TestObject>(1));
  list.push_back(std::make_unique<TestObject>(2));
  list.push_back(std::make_unique<TestObject>(3));

  AssertListEq(list, {1, 2, 3});
}

TEST_F(IntrusiveListTest, push_front_move) {
  TestObjectList list;

  list.push_front(TestObject(1));
  list.push_front(TestObject(2));
  list.push_front(TestObject(3));

  AssertListEq(list, {3, 2, 1});
}

TEST_F(IntrusiveListTest, push_back_move) {
  TestObjectList list;

  list.push_back(TestObject(1));
  list.push_back(TestObject(2));
  list.push_back(TestObject(3));

  AssertListEq(list, {1, 2, 3});
}

TEST_F(IntrusiveListTest, pop_front) {
  TestObjectList list = NewList({1, 2, 3, 4});

  list.pop_front();
  AssertListEq(list, {2, 3, 4});
  list.pop_front();
  AssertListEq(list, {3, 4});
  list.pop_front();
  AssertListEq(list, {4});
  list.pop_front();
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, pop_back) {
  TestObjectList list = NewList({1, 2, 3, 4});

  list.pop_back();
  AssertListEq(list, {1, 2, 3});
  list.pop_back();
  AssertListEq(list, {1, 2});
  list.pop_back();
  AssertListEq(list, {1});
  list.pop_back();
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, extract_front) {
  TestObjectList list = NewList({1, 2, 3});

  std::unique_ptr<TestObject> t1(list.extract_front());
  ASSERT_EQ(1, t1->data);
  AssertListEq(list, {2, 3});

  std::unique_ptr<TestObject> t2(list.extract_front());
  ASSERT_EQ(2, t2->data);
  AssertListEq(list, {3});

  std::unique_ptr<TestObject> t3(list.extract_front());
  ASSERT_EQ(3, t3->data);
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, extract_back) {
  TestObjectList list = NewList({1, 2, 3});

  std::unique_ptr<TestObject> t1(list.extract_back());
  ASSERT_EQ(3, t1->data);
  AssertListEq(list, {1, 2});

  std::unique_ptr<TestObject> t2(list.extract_back());
  ASSERT_EQ(2, t2->data);
  AssertListEq(list, {1});

  std::unique_ptr<TestObject> t3(list.extract_back());
  ASSERT_EQ(1, t3->data);
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, emplace) {
  TestObjectList list;

  // Pass an additional arg to show that forwarding works properly.
  list.emplace(list.begin(), 2, 200);
  list.emplace(list.end(), 4, 400);
  list.emplace(std::next(list.begin()), 3, 300);
  list.emplace(list.begin(), 1, 100);

  AssertListEq(list, {1, 2, 3, 4}, {100, 200, 300, 400});
}

TEST_F(IntrusiveListTest, insert_pointer) {
  TestObjectList list;

  list.insert(list.begin(), std::make_unique<TestObject>(2));
  list.insert(list.end(), std::make_unique<TestObject>(4));
  list.insert(std::next(list.begin()), std::make_unique<TestObject>(3));
  list.insert(list.begin(), std::make_unique<TestObject>(1));

  AssertListEq(list, {1, 2, 3, 4});
}

TEST_F(IntrusiveListTest, insert_move) {
  TestObjectList list;

  list.insert(list.begin(), TestObject(2));
  list.insert(list.end(), TestObject(4));
  list.insert(std::next(list.begin()), TestObject(3));
  list.insert(list.begin(), TestObject(1));

  AssertListEq(list, {1, 2, 3, 4});
}

TEST_F(IntrusiveListTest, extract) {
  TestObjectList list = NewList({1, 2, 3, 4});

  TestObjectList::iterator t1_iter = std::next(list.begin(), 0);
  TestObjectList::iterator t2_iter = std::next(list.begin(), 1);
  TestObjectList::iterator t3_iter = std::next(list.begin(), 2);
  TestObjectList::iterator t4_iter = std::next(list.begin(), 3);

  std::unique_ptr<TestObject> t2(list.extract(t2_iter));
  ASSERT_EQ(2, t2->data);
  AssertListEq(list, {1, 3, 4});

  std::unique_ptr<TestObject> t4(list.extract(t4_iter));
  ASSERT_EQ(4, t4->data);
  AssertListEq(list, {1, 3});

  std::unique_ptr<TestObject> t1(list.extract(t1_iter));
  ASSERT_EQ(1, t1->data);
  AssertListEq(list, {3});

  std::unique_ptr<TestObject> t3(list.extract(t3_iter));
  ASSERT_EQ(3, t3->data);
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, erase) {
  TestObjectList list = NewList({1, 2, 3, 4});

  TestObjectList::iterator t1_iter = std::next(list.begin(), 0);
  TestObjectList::iterator t2_iter = std::next(list.begin(), 1);
  TestObjectList::iterator t3_iter = std::next(list.begin(), 2);
  TestObjectList::iterator t4_iter = std::next(list.begin(), 3);

  // erase returns an iterator to the following node.
  ASSERT_EQ(3, list.erase(t2_iter)->data);
  AssertListEq(list, {1, 3, 4});

  ASSERT_EQ(list.end(), list.erase(t4_iter));
  AssertListEq(list, {1, 3});

  ASSERT_EQ(3, list.erase(t1_iter)->data);
  AssertListEq(list, {3});

  ASSERT_EQ(list.end(), list.erase(t3_iter));
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, erase_range) {
  TestObjectList list = NewList({1, 2, 3, 4, 5, 6});

  // OK to erase an empty range.
  list.erase(list.begin(), list.begin());
  list.erase(list.end(), list.end());

  // Erase the first element (1).
  list.erase(list.begin(), std::next(list.begin()));
  AssertListEq(list, {2, 3, 4, 5, 6});

  // Erase the last element (6).
  list.erase(std::prev(list.end()), list.end());
  AssertListEq(list, {2, 3, 4, 5});

  // Erase [3, 4] => [2, 5]
  list.erase(std::next(list.begin()), std::prev(list.end()));
  AssertListEq(list, {2, 5});

  // Erase the rest.
  list.erase(list.begin(), list.end());
  AssertListEq(list, {});
}

TEST_F(IntrusiveListTest, swap) {
  TestObjectList list1 = NewList({1, 2, 3, 4});

  TestObjectList list2 = NewList({100, 200, 300});

  AssertListEq(list1, {1, 2, 3, 4});
  AssertListEq(list2, {100, 200, 300});

  list1.swap(list2);

  AssertListEq(list1, {100, 200, 300});
  AssertListEq(list2, {1, 2, 3, 4});
}

TEST_F(IntrusiveListTest, clear) {
  TestObjectList list = NewList({1, 2, 3, 4});

  ASSERT_FALSE(list.empty());

  list.clear();

  ASSERT_EQ(0U, list.size());
  ASSERT_TRUE(list.empty());
}

TEST_F(IntrusiveListTest, splice_list) {
  TestObjectList list1 = NewList({1, 2, 3});

  // Splice at beginning.
  TestObjectList list2 = NewList({100, 200});
  list1.splice(list1.begin(), list2);
  AssertListEq(list1, {100, 200, 1, 2, 3});
  AssertListEq(list2, {});

  // Splice at end.
  TestObjectList list3 = NewList({500, 600, 700});
  list1.splice(list1.end(), list3);
  AssertListEq(list1, {100, 200, 1, 2, 3, 500, 600, 700});
  AssertListEq(list3, {});

  // Splice in the middle.
  TestObjectList list4 = NewList({400});
  list1.splice(std::next(list1.begin(), 4), list4);
  AssertListEq(list1, {100, 200, 1, 2, 400, 3, 500, 600, 700});
  AssertListEq(list4, {});
}

TEST_F(IntrusiveListTest, splice_list_move) {
  TestObjectList list1 = NewList({1, 2, 3});

  // Splice at beginning.
  list1.splice(list1.begin(), NewList({100, 200}));
  AssertListEq(list1, {100, 200, 1, 2, 3});

  // Splice at end.
  list1.splice(list1.end(), NewList({500, 600, 700}));
  AssertListEq(list1, {100, 200, 1, 2, 3, 500, 600, 700});

  // Splice in the middle.
  list1.splice(std::next(list1.begin(), 4), NewList({400}));
  AssertListEq(list1, {100, 200, 1, 2, 400, 3, 500, 600, 700});
}

TEST_F(IntrusiveListTest, splice_node) {
  TestObjectList list1 = NewList({1, 2, 3});

  // Splice at beginning.
  TestObjectList list2 = NewList({100, 200});
  list1.splice(list1.begin(), list2, list2.begin());
  AssertListEq(list1, {100, 1, 2, 3});
  AssertListEq(list2, {200});

  // Splice at end.
  TestObjectList list3 = NewList({500, 600, 700});
  list1.splice(list1.end(), list3, std::next(list3.begin(), 2));
  AssertListEq(list1, {100, 1, 2, 3, 700});
  AssertListEq(list3, {500, 600});

  // Splice in the middle.
  TestObjectList list4 = NewList({400});
  list1.splice(std::next(list1.begin(), 3), list4, list4.begin());
  AssertListEq(list1, {100, 1, 2, 400, 3, 700});
  AssertListEq(list4, {});
}

TEST_F(IntrusiveListTest, splice_range) {
  TestObjectList list1 = NewList({1, 2, 3});

  // Splice at beginning.
  TestObjectList list2 = NewList({100, 200, 300});
  list1.splice(list1.begin(), list2, list2.begin(), std::prev(list2.end()));
  AssertListEq(list1, {100, 200, 1, 2, 3});
  AssertListEq(list2, {300});

  // Splice at end.
  TestObjectList list3 = NewList({500, 600, 700});
  list1.splice(list1.end(), list3, std::next(list3.begin()), list3.end());
  AssertListEq(list1, {100, 200, 1, 2, 3, 600, 700});
  AssertListEq(list3, {500});

  // Splice in the middle.
  TestObjectList list4 = NewList({400});
  list1.splice(std::next(list1.begin(), 4), list4, list4.begin(), list4.end());
  AssertListEq(list1, {100, 200, 1, 2, 400, 3, 600, 700});
  AssertListEq(list4, {});
}
