/*
    This file is part of TON Blockchain Library.
    TON Blockchain Library is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.
    TON Blockchain Library 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.  See the
    GNU Lesser General Public License for more details.
    You should have received a copy of the GNU Lesser General Public License
    along with TON Blockchain Library.  If not, see .
    Copyright 2017-2020 Telegram Systems LLP
*/
#include "td/utils/common.h"
#include "td/utils/List.h"
#include "td/utils/MovableValue.h"
#include "td/utils/port/thread.h"
#include "td/utils/Random.h"
#include "td/utils/tests.h"
#include "td/utils/TsList.h"
#include 
#include 
#include 
#include 
struct ListData {
  td::MovableValue value;
  td::MovableValue in_list;
  ListData() = default;
  ListData(td::uint64 value, bool in_list) : value(value), in_list(in_list) {
  }
};
struct Node : public td::ListNode {
  Node() = default;
  explicit Node(ListData data) : data(std::move(data)) {
  }
  ListData data;
};
static ListData &get_data(Node &node) {
  return node.data;
}
static ListData &get_data(td::TsListNode &node) {
  return node.get_data_unsafe();
}
static std::unique_lock lock(td::ListNode &node) {
  return {};
}
static std::unique_lock lock(td::TsListNode &node) {
  return node.lock();
}
template 
static void do_run_list_test(ListRootT &root, std::atomic &id) {
  td::vector nodes;
  td::Random::Xorshift128plus rnd(123);
  auto next_id = [&] {
    return ++id;
  };
  auto add_node = [&] {
    if (nodes.size() >= 20) {
      return;
    }
    nodes.emplace_back(NodeT({next_id(), false}));
  };
  auto pop_node = [&] {
    if (nodes.empty()) {
      return;
    }
    nodes.pop_back();
  };
  auto random_node_index = [&] {
    CHECK(!nodes.empty());
    return rnd.fast(0, static_cast(nodes.size()) - 1);
  };
  auto link_node = [&] {
    if (nodes.empty()) {
      return;
    }
    auto i = random_node_index();
    nodes[i].remove();
    get_data(nodes[i]) = ListData(next_id(), true);
    root.put(&nodes[i]);
  };
  auto unlink_node = [&] {
    if (nodes.empty()) {
      return;
    }
    auto i = random_node_index();
    nodes[i].remove();
    get_data(nodes[i]).in_list = false;
  };
  auto swap_nodes = [&] {
    if (nodes.empty()) {
      return;
    }
    auto i = random_node_index();
    auto j = random_node_index();
    std::swap(nodes[i], nodes[j]);
  };
  auto set_node = [&] {
    if (nodes.empty()) {
      return;
    }
    auto i = random_node_index();
    auto j = random_node_index();
    nodes[i] = std::move(nodes[j]);
  };
  auto validate = [&] {
    std::multiset in_list, not_in_list;
    for (auto &node : nodes) {
      if (get_data(node).in_list.get()) {
        in_list.insert(get_data(node).value.get());
      } else {
        not_in_list.insert(get_data(node).value.get());
      }
    }
    auto guard = lock(root);
    for (auto *begin = root.begin(), *end = root.end(); begin != end; begin = begin->get_next()) {
      auto &data = get_data(*static_cast(begin));
      CHECK(data.in_list.get());
      CHECK(data.value.get() != 0);
      auto it = in_list.find(data.value.get());
      if (it != in_list.end()) {
        in_list.erase(it);
      } else {
        ASSERT_EQ(0u, not_in_list.count(data.value.get()));
      }
    }
    ASSERT_EQ(0u, in_list.size());
  };
  td::RandomSteps steps(
      {{add_node, 3}, {pop_node, 1}, {unlink_node, 1}, {link_node, 3}, {swap_nodes, 1}, {set_node, 1}, {validate, 1}});
  for (int i = 0; i < 10000; i++) {
    steps.step(rnd);
  }
}
TEST(Misc, List) {
  td::ListNode root;
  std::atomic id{0};
  for (std::size_t i = 0; i < 4; i++) {
    do_run_list_test(root, id);
  }
}
TEST(Misc, TsList) {
  td::TsList root;
  std::atomic id{0};
  for (std::size_t i = 0; i < 4; i++) {
    do_run_list_test, td::TsList, td::TsListNode>(root, id);
  }
}
TEST(Misc, TsListConcurrent) {
  td::TsList root;
  td::vector threads;
  std::atomic id{0};
  for (std::size_t i = 0; i < 4; i++) {
    threads.emplace_back(
        [&] { do_run_list_test, td::TsList, td::TsListNode>(root, id); });
  }
}