/*
    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-2019 Telegram Systems LLP
*/
#pragma once
#include "td/utils/common.h"
#include "td/utils/logging.h"
#include 
#include 
#include 
namespace td {
// It is draft object pool implementaion
//
// Compared with std::shared_ptr:
// + WeakPtr are much faster. Just pointer copy. No barriers, no atomics.
// - We can't destroy object, because we don't know if it is pointed to by some weak pointer
//
template 
class ObjectPool {
  struct Storage;
 public:
  class WeakPtr {
   public:
    WeakPtr() : generation_(-1), storage_(nullptr) {
    }
    WeakPtr(int32 generation, Storage *storage) : generation_(generation), storage_(storage) {
    }
    DataT &operator*() const {
      return storage_->data;
    }
    DataT *operator->() const {
      return &**this;
    }
    // Pattern of usage: 1. Read an object 2. Check if read was valid via is_alive
    //
    // It is not very usual case of acquire/release use.
    // Instead of publishing an object via some flag we do the opposite.
    // We publish new generation via destruction of the data.
    // In usual case if we see a flag then we are able to use an object.
    // In our case if we have used an object and it is already invalid, then generation will mismatch
    bool is_alive() const {
      if (!storage_) {
        return false;
      }
      std::atomic_thread_fence(std::memory_order_acquire);
      return generation_ == storage_->generation.load(std::memory_order_relaxed);
    }
    // Used for ActorId
    bool is_alive_unsafe() const {
      if (!storage_) {
        return false;
      }
      return generation_ == storage_->generation.load(std::memory_order_relaxed);
    }
    bool empty() const {
      return storage_ == nullptr;
    }
    void clear() {
      generation_ = -1;
      storage_ = nullptr;
    }
    int32 generation() {
      return generation_;
    }
   private:
    int32 generation_;
    Storage *storage_;
  };
  class OwnerPtr {
   public:
    OwnerPtr() = default;
    OwnerPtr(const OwnerPtr &) = delete;
    OwnerPtr &operator=(const OwnerPtr &) = delete;
    OwnerPtr(OwnerPtr &&other) : storage_(other.storage_), parent_(other.parent_) {
      other.storage_ = nullptr;
      other.parent_ = nullptr;
    }
    OwnerPtr &operator=(OwnerPtr &&other) {
      if (this != &other) {
        storage_ = other.storage_;
        parent_ = other.parent_;
        other.storage_ = nullptr;
        other.parent_ = nullptr;
      }
      return *this;
    }
    ~OwnerPtr() {
      reset();
    }
    DataT *get() {
      return &storage_->data;
    }
    DataT &operator*() {
      return *get();
    }
    DataT *operator->() {
      return get();
    }
    const DataT *get() const {
      return &storage_->data;
    }
    const DataT &operator*() const {
      return *get();
    }
    const DataT *operator->() const {
      return get();
    }
    WeakPtr get_weak() {
      return WeakPtr(storage_->generation.load(std::memory_order_relaxed), storage_);
    }
    int32 generation() {
      return storage_->generation.load(std::memory_order_relaxed);
    }
    Storage *release() {
      auto result = storage_;
      storage_ = nullptr;
      return result;
    }
    bool empty() const {
      return storage_ == nullptr;
    }
    void reset() {
      if (storage_ != nullptr) {
        // for crazy cases when data owns owner pointer to itself.
        auto tmp = storage_;
        storage_ = nullptr;
        parent_->release(OwnerPtr(tmp, parent_));
      }
    }
   private:
    friend class ObjectPool;
    OwnerPtr(Storage *storage, ObjectPool *parent) : storage_(storage), parent_(parent) {
    }
    Storage *storage_ = nullptr;
    ObjectPool *parent_ = nullptr;
  };
  template 
  OwnerPtr create(ArgsT &&... args) {
    Storage *storage = get_storage();
    storage->init_data(std::forward(args)...);
    return OwnerPtr(storage, this);
  }
  OwnerPtr create_empty() {
    Storage *storage = get_storage();
    return OwnerPtr(storage, this);
  }
  void set_check_empty(bool flag) {
    check_empty_flag_ = flag;
  }
  void release(OwnerPtr &&owner_ptr) {
    Storage *storage = owner_ptr.release();
    storage->destroy_data();
    release_storage(storage);
  }
  ObjectPool() = default;
  ObjectPool(const ObjectPool &) = delete;
  ObjectPool &operator=(const ObjectPool &) = delete;
  ObjectPool(ObjectPool &&other) = delete;
  ObjectPool &operator=(ObjectPool &&other) = delete;
  ~ObjectPool() {
    while (head_.load()) {
      auto to_delete = head_.load();
      head_ = to_delete->next;
      delete to_delete;
      storage_count_--;
    }
    LOG_CHECK(storage_count_.load() == 0) << storage_count_.load();
  }
 private:
  struct Storage {
    // union {
    DataT data;
    //};
    Storage *next = nullptr;
    std::atomic generation{1};
    template 
    void init_data(ArgsT &&... args) {
      // new  (&data) DataT(std::forward(args)...);
      data = DataT(std::forward(args)...);
    }
    void destroy_data() {
      generation.fetch_add(1, std::memory_order_relaxed);
      std::atomic_thread_fence(std::memory_order_release);
      data.clear();
    }
  };
  std::atomic storage_count_{0};
  std::atomic head_{static_cast(nullptr)};
  bool check_empty_flag_ = false;
  // TODO(perf): allocation Storages in chunks? Anyway we won't be able to release them.
  // TODO(perf): memory order
  // TODO(perf): use another non lockfree list for release on the same thread
  // only one thread, so no aba problem
  Storage *get_storage() {
    if (head_.load() == nullptr) {
      storage_count_++;
      return new Storage();
    }
    Storage *res;
    while (true) {
      res = head_.load();
      auto *next = res->next;
      if (head_.compare_exchange_weak(res, next)) {
        break;
      }
    }
    return res;
  }
  // release can be called from other thread
  void release_storage(Storage *storage) {
    while (true) {
      auto *save_head = head_.load();
      storage->next = save_head;
      if (head_.compare_exchange_weak(save_head, storage)) {
        break;
      }
    }
  }
};
}  // namespace td