
#ifndef PLS_LOCKING_DEQUE_H
#define PLS_LOCKING_DEQUE_H

#include <mutex>

#include "pls/internal/base/spin_lock.h"
#include "pls/internal/data_structures/aligned_stack.h"

namespace pls {
namespace internal {
namespace data_structures {

/**
 * Wraps any object into a deque item.
 */
template<typename Item>
struct locking_deque_item {
  Item *item_;

  locking_deque_item *prev_;
  locking_deque_item *next_;

};

template<typename Item, typename Content>
struct locking_deque_container : public locking_deque_item<Item> {
  Content content_;

 public:
  explicit locking_deque_container(const Content &content_) : content_{content_} {}
};

/**
 * A double linked list based deque.
 * Storage is therefore only needed for the individual items.
 *
 * @tparam Item The type of items stored in this deque
 */
template<typename Item>
class locking_deque {
  aligned_stack *stack_;

  locking_deque_item<Item> *head_;
  locking_deque_item<Item> *tail_;

  base::spin_lock lock_;

 public:
  using state = aligned_stack::state;

  explicit locking_deque(aligned_stack *stack)
      : stack_{stack}, head_{nullptr}, tail_{nullptr}, lock_{} {}

  template<typename T>
  T *push_tail(const T &new_item) {
    static_assert(std::is_same<Item, T>::value || std::is_base_of<Item, T>::value,
                  "Must only push types of <Item> onto work_stealing_deque<Item>");

    std::lock_guard<base::spin_lock> lock{lock_};
    auto deque_item = stack_->push(locking_deque_container<Item, T>{new_item});
    deque_item->item_ = &deque_item->content_;

    if (tail_ != nullptr) {
      tail_->next_ = deque_item;
    } else {
      head_ = deque_item;
    }
    deque_item->prev_ = tail_;
    deque_item->next_ = nullptr;
    tail_ = deque_item;

    return &deque_item->content_;
  }

  Item *pop_tail() {
    std::lock_guard<base::spin_lock> lock{lock_};

    if (tail_ == nullptr) {
      return nullptr;
    }

    auto result = tail_;
    tail_ = tail_->prev_;
    if (tail_ == nullptr) {
      head_ = nullptr;
    } else {
      tail_->next_ = nullptr;
    }

    return result->item_;
  }

  Item *pop_head() {
    std::lock_guard<base::spin_lock> lock{lock_};

    if (head_ == nullptr) {
      return nullptr;
    }

    auto result = head_;
    head_ = head_->next_;
    if (head_ == nullptr) {
      tail_ = nullptr;
    } else {
      head_->prev_ = nullptr;
    }

    return result->item_;
  }

  void release_memory_until(state state) {
    stack_->reset_state(state);
  }
  state save_state() {
    return stack_->save_state();
  }
};

}
}
}

#endif //PLS_LOCKING_DEQUE_H
