context: #region Header
  // **************************************\
  //     _  _   _   __  ___  _   _   ___   |
  //    |# |#  |#  |## |### |#  |#  |###   |
  //    |# |#  |# |#    |#  |#  |# |#  |#  |
  //    |# |#  |#  |#   |#  |#  |# |#  |#  |
  //   _|# |#__|#  _|#  |#  |#__|# |#__|#  |
  //  |##   |##   |##   |#   |##    |###   |
  //        [http://www.playuo.org]        |
  // **************************************/
  //  [2014] FileQueue.cs
  // ************************************/
  #endregion
  
  #region References
  using System;
  using System.Collections.Generic;
  using System.Threading;
  
  using Server.Network;
  #endregion
  
  namespace Server
  {
  	public delegate void FileCommitCallback(FileQueue.Chunk chunk);
  
  	public sealed class FileQueue : IDisposable
  	{
  		private static readonly int bufferSize;
  		private static readonly BufferPool bufferPool;
  
  		private readonly object syncRoot;
  		private readonly Chunk[] active;
  		private readonly Queue<Page> pending;
  		private Page buffered;
  		private readonly FileCommitCallback callback;
  		private int activeCount;
  		private ManualResetEvent idle;
  		private long position;
  
  		public FileQueue(int concurrentWrites, FileCommitCallback callback)
  		{
  			if (concurrentWrites < 1)
  			{
  				throw new ArgumentOutOfRangeException("concurrent" + "Writes");
  			}
  
  			if (bufferSize < 1)
  			{
  				throw new ArgumentOutOfRangeException("buffer" + "Size");
  			}
  
  			if (callback == null)
  			{
  				throw new ArgumentNullException("callback");
  			}
  
  			syncRoot = new object();
  
  			active = new Chunk[concurrentWrites];
  			pending = new Queue<Page>();
  
  			this.callback = callback;
  
  			idle = new ManualResetEvent(true);
  		}
  
  		static FileQueue()
  		{
  			bufferSize = FileOperations.BufferSize;
  			bufferPool = new BufferPool("File Buffers", 64, bufferSize);
  		}
  
  		public long Position { get { return position; } }
  
  		public void Dispose()
  		{
  			if (idle != null)
  			{
  				idle.Close();
  				idle = null;
  			}
  		}
  
  		public void Flush()
  		{
  			if (buffered.buffer != null)
  			{
  				Append(buffered);
  
  				buffered.buffer = null;
  				buffered.length = 0;
  			}
  			/*
  			lock (syncRoot)
  			{
  				if (pending.Count > 0)
  				{
  					idle.Reset();
  				}
  
  				for (int slot = 0; slot < active.Length && pending.Count > 0; ++slot)
  				{
  					if (active[slot] == null)
  					{
  						Page page = pending.Dequeue();
  
  						active[slot] = new Chunk(this, slot, page.buffer, 0, page.length);
  
  						++activeCount;
  
  						callback(active[slot]);
  					}
  				}
  			}
  			*/
  			idle.WaitOne();
  		}
  
  		public void Enqueue(byte[] buffer, int offset, int size)
  		{
  			if (buffer == null)
  			{
  				throw new ArgumentNullException("buffer");
  			}
  
  			if (offset < 0)
  			{
  				throw new ArgumentOutOfRangeException("offset");
  			}
  
  			if (size < 0)
  			{
  				throw new ArgumentOutOfRangeException("size");
  			}
  
  			if ((buffer.Length - offset) < size)
  			{
  				throw new ArgumentException();
  			}
  
  			position += size;
  
  			while (size > 0)
  			{
  				if (buffered.buffer == null)
  				{
  					// nothing yet buffered
  					buffered.buffer = bufferPool.AcquireBuffer();
  				}
  
  				byte[] page = buffered.buffer; // buffer page
  				int pageSpace = page.Length - buffered.length; // available bytes in page
  				int byteCount = (size > pageSpace ? pageSpace : size); // how many bytes we can copy over
  
  				Buffer.BlockCopy(buffer, offset, page, buffered.length, byteCount);
  
  				buffered.length += byteCount;
  				offset += byteCount;
  				size -= byteCount;
  
  				if (buffered.length != page.Length)
  				{
  					continue;
  				}
  
  				// page full
  				Append(buffered);
  
  				buffered.buffer = null;
  				buffered.length = 0;
  			}
  		}
  
  		private void Append(Page page)
  		{
  			lock (syncRoot)
  			{
  				if (activeCount == 0)
  				{
  					idle.Reset();
  				}
  
  				++activeCount;
  
  				for (int slot = 0; slot < active.Length; ++slot)
  				{
  					if (active[slot] != null)
  					{
  						continue;
  					}
  
  					active[slot] = new Chunk(this, slot, page.buffer, 0, page.length);
  					callback(active[slot]);
  					return;
  				}
  
  				pending.Enqueue(page);
  			}
  		}
  
  		private void Commit(Chunk chunk, int slot)
  		{
  			if (slot < 0 || slot >= active.Length)
  			{
  				throw new ArgumentOutOfRangeException("slot");
  			}
  
  			lock (syncRoot)
  			{
  				if (active[slot] != chunk)
  				{
  					throw new ArgumentException();
  				}
  
  				bufferPool.ReleaseBuffer(chunk.Buffer);
  
  				if (pending.Count > 0)
  				{
  
gt: 					Page page = pending.Dequeue();