/* This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package com.l2jserver.util;

import java.security.SecureRandom;
import java.util.Random;

/**
 * @author Forsaiken
 */
public final class Rnd
{
	/**
	 * This class extends {@link java.util.Random} but do not compare and store atomically.<br>
	 * Instead it`s using a simple volatile flag to ensure reading and storing the whole 64bit seed chunk.<br>
	 * This implementation is much faster on parallel access, but may generate the same seed for 2 threads.
	 * @author Forsaiken
	 * @see java.util.Random
	 */
	public static final class NonAtomicRandom extends Random
	{
		private static final long serialVersionUID = 1L;
		private volatile long _seed;
		
		public NonAtomicRandom()
		{
			this(++SEED_UNIQUIFIER + System.nanoTime());
		}
		
		public NonAtomicRandom(final long seed)
		{
			setSeed(seed);
		}
		
		@Override
		public final int next(final int bits)
		{
			return (int) ((_seed = ((_seed * MULTIPLIER) + ADDEND) & MASK) >>> (48 - bits));
		}
		
		@Override
		public final void setSeed(final long seed)
		{
			_seed = (seed ^ MULTIPLIER) & MASK;
		}
	}
	
	/**
	 * @author Forsaiken
	 */
	protected static final class RandomContainer
	{
		private final Random _random;
		
		protected RandomContainer(final Random random)
		{
			_random = random;
		}
		
		public final Random directRandom()
		{
			return _random;
		}
		
		/**
		 * Get a random double number from 0 to 1
		 * @return A random double number from 0 to 1
		 * @see com.l2jserver.util.Rnd#nextDouble()
		 */
		public final double get()
		{
			return _random.nextDouble();
		}
		
		/**
		 * Gets a random integer number from 0(inclusive) to n(exclusive)
		 * @param n The superior limit (exclusive)
		 * @return A random integer number from 0 to n-1
		 */
		public final int get(final int n)
		{
			return (int) (_random.nextDouble() * n);
		}
		
		/**
		 * Gets a random integer number from min(inclusive) to max(inclusive)
		 * @param min The minimum value
		 * @param max The maximum value
		 * @return A random integer number from min to max
		 */
		public final int get(final int min, final int max)
		{
			return min + (int) (_random.nextDouble() * ((max - min) + 1));
		}
		
		/**
		 * Gets a random long number from min(inclusive) to max(inclusive)
		 * @param min The minimum value
		 * @param max The maximum value
		 * @return A random long number from min to max
		 */
		public final long get(final long min, final long max)
		{
			return min + (long) (_random.nextDouble() * ((max - min) + 1));
		}
		
		/**
		 * Get a random boolean state (true or false)
		 * @return A random boolean state (true or false)
		 * @see java.util.Random#nextBoolean()
		 */
		public final boolean nextBoolean()
		{
			return _random.nextBoolean();
		}
		
		/**
		 * Fill the given array with random byte numbers from Byte.MIN_VALUE(inclusive) to Byte.MAX_VALUE(inclusive)
		 * @param array The array to be filled with random byte numbers
		 * @see java.util.Random#nextBytes(byte[] bytes)
		 */
		public final void nextBytes(final byte[] array)
		{
			_random.nextBytes(array);
		}
		
		/**
		 * Get a random double number from 0 to 1
		 * @return A random double number from 0 to 1
		 * @see java.util.Random#nextDouble()
		 */
		public final double nextDouble()
		{
			return _random.nextDouble();
		}
		
		/**
		 * Get a random float number from 0 to 1
		 * @return A random integer number from 0 to 1
		 * @see java.util.Random#nextFloat()
		 */
		public final float nextFloat()
		{
			return _random.nextFloat();
		}
		
		/**
		 * Get a random gaussian double number from 0 to 1
		 * @return A random gaussian double number from 0 to 1
		 * @see java.util.Random#nextGaussian()
		 */
		public final double nextGaussian()
		{
			return _random.nextGaussian();
		}
		
		/**
		 * Get a random integer number from Integer.MIN_VALUE(inclusive) to Integer.MAX_VALUE(inclusive)
		 * @return A random integer number from Integer.MIN_VALUE to Integer.MAX_VALUE
		 * @see java.util.Random#nextInt()
		 */
		public final int nextInt()
		{
			return _random.nextInt();
		}
		
		/**
		 * Get a random long number from Long.MIN_VALUE(inclusive) to Long.MAX_VALUE(inclusive)
		 * @return A random integer number from Long.MIN_VALUE to Long.MAX_VALUE
		 * @see java.util.Random#nextLong()
		 */
		public final long nextLong()
		{
			return _random.nextLong();
		}
	}
	
	/**
	 * @author Forsaiken
	 */
	public static enum RandomType
	{
		/**
		 * For best random quality.
		 * @see java.security.SecureRandom
		 */
		SECURE,
		
		/**
		 * For average random quality.
		 * @see java.util.Random
		 */
		UNSECURE_ATOMIC,
		
		/**
		 * Like {@link com.l2jserver.util.Rnd.RandomType#UNSECURE_ATOMIC}.<br>
		 * Each thread has it`s own random instance.<br>
		 * Provides best parallel access speed.
		 * @see com.l2jserver.util.Rnd.ThreadLocalRandom
		 */
		UNSECURE_THREAD_LOCAL,
		
		/**
		 * Like {@link com.l2jserver.util.Rnd.RandomType#UNSECURE_ATOMIC}.<br>
		 * Provides much faster parallel access speed.
		 * @see com.l2jserver.util.Rnd.NonAtomicRandom
		 */
		UNSECURE_VOLATILE
	}
	
	/**
	 * This class extends {@link java.util.Random} but do not compare and store atomically.<br>
	 * Instead it`s using thread local ensure reading and storing the whole 64bit seed chunk.<br>
	 * This implementation is the fastest, never generates the same seed for 2 threads.<br>
	 * Each thread has it`s own random instance.
	 * @author Forsaiken
	 * @see java.util.Random
	 */
	public static final class ThreadLocalRandom extends Random
	{
		private static final class Seed
		{
			long _seed;
			
			Seed(final long seed)
			{
				setSeed(seed);
			}
			
			final int next(final int bits)
			{
				return (int) ((_seed = ((_seed * MULTIPLIER) + ADDEND) & MASK) >>> (48 - bits));
			}
			
			final void setSeed(final long seed)
			{
				_seed = (seed ^ MULTIPLIER) & MASK;
			}
		}
		
		private static final long serialVersionUID = 1L;
		private final ThreadLocal<Seed> _seedLocal;
		
		public ThreadLocalRandom()
		{
			_seedLocal = new ThreadLocal<Seed>()
			{
				@Override
				public final Seed initialValue()
				{
					return new Seed(++SEED_UNIQUIFIER + System.nanoTime());
				}
			};
		}
		
		public ThreadLocalRandom(final long seed)
		{
			_seedLocal = new ThreadLocal<Seed>()
			{
				@Override
				public final Seed initialValue()
				{
					return new Seed(seed);
				}
			};
		}
		
		@Override
		public final int next(final int bits)
		{
			return _seedLocal.get().next(bits);
		}
		
		@Override
		public final void setSeed(final long seed)
		{
			if (_seedLocal != null)
			{
				_seedLocal.get().setSeed(seed);
			}
		}
	}
	
	private static final long ADDEND = 0xBL;
	
	private static final long MASK = (1L << 48) - 1;
	
	private static final long MULTIPLIER = 0x5DEECE66DL;
	
	private static final RandomContainer rnd = newInstance(RandomType.UNSECURE_THREAD_LOCAL);
	
	protected static volatile long SEED_UNIQUIFIER = 8682522807148012L;
	
	public static final Random directRandom()
	{
		return rnd.directRandom();
	}
	
	/**
	 * Get a random double number from 0 to 1
	 * @return A random double number from 0 to 1
	 * @see com.l2jserver.util.Rnd#nextDouble()
	 */
	public static final double get()
	{
		return rnd.nextDouble();
	}
	
	/**
	 * Gets a random integer number from 0(inclusive) to n(exclusive)
	 * @param n The superior limit (exclusive)
	 * @return A random integer number from 0 to n-1
	 */
	public static final int get(final int n)
	{
		return rnd.get(n);
	}
	
	/**
	 * Gets a random integer number from min(inclusive) to max(inclusive)
	 * @param min The minimum value
	 * @param max The maximum value
	 * @return A random integer number from min to max
	 */
	public static final int get(final int min, final int max)
	{
		return rnd.get(min, max);
	}
	
	/**
	 * Gets a random long number from min(inclusive) to max(inclusive)
	 * @param min The minimum value
	 * @param max The maximum value
	 * @return A random long number from min to max
	 */
	public static final long get(final long min, final long max)
	{
		return rnd.get(min, max);
	}
	
	public static final RandomContainer newInstance(final RandomType type)
	{
		switch (type)
		{
			case UNSECURE_ATOMIC:
				return new RandomContainer(new Random());
				
			case UNSECURE_VOLATILE:
				return new RandomContainer(new NonAtomicRandom());
				
			case UNSECURE_THREAD_LOCAL:
				return new RandomContainer(new ThreadLocalRandom());
				
			case SECURE:
				return new RandomContainer(new SecureRandom());
		}
		
		throw new IllegalArgumentException();
	}
	
	/**
	 * Get a random boolean state (true or false)
	 * @return A random boolean state (true or false)
	 * @see java.util.Random#nextBoolean()
	 */
	public static final boolean nextBoolean()
	{
		return rnd.nextBoolean();
	}
	
	/**
	 * Fill the given array with random byte numbers from Byte.MIN_VALUE(inclusive) to Byte.MAX_VALUE(inclusive)
	 * @param array The array to be filled with random byte numbers
	 * @see java.util.Random#nextBytes(byte[] bytes)
	 */
	public static final void nextBytes(final byte[] array)
	{
		rnd.nextBytes(array);
	}
	
	/**
	 * Get a random double number from 0 to 1
	 * @return A random double number from 0 to 1
	 * @see java.util.Random#nextDouble()
	 */
	public static final double nextDouble()
	{
		return rnd.nextDouble();
	}
	
	/**
	 * Get a random float number from 0 to 1
	 * @return A random integer number from 0 to 1
	 * @see java.util.Random#nextFloat()
	 */
	public static final float nextFloat()
	{
		return rnd.nextFloat();
	}
	
	/**
	 * Get a random gaussian double number from 0 to 1
	 * @return A random gaussian double number from 0 to 1
	 * @see java.util.Random#nextGaussian()
	 */
	public static final double nextGaussian()
	{
		return rnd.nextGaussian();
	}
	
	/**
	 * Get a random integer number from Integer.MIN_VALUE(inclusive) to Integer.MAX_VALUE(inclusive)
	 * @return A random integer number from Integer.MIN_VALUE to Integer.MAX_VALUE
	 * @see java.util.Random#nextInt()
	 */
	public static final int nextInt()
	{
		return rnd.nextInt();
	}
	
	/**
	 * @param n
	 * @return
	 * @see com.l2jserver.util.Rnd#get(int n)
	 */
	public static final int nextInt(final int n)
	{
		return get(n);
	}
	
	/**
	 * Get a random long number from Long.MIN_VALUE(inclusive) to Long.MAX_VALUE(inclusive)
	 * @return A random integer number from Long.MIN_VALUE to Long.MAX_VALUE
	 * @see java.util.Random#nextLong()
	 */
	public static final long nextLong()
	{
		return rnd.nextLong();
	}
}
