#ifndef __INC_METIN_II_GAME_POOL_H__
#define __INC_METIN_II_GAME_POOL_H__

// Neither error-checking nor watermarking here.
// Definitely not thread-safe.
// In order to debug the heap memory usage, activate DebugAllocator by defining
// DEBUG_ALLOC.
#ifdef M2_USE_POOL

template<typename T>
struct PoolNode {
	T* block;
	PoolNode* next;
};

template<typename T>
struct PoolAllocation {
	T* chunk;
	size_t num_blocks;
	PoolNode<T>* nodes;
};

template<typename T>
struct PoolDetail {
	typedef T* PointerType;
	typedef T* ArithmeticPointerType;

	static PointerType Alloc(size_t num) {
		return new T[num];
	}
	static void Free(PointerType p) {
		delete[] p;
	}
};

// Explicit specialization of PoolDetail for raw non-initialized memory blocks.
template<>
struct PoolDetail<void> {
	typedef void* PointerType;
	typedef char* ArithmeticPointerType;

	static PointerType Alloc(size_t num) {
		return ::malloc(num);
	}
	static void Free(PointerType p) {
		::free(p);
	}
};

// Generic grow-only pool of arrays.
// Non-void template parameter type T should provide no-arg default constructor.
template<typename T>
class ArrayPool {
public:
	ArrayPool(size_t array_size, size_t initial_capacity = 0)
			: free_(NULL),
			  array_size_(array_size), 
			  capacity_(0),
			  alloc_count_(0),
			  alloc_index_of_last_release_(0) {
		assert(array_size_ != 0);
		if (initial_capacity != 0) {
			Reserve(initial_capacity);
		}
	}
	~ArrayPool() {
		CleanUp();
	}

	// Acquires an available array from the pool.
	T* Acquire() {
		if (free_ == NULL) {
			if (Stretch(capacity_) == false) {
				return NULL;
			}
		}
		assert(free_ != NULL);
		PointerType p = free_->block;
		free_ = free_->next;
		return p;
	}
	// Releases the specified array and returns it to the pool.
	void Release(T* p) {
		if (p == NULL) {
			return;
		}
		size_t index = alloc_index_of_last_release_;
		for (size_t i = 0; i < alloc_count_; ++i, ++index) {
			if (index >= alloc_count_) {
				index = 0;
			}
			AllocationType& alloc = allocated_[index];
			ArithmeticPointerType ptr = static_cast<ArithmeticPointerType>(p);
			ArithmeticPointerType begin = static_cast<ArithmeticPointerType>(
				alloc.chunk);
			ArithmeticPointerType end = begin + (array_size_ * alloc.num_blocks);
			if (begin <= ptr && ptr < end) {
				size_t node_index = (ptr - begin) / array_size_;
				NodeType* node = alloc.nodes + node_index;
				assert(node->block == p);
				if (node->block != p) {
					break;
				}
				node->next = free_;
				free_ = node;
				alloc_index_of_last_release_ = index;
				break;
			}
		}
	}

	// Requests that the capacity of the pool be enough to hold at least n arrays.
	void Reserve(size_t n) {
		if (n <= capacity_) {
			return;
		}
		Stretch(n - capacity_);
	}
	// Frees all the memory blocks allocated by the pool.
	void CleanUp() {
		if (alloc_count_ == 0) {
			return;
		}
		while (alloc_count_ != 0) {
			AllocationType& alloc = allocated_[--alloc_count_];
			DetailType::Free(alloc.chunk);
			delete[] alloc.nodes;
		}
		capacity_ = 0;
		free_ = NULL;
	}

	// Gets the size of an array in the pool.
	size_t array_size() const { return array_size_; }
	// Gets the current total capacity, in number of arrays, of the pool.
	size_t capacity() const { return capacity_; }

private:
	typedef PoolNode<T> NodeType;
	typedef PoolAllocation<T> AllocationType;
	typedef PoolDetail<T> DetailType;
	typedef typename DetailType::PointerType PointerType;
	typedef typename DetailType::ArithmeticPointerType ArithmeticPointerType;

	static const size_t kMaxAllocCount = sizeof(size_t) * CHAR_BIT;

	bool Stretch(size_t increment) {
		if (increment == 0) {
			++increment; // minimum increment 1
		}
		if (alloc_count_ >= kMaxAllocCount) {
			return false;
		}

		ArithmeticPointerType p = static_cast<ArithmeticPointerType>(
			DetailType::Alloc(array_size_ * increment));
		assert(p != NULL);
		if (p == NULL) {
			return false;
		}
		NodeType* node = new NodeType[increment];
		assert(node != NULL);
		if (node == NULL) {
			DetailType::Free(p);
			return false;
		}

		AllocationType& alloc = allocated_[alloc_count_++];
		alloc.chunk = p;
		alloc.num_blocks = increment;
		alloc.nodes = node;

		NodeType* tail = free_;
		NodeType** link = &free_;
		for (size_t i = 0; i < increment ; ++i, ++node, p += array_size_) {
			node->block = p;
			*link = node;
			link = &(node->next);
		}
		*link = tail;

		capacity_ += increment;
		return true;
	}

	NodeType* free_;

	size_t array_size_;
	size_t capacity_;

	AllocationType allocated_[kMaxAllocCount];
	size_t alloc_count_;
	size_t alloc_index_of_last_release_;

	// No copy
	ArrayPool(const ArrayPool&);
	void operator=(const ArrayPool&);
};

// Special alias for the pool of raw(non-typed) non-initialized memory blocks.
typedef ArrayPool<void> Pool;

// Variable-length memory pool backed by multiple fixed-length pools.
class MemoryPool {
public:
	MemoryPool() {}
	~MemoryPool() {
		PoolMapType::iterator it = pools_.begin(), end = pools_.end();
		for ( ; it != end; ++it) {
			delete (it->second);
		}
	}
	// Acquires a memory block of specified size from a pool.
	void* Acquire(size_t size) {
		Pool* pool;
		PoolMapType::iterator it = pools_.find(size);
		if (it != pools_.end()) {
			pool = it->second;
		} else {
			pool = new Pool(size);
			pools_.insert(PoolMapType::value_type(size, pool));
		}
		return pool->Acquire();
	}
	// Releases the specified memory block and returns it to a pool.
	void Release(void* p, size_t size) {
		PoolMapType::iterator it = pools_.find(size);
		if (it == pools_.end()) {
			return;
		}
		Pool* pool = it->second;
		pool->Release(p);
	}
private:
	typedef std::unordered_map<size_t, Pool*> PoolMapType;
	PoolMapType pools_;
};

// Grow-only simple object pool, relying on ctor/dtor.
template<class T> // T should provide no-arg default constructor
class ObjectPool {
public:
	ObjectPool(size_t initial_capacity = 0)
		: pool_(sizeof(T), initial_capacity) {}
	~ObjectPool() {}

	// Constructs a new object from the pool.
	T* Construct() {
		void* p = pool_.Acquire();
		if (p == NULL) {
			return NULL;
		}
		return new (p) T();
	}
	// Destroys the specified object and returns it to the pool.
	void Destroy(T* p) {
		if (p == NULL) {
			return;
		}
		p->~T();
		pool_.Release(p);
	}

	// Requests that the pool capacity be enough to hold at least n objects.
	void Reserve(size_t n) {
		pool_.Reserve(n);
	}

private:
	Pool pool_;

	// No copy
	ObjectPool(const ObjectPool&);
	void operator=(const ObjectPool&);
};

#endif

#endif // __INC_METIN_II_GAME_POOL_H__
