/******************************************************************************
 *
 *  Copyright (C) 2016 Google, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at:
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 ******************************************************************************/
#pragma once

#include <memory>
#include <mutex>
#include <queue>

namespace system_bt_osi {

/*
 *   LeakyBondedQueue<T>
 *
 * - LeakyLondedQueue<T> is a fixed size queue that leaks oldest item when
 *   reaching its capacity. This is useful in creating memory bonded data
 *   structure where freshness is more important than full coverage.
 * - The queue is protected by a simple mutex and is thread-safe, although
 *   improvements could be made to lock enqueue and dequeue separately, it
 *   is not implemented at this moment due to lack of demand
 * - The queue uses unique_ptr to automatically free its content when it is
 *   destructed. It is the user's responsibility to implement T's destructor
 *   correctly.
 *
 */
template <class T>
class LeakyBondedQueue {
 public:
  LeakyBondedQueue(size_t capacity);
  /* Default destructor
   *
   * Call Clear() and free the queue structure itself
   */
  ~LeakyBondedQueue();
  /*
   * Add item NEW_ITEM to the underlining queue. If the queue is full, pop
   * the oldest item
   */
  void Enqueue(T* new_item);
  /*
   * Add item NEW_ITEM to the underlining queue. If the queue is full, dequeue
   * the oldest item and returns it to the caller. Return nullptr otherwise.
   */
  T* EnqueueWithPop(T* new_item);
  /*
   * Dequeues the oldest item from the queue. Return nullptr if queue is empty
   */
  T* Dequeue();
  /*
   * Returns the length of queue
   */
  size_t Length();
  /*
   * Returns the defined capacity of the queue
   */
  size_t Capacity();
  /*
   * Returns whether the queue is empty
   */
  bool Empty();
  /*
   * Pops all items from the queue
   */
  void Clear();

 private:
  // Put item in unique_ptr so that they get freed automatically when poped or
  // when queue_ is freed
  std::queue<std::unique_ptr<T>> queue_;
  std::mutex lock_;
  size_t capacity_;
};

/*
* Definitions must be in the header for template classes
*/

template <class T>
LeakyBondedQueue<T>::LeakyBondedQueue(size_t capacity) {
  capacity_ = capacity;
}

template <class T>
LeakyBondedQueue<T>::~LeakyBondedQueue() {}

template <class T>
void LeakyBondedQueue<T>::Enqueue(T* new_item) {
  std::lock_guard<std::mutex> lock(lock_);
  if ((queue_.size() + 1) > capacity_) {
    queue_.pop();
  }
  std::unique_ptr<T> item_ptr(new_item);
  queue_.push(std::move(item_ptr));
}

template <class T>
T* LeakyBondedQueue<T>::EnqueueWithPop(T* new_item) {
  std::lock_guard<std::mutex> lock(lock_);
  T* old_item = nullptr;
  if ((queue_.size() + 1) > capacity_) {
    std::unique_ptr<T> item = std::move(queue_.front());
    queue_.pop();
    old_item = item.release();
  }
  std::unique_ptr<T> item_ptr(new_item);
  queue_.push(std::move(item_ptr));
  return old_item;
}

template <class T>
T* LeakyBondedQueue<T>::Dequeue() {
  std::lock_guard<std::mutex> lock(lock_);
  std::unique_ptr<T> item = std::move(queue_.front());
  queue_.pop();
  return item.release();
}

template <class T>
void LeakyBondedQueue<T>::Clear() {
  std::lock_guard<std::mutex> lock(lock_);
  while (!queue_.empty()) {
    // unique_ptr does not need to be freed
    queue_.pop();
  }
}

template <class T>
size_t LeakyBondedQueue<T>::Length() {
  std::lock_guard<std::mutex> lock(lock_);
  return queue_.size();
}

template <class T>
size_t LeakyBondedQueue<T>::Capacity() {
  return capacity_;
}

template <class T>
bool LeakyBondedQueue<T>::Empty() {
  std::lock_guard<std::mutex> lock(lock_);
  return queue_.empty();
}

}  // namespace system_bt_osi
