#include "mt19937.h"
#include "mt19937-jump.h"
#include "mt19937-poly.h"

void mt19937_seed(mt19937_state_t *state, uint32_t seed)
{
    int pos;
    seed &= 0xffffffffUL;

    /* Knuth's PRNG as used in the Mersenne Twister reference implementation */
    for (pos = 0; pos < RK_STATE_LEN; pos++)
    {
        state->key[pos] = seed;
        seed = (1812433253UL * (seed ^ (seed >> 30)) + pos + 1) & 0xffffffffUL;
    }
    state->pos = RK_STATE_LEN;
}

/* initializes mt[RK_STATE_LEN] with a seed */
static void init_genrand(mt19937_state_t *state, uint32_t s)
{
    int mti;
    uint32_t *mt = state->key;

    mt[0] = s & 0xffffffffUL;
    for (mti = 1; mti < RK_STATE_LEN; mti++)
    {
        /*
         * See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier.
         * In the previous versions, MSBs of the seed affect
         * only MSBs of the array mt[].
         * 2002/01/09 modified by Makoto Matsumoto
         */
        mt[mti] = (1812433253UL * (mt[mti - 1] ^ (mt[mti - 1] >> 30)) + mti);
        /* for > 32 bit machines */
        mt[mti] &= 0xffffffffUL;
    }
    state->pos = mti;
    return;
}

/*
 * initialize by an array with array-length
 * init_key is the array for initializing keys
 * key_length is its length
 */
void mt19937_init_by_array(mt19937_state_t *state, uint32_t *init_key,
                           int key_length)
{
    /* was signed in the original code. RDH 12/16/2002 */
    int i = 1;
    int j = 0;
    uint32_t *mt = state->key;
    int k;

    init_genrand(state, 19650218UL);
    k = (RK_STATE_LEN > key_length ? RK_STATE_LEN : key_length);
    for (; k; k--)
    {
        /* non linear */
        mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1664525UL)) +
                init_key[j] + j;
        /* for > 32 bit machines */
        mt[i] &= 0xffffffffUL;
        i++;
        j++;
        if (i >= RK_STATE_LEN)
        {
            mt[0] = mt[RK_STATE_LEN - 1];
            i = 1;
        }
        if (j >= key_length)
        {
            j = 0;
        }
    }
    for (k = RK_STATE_LEN - 1; k; k--)
    {
        mt[i] = (mt[i] ^ ((mt[i - 1] ^ (mt[i - 1] >> 30)) * 1566083941UL)) -
                i;             /* non linear */
        mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
        i++;
        if (i >= RK_STATE_LEN)
        {
            mt[0] = mt[RK_STATE_LEN - 1];
            i = 1;
        }
    }

    mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
}

void mt19937_gen(mt19937_state_t *state)
{
    uint32_t y;
    int i;

    for (i = 0; i < N - M; i++)
    {
        y = (state->key[i] & UPPER_MASK) | (state->key[i + 1] & LOWER_MASK);
        state->key[i] = state->key[i + M] ^ (y >> 1) ^ (-(y & 1) & MATRIX_A);
    }
    for (; i < N - 1; i++)
    {
        y = (state->key[i] & UPPER_MASK) | (state->key[i + 1] & LOWER_MASK);
        state->key[i] =
            state->key[i + (M - N)] ^ (y >> 1) ^ (-(y & 1) & MATRIX_A);
    }
    y = (state->key[N - 1] & UPPER_MASK) | (state->key[0] & LOWER_MASK);
    state->key[N - 1] = state->key[M - 1] ^ (y >> 1) ^ (-(y & 1) & MATRIX_A);

    state->pos = 0;
}

extern INLINE uint64_t mt19937_next64(mt19937_state_t *state);

extern INLINE uint32_t mt19937_next32(mt19937_state_t *state);

extern INLINE double mt19937_next_double(mt19937_state_t *state);

void mt19937_jump_default(mt19937_state_t *state)
{
    /*
     * Testing function for comparison with default
     */
    mt19937_jump_state(state, poly_default);
}

void mt19937_jump(mt19937_state_t *state)
{
    mt19937_jump_state(state, poly_128);
}

void mt19937_jump_n(mt19937_state_t *state, int count)
{
    /* poly_xxx is 2**xxx ahead */
    int remaining = count;
    while (remaining > 0)
    {
        if (remaining >= 65536)
        {
            mt19937_jump_state(state, poly_144);
            remaining -= 65536;
        }
        else if (remaining >= 4096)
        {
            mt19937_jump_state(state, poly_140);
            remaining -= 4096;
        }
        else if (remaining >= 256)
        {
            mt19937_jump_state(state, poly_136);
            remaining -= 256;
        }
        else if (remaining >= 16)
        {
            mt19937_jump_state(state, poly_132);
            remaining -= 16;
        }
        else
        {
            mt19937_jump_state(state, poly_128);
            remaining -= 1;
        }
    }
};
