//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2021
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#pragma once

#include "td/utils/common.h"
#include "td/utils/List.h"

#include <mutex>

namespace td {

template <class DataT>
class TsList;

template <class DataT>
class TsListNode : protected ListNode {
 public:
  TsListNode() {
    clear();
  }
  explicit TsListNode(DataT &&data) : data_(std::move(data)) {
    clear();
  }

  ~TsListNode() {
    remove();
  }

  std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT;

  TsListNode(const TsListNode &) = delete;
  TsListNode &operator=(const TsListNode &) = delete;

  TsListNode(TsListNode &&other) {
    other.validate();
    if (other.empty()) {
      data_ = std::move(other.data_);
      clear();
    } else {
      auto guard = other.lock();
      init_from(std::move(other));
    }
    validate();
    other.validate();
  }

  TsListNode &operator=(TsListNode &&other) {
    validate();
    if (this == &other) {
      return *this;
    }
    other.validate();
    remove();

    if (other.empty()) {
      data_ = std::move(other.data_);
    } else {
      auto guard = other.lock();
      init_from(std::move(other));
    }

    validate();
    other.validate();
    return *this;
  }

  void validate() {
    if (empty()) {
      CHECK(ListNode::empty());
    } else {
      auto guard = lock();
      CHECK(!ListNode::empty() || is_root);
    }
  }

  void remove() {
    validate();
    if (is_root) {
      CHECK(ListNode::empty());
      return;
    }
    if (empty()) {
      CHECK(ListNode::empty());
      return;
    }
    {
      auto guard = lock();
      ListNode::remove();
      if (!is_root) {
        parent = nullptr;
      }
    }
    validate();
  }

  void put(TsListNode *other) {
    validate();
    other->validate();
    DCHECK(other->empty());
    DCHECK(!empty());
    DCHECK(!other->is_root);
    {
      auto guard = lock();
      ListNode::put(other);
      other->parent = parent;
    }
    validate();
    other->validate();
  }

  void put_back(TsListNode *other) {
    DCHECK(other->empty());
    DCHECK(!empty());
    DCHECK(!other->is_root);
    auto guard = lock();
    ListNode::put_back(other);
    other->parent = parent;
  }

  bool empty() const {
    return parent == nullptr;
  }

  TsListNode *get_next() {
    return static_cast<TsListNode *>(next);
  }
  TsListNode *get_prev() {
    return static_cast<TsListNode *>(prev);
  }

  DataT &get_data_unsafe() {
    return data_;
  }

 private:
  TsList<DataT> *parent;
  bool is_root{false};
  DataT data_;

  friend class TsList<DataT>;

  void clear() {
    ListNode::clear();
    if (!is_root) {
      parent = nullptr;
    }
  }

  void init_from(TsListNode &&other) {
    ListNode::init_from(std::move(other));
    parent = other.parent;
    other.parent = nullptr;
    data_ = std::move(other.data_);
  }
};

template <class DataT>
class TsList : public TsListNode<DataT> {
 public:
  TsList() {
    this->parent = this;
    this->is_root = true;
  }
  TsList(const TsList &) = delete;
  TsList &operator=(const TsList &) = delete;
  TsList(TsList &&) = delete;
  TsList &operator=(TsList &&) = delete;
  ~TsList() {
    auto guard = lock();
    while (true) {
      auto res = static_cast<TsListNode<DataT> *>(ListNode::get());
      if (!res) {
        break;
      }
      res->parent = nullptr;
    }
    this->parent = nullptr;
  }
  std::unique_lock<std::mutex> lock() TD_WARN_UNUSED_RESULT {
    return std::unique_lock<std::mutex>(mutex_);
  }
  TsListNode<DataT> *begin() {
    return this->get_next();
  }
  TsListNode<DataT> *end() {
    return this;
  }
  TsListNode<DataT> *get() {
    auto guard = lock();
    auto res = static_cast<TsListNode<DataT> *>(ListNode::get());
    if (res) {
      res->parent = nullptr;
    }
    return res;
  }

 private:
  std::mutex mutex_;
};

template <class DataT>
std::unique_lock<std::mutex> TsListNode<DataT>::lock() {
  if (parent == nullptr) {
    return {};
  }
  CHECK(parent != nullptr);
  return parent->lock();
}

}  // namespace td