// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
/*============================================================
**
** Class:  Random
**
**
** Purpose: A random number generator.
**
** 
===========================================================*/
namespace System
{
	using System;
	using System.Globalization;
	using System.Diagnostics.Contracts;
	using System.Diagnostics;
	using System.Threading;

	// This class is thread-safe for random results, but not deterministically thread-safe
	public class Random
	{
      //
      // Private Constants 
      //
		private const int32 MBIG = Int32.MaxValue;
		private const int32 MSEED = 161803398;
		private const int32 MZ = 0;
    
      
      //
      // Member Variables
      //
		private int32 inext;
		private int32 inextp;
		private int32[] SeedArray = new int32[56] ~ delete _;

		private static int32 sSeed = (int32)Platform.BfpSystem_GetTimeStamp();

      //
      // Public Constants
      //
    
      //
      // Native Declarations
      //
    
      //
      // Constructors
      //

		public this() : this(Interlocked.Increment(ref sSeed))
		{
		}

		public this(int seed)
		{
			int32 ii;
			int32 mj, mk;
			int32 useSeed = (int32)seed;
    
        //Initialize our Seed array.
        //This algorithm comes from Numerical Recipes in C (2nd Ed.)
			int32 subtraction = (useSeed == Int32.MinValue) ? Int32.MaxValue : Math.Abs(useSeed);
			mj = MSEED - subtraction;
			SeedArray[55] = mj;
			mk = 1;
			for (int32 i = 1; i < 55; i++)
			{  //Apparently the range [1..55] is special (Knuth) and so we're wasting the 0'th position.
				ii = (21 * i) % 55;
				SeedArray[ii] = mk;
				mk = mj - mk;
				if (mk < 0) mk += MBIG;
				mj = SeedArray[ii];
			}
			for (int32 k = 1; k < 5; k++)
			{
				for (int32 i = 1; i < 56; i++)
				{
					SeedArray[i] &-= SeedArray[1 + (i + 30) % 55];
					if (SeedArray[i] < 0) SeedArray[i] += MBIG;
				}
			}
			inext = 0;
			inextp = 21;
			useSeed = 1;
		}
    
      //
      // Package Private Methods
      //
    
      /*====================================Sample====================================
      **Action: Return a new random number [0..1) and reSeed the Seed array.
      **Returns: A double [0..1)
      **Arguments: None
      **Exceptions: None
      ==============================================================================*/
		protected virtual double Sample()
		{
          //Including this division at the end gives us significantly improved
          //random number distribution.
			return ((InternalSample() & Int32.MaxValue) * (1.0 / Int32.MaxValue));
		}

		private int32 InternalSample()
		{
			int32 retVal;
			int32 locINext = inext;
			int32 locINextp = inextp;

			if (++locINext >= 56) locINext = 1;
			if (++locINextp >= 56) locINextp = 1;

			retVal = SeedArray[locINext] &- SeedArray[locINextp];
			SeedArray[locINext] = retVal;

			inext = locINext;
			inextp = locINextp;

			return retVal;
		}

      //
      // Public Instance Methods
      // 
    
    
      /*=====================================Next=====================================
      **Returns: An int [0..Int32.MaxValue)
      **Arguments: None
      **Exceptions: None.
      ==============================================================================*/
		public virtual int32 NextI32()
		{
			return InternalSample() & Int32.MaxValue;
		}

		public virtual int32 NextS32()
		{
			return InternalSample();
		}

		public virtual uint32 NextU32()
		{
			return (.)InternalSample();
		}

		public virtual int64 NextI64()
		{
			return (int64)(((uint64)InternalSample() << 32) | (uint64)InternalSample()) & 0x7FFFFFFF'FFFFFFFFL;
		}

		public virtual int64 NextS64()
		{
			return (int64)(((uint64)InternalSample() << 32) | (uint64)InternalSample());
		}

		public virtual uint64 NextU64()
		{
			return (((uint64)InternalSample() << 32) | (uint64)InternalSample());
		}

		private double GetSampleForLargeRange()
		{
          // The distribution of double value returned by Sample 
          // is not distributed well enough for a large range.
          // If we use Sample for a range [Int32.MinValue..Int32.MaxValue)
          // We will end up getting even numbers only.
			int32 result = InternalSample();
          // Note we can't use addition here. The distribution will be bad if we do that.
			bool negative = (InternalSample() % 2 == 0) ? true : false;  // decide the sign based on second sample
			if (negative)
			{
				result = -result;
			}
			double d = result;
			d += (Int32.MaxValue - 1); // get a number in range [0 .. 2 * Int32MaxValue - 1)
			d /= 2 * (uint32)Int32.MaxValue - 1;
			return d;
		}


      /*=====================================Next=====================================
      **Returns: An int [minvalue..maxvalue)
      **Arguments: minValue -- the least legal value for the Random number.
      **           maxValue -- One greater than the greatest legal return value.
      **Exceptions: None.
      ==============================================================================*/
		public virtual int32 Next(int32 minValue, int32 maxValue)
		{
			if (minValue > maxValue)
			{
				Runtime.FatalError();
			}
			Contract.EndContractBlock();

			int64 range = (int64)maxValue - minValue;
			if (range <= (int64)Int32.MaxValue)
			{
				return ((int32)(Sample() * range) + minValue);
			}
			else
			{
				return (int32)((int64)(GetSampleForLargeRange() * range) + minValue);
			}
		}

		public virtual int64 Next(int64 minValue, int64 maxValue)
		{
			if (minValue > maxValue)
			{
				Runtime.FatalError();
			}
			Contract.EndContractBlock();

			int64 range = (int64)maxValue - minValue;
			if (range <= (int64)Int32.MaxValue)
			{
				return ((int32)(Sample() * range) + minValue);
			}
			else
			{
				return (int32)((int64)(GetSampleForLargeRange() * range) + minValue);
			}
		}
    
    
      /*=====================================Next=====================================
      **Returns: An int [0..maxValue)
      **Arguments: maxValue -- One more than the greatest legal return value.
      **Exceptions: None.
      ==============================================================================*/
		public virtual int32 Next(int32 maxValue)
		{
			if (maxValue < 0)
			{
				Runtime.FatalError();
			}
			Contract.EndContractBlock();
			return (.)(Sample() * maxValue);
		}

		public virtual int64 Next(int64 maxValue)
		{
			if (maxValue < 0)
			{
				Runtime.FatalError();
			}
			Contract.EndContractBlock();
			return (.)(Sample() * maxValue);
		}
    
    
      /*=====================================Next=====================================
      **Returns: A double [0..1)
      **Arguments: None
      **Exceptions: None
      ==============================================================================*/
		public virtual double NextDouble()
		{
			return Sample();
		}

		public virtual double NextDoubleSigned()
		{
			return (InternalSample() * (1.0 / MBIG));
		}
    
      /*==================================NextBytes===================================
      **Action:  Fills the byte array with random bytes [0..0x7f].  The entire array is filled.
      **Returns:Void
      **Arugments:  buffer -- the array to be filled.
      **Exceptions: None
      ==============================================================================*/
		public virtual void NextBytes(uint8[] buffer)
		{
			if (buffer == null) Runtime.FatalError();
			for (int32 i = 0; i < buffer.Count; i++)
			{
				buffer[i] = (uint8)(InternalSample() % ((int32)UInt8.MaxValue + 1));
			}
		}
	}

	static
	{
		public static Random gRand = new Random() ~ delete _;
	}
}
