using System;

namespace ChainUtils.BouncyCastle.Crypto.Prng
{
	/// <remarks>
	/// Takes bytes generated by an underling RandomGenerator and reverses the order in
	/// each small window (of configurable size).
	/// <p>
	/// Access to internals is synchronized so a single one of these can be shared.
	/// </p>
	/// </remarks>
	public class ReversedWindowGenerator
		: IRandomGenerator
	{
		private readonly IRandomGenerator generator;

		private byte[] window;
		private int windowCount;

		public ReversedWindowGenerator(
			IRandomGenerator	generator,
			int					windowSize)
		{
			if (generator == null)
				throw new ArgumentNullException("generator");
			if (windowSize < 2)
				throw new ArgumentException("Window size must be at least 2", "windowSize");

			this.generator = generator;
			window = new byte[windowSize];
		}

		/// <summary>Add more seed material to the generator.</summary>
		/// <param name="seed">A byte array to be mixed into the generator's state.</param>
		public virtual void AddSeedMaterial(
			byte[] seed)
		{
			lock (this)
			{
				windowCount = 0;
				generator.AddSeedMaterial(seed);
			}
		}

		/// <summary>Add more seed material to the generator.</summary>
		/// <param name="seed">A long value to be mixed into the generator's state.</param>
		public virtual void AddSeedMaterial(
			long seed)
		{
			lock (this)
			{
				windowCount = 0;
				generator.AddSeedMaterial(seed);
			}
		}

		/// <summary>Fill byte array with random values.</summary>
		/// <param name="bytes">Array to be filled.</param>
		public virtual void NextBytes(
			byte[] bytes)
		{
			doNextBytes(bytes, 0, bytes.Length);
		}

		/// <summary>Fill byte array with random values.</summary>
		/// <param name="bytes">Array to receive bytes.</param>
		/// <param name="start">Index to start filling at.</param>
		/// <param name="len">Length of segment to fill.</param>
		public virtual void NextBytes(
			byte[]	bytes,
			int		start,
			int		len)
		{
			doNextBytes(bytes, start, len);
		}

		private void doNextBytes(
			byte[]	bytes,
			int		start,
			int		len)
		{
			lock (this)
			{
				var done = 0;
				while (done < len)
				{
					if (windowCount < 1)
					{
						generator.NextBytes(window, 0, window.Length);
						windowCount = window.Length;
					}

					bytes[start + done++] = window[--windowCount];
				}
			}
		}
	}
}
