#pragma once

#ifndef TCG_POOL_H
#define TCG_POOL_H

// STD includes
#include "assert.h"

#include <vector>
#include <algorithm>
#include <functional>

// Discriminate rvalues support - either use Boost's config.hpp for automatic
// lookup,
// or deal with it manually

#ifndef TCG_RVALUES_SUPPORT
#include <boost/config.hpp>

#ifdef BOOST_NO_RVALUE_REFERENCES
#define TCG_RVALUES_SUPPORT 0
#else
#define TCG_RVALUES_SUPPORT 1
#endif
#endif

/*!
  \file     pool.h

  \brief    This file contains an implementation of the \a pool container
  concept.

  \details  A pool is a container that supports two fundamental operations,
  acquire() and
            release(), whose purpose is that of marking access operations to a
  resource
            that <I>should not be destroyed</I> when the access operation ends.

            Creation and destruction of resources can be a time-consuming task
  that could
            be avoided when it is known that resources could be reused - which
  is the very
            reason why caching can be a useful approach to some problems.

            The classical cache concept is nothing more than that of a standard
  associative
            map, tweaked to accommodate specific commodities - such as managing
  the cache
            size.

            However, sometimes resources could be considered to be effectively
  identical,
            to the point that assigning a key is merely a way to keep the
  objects
            distinct in the cache. Users are in the condition to require only
  two functions,
            one to acquire() a resource, the other to release() it.

            The Pool concept we are introducing satisfies the following ruleset:

            <UL>
            <LI> The pool is a dynamical container which allocates resources by
  invoking the
                 objects' default constructor. </LI>
            <LI> Objects in the pool are \b not destructed, except when the pool
  is explicitly
                 \a squeezed by the user.</LI>
            <LI> Keys are indices, returned by the pool with an incremental
  ordering. </LI>
            <LI> An object which has been acquired cannot be acquired again
  until it has been
                 released </LI>
            </UL>
*/

namespace tcg {

//****************************************************************************
//    TCG Indices Pool  class
//****************************************************************************

template <typename T = size_t, typename Cont = std::vector<T>>
class indices_pool {
public:
  typedef T value_type;
  typedef Cont container_type;

private:
  T m_start;               //!< First index to be acquired
  T m_size;                //!< Current indices count
  Cont m_releasedIndices;  //!< Priority queue of released indices

public:
  indices_pool(value_type start = 0) : m_start(start), m_size(0) {}
  ~indices_pool() {}

  template <typename iter>
  indices_pool(value_type size, iter releasedBegin, iter releasedEnd,
               value_type start = 0)
      : m_start(start)
      , m_size(size)
      , m_releasedIndices(releasedBegin, releasedEnd) {
    std::make_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
                   std::greater<T>());
  }

  template <typename iter>
  indices_pool(iter acquiredBegin, iter acquiredEnd, value_type start = 0)
      : m_start(start) {
    if (acquiredBegin == acquiredEnd)
      m_size = 0;
    else {
      // Sort the acquired indices
      std::vector<T> acquired(acquiredBegin, acquiredEnd);

      std::sort(acquired.begin(), acquired.end());
      assert(acquired.front() >= m_start);

      // Scan them, adding holes in the acquired sequence to released indices
      m_size = acquired.back() - m_start + 1;

      assert(m_size >= (T)acquired.size());
      m_releasedIndices.reserve(m_size - acquired.size());

      T curIdx = m_start;

      typename std::vector<T>::iterator at, aEnd(acquired.end());
      for (at = acquired.begin(); at != aEnd; ++at, ++curIdx)
        for (; curIdx != *at; ++curIdx) m_releasedIndices.push_back(curIdx);

      std::make_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
                     std::greater<T>());
    }
  }

  value_type size() const { return m_size; }
  value_type releasedSize() const { return m_releasedIndices.size(); }
  value_type acquiredSize() const { return size() - releasedSize(); }

  const container_type &releasedIndices() const { return m_releasedIndices; }

  value_type acquire() {
    if (!m_releasedIndices.empty()) {
      value_type idx = m_releasedIndices.front();

      pop_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
               std::greater<T>());
      m_releasedIndices.pop_back();

      return idx;
    }

    return m_start + m_size++;
  }

  void release(value_type idx) {
    m_releasedIndices.push_back(idx);
    push_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
              std::greater<T>());
  }

  void clear() {
    m_size = 0;
    m_releasedIndices.clear();
  }

  //! Removes pool entries whose index is beyond that of the last acquired one.
  void squeeze() {
    if (m_releasedIndices.empty()) return;

    std::sort_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
                   std::greater<T>());  // O(n logn)

    // Decrease the pool size as long as the back items are unused
    T lastIndex = m_start + m_size - 1;

    typename Cont::iterator ut, uEnd(m_releasedIndices.end());
    for (ut = m_releasedIndices.begin(); ut != uEnd && *ut == lastIndex; ++ut)
      --lastIndex, --m_size;

    if (ut != m_releasedIndices.begin()) m_releasedIndices.swap(Cont(ut, uEnd));

    std::make_heap(m_releasedIndices.begin(), m_releasedIndices.end(),
                   std::greater<T>());  // O(n)
  }

#if (TCG_RVALUES_SUPPORT > 0)

  indices_pool(const indices_pool &other)
      : m_start(other.m_start)
      , m_size(other.m_size)
      , m_releasedIndices(other.m_releasedIndices) {}

  indices_pool &operator=(const indices_pool &other) {
    m_start = other.m_start, m_size = other.m_size,
    m_releasedIndices = other.m_releasedIndices;
    return *this;
  }

  indices_pool(indices_pool &&other)
      : m_start(other.m_start)
      , m_size(other.m_size)
      , m_releasedIndices(
            std::forward<container_type &&>(other.m_releasedIndices)) {}

  indices_pool &operator=(indices_pool &&other) {
    m_start = other.m_start, m_size = other.m_size;
    m_releasedIndices =
        std::forward<container_type &&>(other.m_releasedIndices);
    return *this;
  }

#endif
};

//----------------------------------------------------------------

template <typename T, typename Cont = std::vector<T>>
class pool {
public:
  typedef T value_type;
  typedef Cont container_type;
  typedef size_t size_type;

private:
  Cont m_objects;                       //!< Objects in the pool
  tcg::indices_pool<size_t> m_indices;  //!< Indices Pool representing m_objects

public:
  pool() {}
  ~pool() {}

  const tcg::indices_pool<size_t> &indices() const { return m_indices; }

  size_type size() const {
    assert(m_indices.size() <= m_objects.size());
    return m_objects.size();
  }
  size_type releasedSize() const {
    return m_objects.size() - m_indices.acquiredSize();
  }
  size_type acquiredSize() const { return m_indices.acquiredSize(); }

  const value_type &operator[](size_type idx) const { return m_objects[idx]; }
  value_type &operator[](size_type idx) { return m_objects[idx]; }

  size_type acquire() {
    size_t result = m_indices.acquire();
    if (result >= m_objects.size()) {
      assert(result == m_objects.size());
      m_objects.push_back(T());
    }

    return result;
  }

  void release(size_type idx) { m_indices.release(idx); }

  void releaseAll() { m_indices.clear(); }
  void clear() {
    releaseAll();
    m_objects.clear();
  }

  //! Removes pool entries whose index is beyond that of the last acquired one.
  void squeeze() {
    m_indices.squeeze();
    m_objects.resize(m_indices.size());
  }
};

}  // namespace tcg

#endif  // TCG_POOL_H
