#ifndef GST_ResourcePool_hpp__
#define GST_ResourcePool_hpp__

#include <cassert>
#include <deque>
#include <functional>
#include <mutex>
#include <utility>

#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>

namespace GST
{
namespace Utils
{
template<class Resource>
class ResourcePool
{
public:
	// The shared_ptr that is managed by this pool.
	using ResourcePtr = boost::shared_ptr<Resource>;

	// The factory function.
	//
	// It takes no arguments and returns a ResourcePtr.
	//
	// This function should not return a nullptr.
	//
	// Exceptions thrown by the factory functions may be thrown by getResource.
	using ResourceFactoryFunc = std::function<ResourcePtr()>;

	// Creates the Pool.
	//
	// New resources are crated with the provided factory function.
	ResourcePool(ResourceFactoryFunc f) : m_impl(boost::make_shared<Impl>(f))
	{
	}

	// Returns a resource.
	//
	// If there are any resources currently in the pool, we return one of those
	// in a FIFO manner. Otherwise we create one with the provided factory and
	// return that one.
	//
	// Might throw, if factory function throws.
	ResourcePtr getResource()
	{
		return m_impl->getResource();
	}

private:
	struct Impl;
	struct Deleter;
	// This shared_ptr is required to make enable_shared_from_this work.
	boost::shared_ptr<Impl> m_impl;
};

template<class Resource>
struct ResourcePool<Resource>::Impl : boost::enable_shared_from_this<Impl>
{
	using Mutex = std::mutex;

	using LockType = std::lock_guard<Mutex>;

	Impl(ResourceFactoryFunc f) : m_available(), m_factory(f), m_mutex()
	{
		assert(m_factory);
	}

	ResourcePtr getResource()
	{
		// If there currently is a resource available, we remove one resource
		// from the front of m_available and use it as return value.
		//
		// If no resource is currently available, we create a new one with the
		// factory function.
		//
		// Finally we create a ResourcePtr with our custom deleter, which
		// handles the returning of the resource to the pool.
		ResourcePtr resource;
		{
			LockType lock(m_mutex);
			if(!m_available.empty())
			{
				resource = m_available.front();
				m_available.pop_front();
				assert(resource);
			}
		}
		if(!resource)
		{
			resource = m_factory();
			assert(resource);
		}
		auto resourceRef = resource.get();
		return ResourcePtr{
			resourceRef, Deleter{std::move(resource), this->weak_from_this()}};
	}

	void returnResource(ResourcePtr resource)
	{
		// We simply add the resource to the back of m_available.
		assert(resource);
		LockType lock(m_mutex);
		m_available.push_back(std::move(resource));
	}

	// Using deque to get FIFO behavior.
	std::deque<ResourcePtr> m_available;
	// The factory function to construct resources.
	ResourceFactoryFunc m_factory;
	// Mutex to provide thread safety for accessing m_available.
	std::mutex m_mutex;
};

template<class Resource>
struct ResourcePool<Resource>::Deleter
{
	using PoolWeakPtr = boost::weak_ptr<Impl>;
	Deleter(ResourcePtr resouce, PoolWeakPtr pool)
		: m_resource(std::move(resouce))
		, m_pool(std::move(pool))
	{
		assert(m_resource);
	}

	void operator()(Resource *)
	{
		// If the pool is still alive, we get a non-null shared_ptr and can
		// return the resource to the pool.
		//
		// Otherwise we do nothing. The resource shared_ptr
		// handles the correct freeing of the resource.
		auto pool = m_pool.lock();
		if(pool)
		{
			pool->returnResource(std::move(m_resource));
		}
	}

	// A shared_ptr to the resource. This guarantees that resource is always
	// alive, event if the pool is destroyed.
	ResourcePtr m_resource;
	// A weak_ptr to the pool. This makes sure we only try to return the
	// resource to the pool, when the pool is still alive.
	PoolWeakPtr m_pool;
};
} // namespace Utils
} // namespace GST

#endif // GST_ResourcePool_hpp__
