﻿﻿using System.Collections.Generic;

namespace RelEnt
{
	/// <summary>
	/// Allocates euids in increasing order from a pool, allowing them to be returned to the pool
	/// and reused at any moment.
	/// 
	/// Always reuses euids before increasing the range of existing euids.
	/// </summary>
	public class EuidAllocator
	{
		public enum EuidDomain
		{
			Positive, // Typically for net-synced entities
			Negative  // Typically for non-synced entities
		}

		public EuidDomain AllocDomain { get; }

		/// <summary>
		/// The euid that will be allocated next, if none are available to be reused.
		/// </summary>
		private euid nextLargestEuid;

		/// <summary>
		/// A queue of unused euids to recycle.
		/// </summary>
		private Queue<euid> unusedEuids;

		/// <summary>
		/// Create an allocator with no euids allocated.
		/// </summary>
		public EuidAllocator(EuidDomain allocDomain)
		{
			AllocDomain = allocDomain;

			// Start at 0, so that the first allocated is 1 or -1, not NULL.
			// TODO: Initialize from existing euids in entity system.
			nextLargestEuid = 0;
			unusedEuids = new Queue<euid>();
		}

		/// <summary>
		/// Allocates a single euid.
		/// </summary>
		/// <returns>The new, free euid.</returns>
		public euid Allocate()
		{
			lock (unusedEuids)
			{
				if (unusedEuids.Count == 0)
				{
					nextLargestEuid += AllocDomain == EuidDomain.Positive ? 1L : -1L;
					return nextLargestEuid;
				}
				return unusedEuids.Dequeue();
			}
		}

		/// <summary>
		/// Returns an euid to this allocator so it can be recycled.
		/// If e is not in the range of this allocator, it is ignored.
		/// </summary>
		public void Return(euid e)
		{
			if ((e < 0 && AllocDomain == EuidDomain.Negative) ||
				(e > 0 && AllocDomain == EuidDomain.Positive))
			{
				lock (unusedEuids)
					unusedEuids.Enqueue(e);
			}
		}
	}
}
