using System;

namespace TrueSync {

    /**
     *  @brief Generates random numbers based on a deterministic approach.
     **/
    public class TSRandom {
        // From http://www.codeproject.com/Articles/164087/Random-Number-Generation
        // Class TSRandom generates random numbers
        // from a uniform distribution using the Mersenne
        // Twister algorithm.
        private const int N = 624;
        private const int M = 397;
        private const uint MATRIX_A = 0x9908b0dfU;
        private const uint UPPER_MASK = 0x80000000U;
        private const uint LOWER_MASK = 0x7fffffffU;
        private const int MAX_RAND_INT = 0x7fffffff;
        private uint[] mag01 = { 0x0U, MATRIX_A };
        private uint[] mt = new uint[N];
        private int mti = N + 1;

        /**
         *  @brief Static instance of {@link TSRandom} with seed 1.
         **/
        public static TSRandom instance;

        internal static void Init() {
            instance = New(1);
        }

        /**
         *  @brief Generates a new instance based on a given seed.
         **/
        public static TSRandom New(int seed) {
            TSRandom r = new TSRandom(seed);
            return r;
        }

        private TSRandom() {
            init_genrand((uint)DateTime.Now.Millisecond);
        }

        private TSRandom(int seed) {
            init_genrand((uint)seed);
        }

        private TSRandom(int[] init) {
            uint[] initArray = new uint[init.Length];
            for (int i = 0; i < init.Length; ++i)
                initArray[i] = (uint)init[i];
            init_by_array(initArray, (uint)initArray.Length);
        }

        public static int MaxRandomInt { get { return 0x7fffffff; } }

        /**
         *  @brief Returns a random integer.
         **/
        public int Next() {
            return genrand_int31();
        }

        /**
         *  @brief Returns a random integer.
         **/
        public static int CallNext() {
            return instance.Next();
        }

        /**
         *  @brief Returns a integer between a min value [inclusive] and a max value [exclusive].
         **/
        public int Next(int minValue, int maxValue) {
            if (minValue > maxValue) {
                int tmp = maxValue;
                maxValue = minValue;
                minValue = tmp;
            }

            int range = maxValue - minValue;

            return minValue + Next() % range;
        }

        /**
         *  @brief Returns a {@link FP} between a min value [inclusive] and a max value [inclusive].
         **/
        public FP Next(float minValue, float maxValue) {
            int minValueInt = (int)(minValue * 1000), maxValueInt = (int)(maxValue * 1000);

            if (minValueInt > maxValueInt) {
                int tmp = maxValueInt;
                maxValueInt = minValueInt;
                minValueInt = tmp;
            }

            return (FP.Floor((maxValueInt - minValueInt + 1) * NextFP() +
                minValueInt)) / 1000;
        }

        /**
         *  @brief Returns a integer between a min value [inclusive] and a max value [exclusive].
         **/
        public static int Range(int minValue, int maxValue) {
            return instance.Next(minValue, maxValue);
        }

        /**
         *  @brief Returns a {@link FP} between a min value [inclusive] and a max value [inclusive].
         **/
        public static FP Range(float minValue, float maxValue) {
            return instance.Next(minValue, maxValue);
        }

        /**
         *  @brief Returns a {@link FP} between 0.0 [inclusive] and 1.0 [inclusive].
         **/
        public FP NextFP() {
            return ((FP) Next()) / (MaxRandomInt);
        }

        /**
         *  @brief Returns a {@link FP} between 0.0 [inclusive] and 1.0 [inclusive].
         **/
        public static FP value {
            get {
                return instance.NextFP();
            }
        }

        /**
         *  @brief Returns a random {@link TSVector} representing a point inside a sphere with radius 1.
         **/
        public static TSVector insideUnitSphere {
            get {
                return new TSVector(value, value, value);
            }
        }

        private float NextFloat() {
            return (float)genrand_real2();
        }

        private float NextFloat(bool includeOne) {
            if (includeOne) {
                return (float)genrand_real1();
            }
            return (float)genrand_real2();
        }

        private float NextFloatPositive() {
            return (float)genrand_real3();
        }

        private double NextDouble() {
            return genrand_real2();
        }

        private double NextDouble(bool includeOne) {
            if (includeOne) {
                return genrand_real1();
            }
            return genrand_real2();
        }

        private double NextDoublePositive() {
            return genrand_real3();
        }

        private double Next53BitRes() {
            return genrand_res53();
        }

        public void Initialize() {
            init_genrand((uint)DateTime.Now.Millisecond);
        }

        public void Initialize(int seed) {
            init_genrand((uint)seed);
        }

        public void Initialize(int[] init) {
            uint[] initArray = new uint[init.Length];
            for (int i = 0; i < init.Length; ++i)
                initArray[i] = (uint)init[i];
            init_by_array(initArray, (uint)initArray.Length);
        }

        private void init_genrand(uint s) {
            mt[0] = s & 0xffffffffU;
            for (mti = 1; mti < N; mti++) {
                mt[mti] = (uint)(1812433253U * (mt[mti - 1] ^ (mt[mti - 1] >> 30)) + mti);
                mt[mti] &= 0xffffffffU;
            }
        }

        private void init_by_array(uint[] init_key, uint key_length) {
            int i, j, k;
            init_genrand(19650218U);
            i = 1;
            j = 0;
            k = (int)(N > key_length ? N : key_length);
            for (; k > 0; k--) {
                mt[i] = (uint)((uint)(mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1664525U)) + init_key[j] + j);
                mt[i] &= 0xffffffffU;
                i++;
                j++;
                if (i >= N) {
                    mt[0] = mt[N - 1];
                    i = 1;
                }
                if (j >= key_length)
                    j = 0;
            }
            for (k = N - 1; k > 0; k--) {
                mt[i] = (uint)((uint)(mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) *
                    1566083941U)) - i);
                mt[i] &= 0xffffffffU;
                i++;
                if (i >= N) {
                    mt[0] = mt[N - 1];
                    i = 1;
                }
            }
            mt[0] = 0x80000000U;
        }

        uint genrand_int32() {
            uint y;
            if (mti >= N) {
                int kk;
                if (mti == N + 1)
                    init_genrand(5489U);
                for (kk = 0; kk < N - M; kk++) {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + M] ^ (y >> 1) ^ mag01[y & 0x1U];
                }
                for (; kk < N - 1; kk++) {
                    y = (mt[kk] & UPPER_MASK) | (mt[kk + 1] & LOWER_MASK);
                    mt[kk] = mt[kk + (M - N)] ^ (y >> 1) ^ mag01[y & 0x1U];
                }
                y = (mt[N - 1] & UPPER_MASK) | (mt[0] & LOWER_MASK);
                mt[N - 1] = mt[M - 1] ^ (y >> 1) ^ mag01[y & 0x1U];
                mti = 0;
            }
            y = mt[mti++];
            y ^= (y >> 11);
            y ^= (y << 7) & 0x9d2c5680U;
            y ^= (y << 15) & 0xefc60000U;
            y ^= (y >> 18);
            return y;
        }

        private int genrand_int31() {
            return (int)(genrand_int32() >> 1);
        }

        FP genrand_FP() {
            return (FP)genrand_int32() * (FP.One / (FP)4294967295);
        }

        double genrand_real1() {
            return genrand_int32() * (1.0 / 4294967295.0);
        }
        double genrand_real2() {
            return genrand_int32() * (1.0 / 4294967296.0);
        }

        double genrand_real3() {
            return (((double)genrand_int32()) + 0.5) * (1.0 / 4294967296.0);
        }

        double genrand_res53() {
            uint a = genrand_int32() >> 5, b = genrand_int32() >> 6;
            return (a * 67108864.0 + b) * (1.0 / 9007199254740992.0);
        }
    }

}