using System;
using System.Collections.Generic;

namespace Me.StefS.Utility{
	public class RandomInt{
		private int lastProducedInt;

		public RandomInt(int seed){
			this.lastProducedInt = seed;
		}

		public int Next()
		{
			lastProducedInt = (int)((lastProducedInt * 0x5DEECE66DL + 0xBL) & ((1L << 48) - 1) >> 32);
			return lastProducedInt;
		}

		public int Next(int maxVal)
		{
			lastProducedInt = Next ();
			return lastProducedInt % maxVal;
		}

		public int Next(int minVal, int maxVal)
		{
			lastProducedInt = Next ();
			return (lastProducedInt % (maxVal - minVal)) + minVal;
		}

		public T Next<T>(IList<T> array)
		{
			return array[this.Next (array.Count)];
		}

		public T Next<T>(IList<T> array, IList<int> expectIndexes){
			return array [Index (array, expectIndexes)];
		}

		public int Index<T>(IList<T> array, IList<int> expectIndexes){
			int chosenOrder = this.Next (array.Count - expectIndexes.Count) + 1;
			if(expectIndexes.Count == 0){
				return chosenOrder -1;
			}

			int excpetionIndex = 0;
			for(int index =0; index < array.Count; index++){

				if(excpetionIndex < expectIndexes.Count && index == expectIndexes[excpetionIndex]){
					excpetionIndex++;
				}else{
					chosenOrder--;
				}

				if(chosenOrder == 0){
					return index;
				}
			}

			throw new InvalidOperationException ("random broke, put some argument checks but it will make it slower");
		}

		public T CustomDistribution<T>(IList<T> array, int[] weights, int total)
		{
			int target = this.Next (total);
			int index = 0;
			while(weights[index] < target){
				index++;
			}

			return array[index];
		}

		public bool CoinTails()
		{
			return this.Next (2) == 0;
		}

		public int RollD6()
		{
			return this.Next(6);
		}

		public int RollD4 ()
		{
			return this.Next(4);
		}

		public T ChooseRandomEnumValue<T>()
		{
			if(!typeof(T).IsEnum)
			{
				throw new System.ArgumentOutOfRangeException("The type argument must be enum, sorry for this not being compile exception");
			}

			System.Array enumValues = System.Enum.GetValues(typeof(T));
			T element = (T)enumValues.GetValue(this.Next(enumValues.Length));
			return element;
		}

		public T ChooseRandomEnumValueExcept<T>(Func<T, bool> exceptFunc)
		{
			if(!typeof(T).IsEnum)
			{
				throw new System.ArgumentOutOfRangeException("The type argument must be enum, sorry for this not being compile exception");
			}

			System.Array enumValues = System.Enum.GetValues(typeof(T));
			T element = default(T);
			bool elementSet = false;
			while(!elementSet){
				element = (T)enumValues.GetValue(this.Next(enumValues.Length));
				elementSet = !exceptFunc(element);
			}

			return element;
		}
	}
}
