/*
    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 2019-2020 Telegram Systems LLP
*/
#pragma once
#include "td/utils/Status.h"
#include "td/utils/Span.h"
namespace td {
template 
class StealingQueue {
 public:
  // tries to put a value
  // returns if succeeded
  // only owner is alowed to to do this
  template 
  void local_push(T value, F&& overflow_f) {
    while (true) {
      auto tail = tail_.load(std::memory_order_relaxed);
      auto head = head_.load();  //TODO: memory order
      if (static_cast(tail - head) < N) {
        buf_[tail & MASK].store(value, std::memory_order_relaxed);
        tail_.store(tail + 1, std::memory_order_release);
        return;
      }
      // queue is full
      // TODO: batch insert into global queue?
      auto n = N / 2 + 1;
      auto new_head = head + n;
      if (!head_.compare_exchange_strong(head, new_head)) {
        continue;
      }
      for (size_t i = 0; i < n; i++) {
        overflow_f(buf_[(i + head) & MASK].load(std::memory_order_relaxed));
      }
      overflow_f(value);
      return;
    }
  }
  // tries to pop a value
  // returns if succeeded
  // only owner is alowed to to do this
  bool local_pop(T& value) {
    auto tail = tail_.load(std::memory_order_relaxed);
    auto head = head_.load();
    if (head == tail) {
      return false;
    }
    value = buf_[head & MASK].load(std::memory_order_relaxed);
    return head_.compare_exchange_strong(head, head + 1);
  }
  bool steal(T& value, StealingQueue& other) {
    while (true) {
      auto tail = tail_.load(std::memory_order_relaxed);
      auto head = head_.load();  //TODO: memory order
      auto other_head = other.head_.load();
      auto other_tail = other.tail_.load(std::memory_order_acquire);
      if (other_tail < other_head) {
        continue;
      }
      size_t n = other_tail - other_head;
      if (n > N) {
        continue;
      }
      n -= n / 2;
      n = td::min(n, static_cast(head + N - tail));
      if (n == 0) {
        return false;
      }
      for (size_t i = 0; i < n; i++) {
        buf_[(i + tail) & MASK].store(other.buf_[(i + other_head) & MASK].load(std::memory_order_relaxed),
                                      std::memory_order_relaxed);
      }
      if (!other.head_.compare_exchange_strong(other_head, other_head + n)) {
        continue;
      }
      n--;
      value = buf_[(tail + n) & MASK].load(std::memory_order_relaxed);
      tail_.store(tail + n, std::memory_order_release);
      return true;
    }
  }
  StealingQueue() {
    for (auto& x : buf_) {
      x.store(T{}, std::memory_order_relaxed);
    }
    std::atomic_thread_fence(std::memory_order_seq_cst);
  }
 private:
  std::atomic head_{0};
  std::atomic tail_{0};
  static constexpr size_t MASK{N - 1};
  std::array, N> buf_;
};
};  // namespace td