#include <iomanip>
#include "random_util.hpp"
#include "mtgp32ex.h"
#include "mtgp64ex.h"

namespace mtgp {
    using namespace std;

    /*
     * COMMON
     */

    /**
     * non zero number.
     * This constant assures that the internal state array is not all zero.
     * 'M','T','G','P'.
     */
    static const uint32_t non_zero = 0x4d544750;

    /**
     * This function represents a function used in the initialization
     * by mtgp32_init_by_array() and mtgp32_init_by_str().
     * @param[in] x 32-bit integer
     * @return 32-bit integer
     */
    template<typename T, int sh, T c>
    class ini_func {
    public:
	T operator ()(T x) {
	    return (x ^ (x >> sh)) * c;
	};
    };
    typedef ini_func<uint32_t, 27, UINT32_C(1664525)> ini_func32_1;
    typedef ini_func<uint32_t, 27, UINT32_C(1566083941)> ini_func32_2;
    typedef ini_func<uint64_t, 59, UINT64_C(2173292883993)> ini_func64_1;
    typedef ini_func<uint64_t, 59, UINT64_C(58885565329898161)> ini_func64_2;
    ini_func32_1 func321;
    ini_func32_2 func322;
    ini_func64_1 func641;
    ini_func64_2 func642;

    template<typename T, typename ST, typename IF1, typename IF2, int shift>
    static void init_by_array(T *array, int size, T rec_tbl[16],
			      const ST *seed_array, int seed_size,
			      IF1 ini_func1, IF2 ini_func2) {
	int i, j, count;
	T r;
	int lag;
	int mid;
	T hidden_seed;
	T tmp;

	if (size >= 623) {
	    lag = 11;
	} else if (size >= 68) {
	    lag = 7;
	} else if (size >= 39) {
	    lag = 5;
	} else {
	    lag = 3;
	}
	mid = (size - lag) / 2;

	hidden_seed = rec_tbl[4] ^ (rec_tbl[8] << 16);
	tmp = hidden_seed >> shift;
	tmp += tmp >> 16;
	tmp += tmp >> 8;
	memset(array, tmp & 0xff, sizeof(T) * size);
	array[0] = hidden_seed;

	if (seed_size + 1 > size) {
	    count = seed_size + 1;
	} else {
	    count = size;
	}
	r = ini_func1(array[0] ^ array[mid] ^ array[size - 1]);
	array[mid] += r;
	r += seed_size;
	array[(mid + lag) % size] += r;
	array[0] = r;
	i = 1;
	count--;
	for (i = 1, j = 0; (j < count) && (j < seed_size); j++) {
	    r = ini_func1(array[i] ^ array[(i + mid) % size]
			  ^ array[(i + size - 1) % size]);
	    array[(i + mid) % size] += r;
	    r += static_cast<T>(seed_array[j]) + i;
	    array[(i + mid + lag) % size] += r;
	    array[i] = r;
	    i = (i + 1) % size;
	}
	for (; j < count; j++) {
	    r = ini_func1(array[i] ^ array[(i + mid) % size]
			  ^ array[(i + size - 1) % size]);
	    array[(i + mid) % size] += r;
	    r += i;
	    array[(i + mid + lag) % size] += r;
	    array[i] = r;
	    i = (i + 1) % size;
	}
	for (j = 0; j < size; j++) {
	    r = ini_func2(array[i] + array[(i + mid) % size]
			  + array[(i + size - 1) % size]);
	    array[(i + mid) % size] ^= r;
	    r -= i;
	    array[(i + mid + lag) % size] ^= r;
	    array[i] = r;
	    i = (i + 1) % size;
	}
	if (array[size - 1] == 0) {
	    array[size - 1] = non_zero;
	}
    }

    mtgp32ex::mtgp32ex(const mtgp_param<uint32_t>& param, uint32_t seed) {
	setup(param);
	reseed(seed);
    }
    mtgp32ex::mtgp32ex(const mtgp_param<uint32_t>& param,
		       const uint32_t *seed, int size) {
	setup(param);
	reseed(seed, size);
    }
    mtgp32ex::mtgp32ex(const mtgp_param<uint32_t>& param,
		       const std::string& seed) {
	setup(param);
	reseed(seed);
    }
    mtgp32ex::mtgp32ex(const mtgp_param<uint32_t>& param) {
	setup(param);
	reseed(sha1);
    }
    mtgp32ex::~mtgp32ex() {
	delete[] array;
    }
    void mtgp32ex::reseed(uint32_t seed) {
	int i;
	uint32_t hidden_seed;
	uint32_t tmp;

	hidden_seed = rec_tbl[4] ^ (rec_tbl[8] << 16);
	tmp = hidden_seed;
	tmp += tmp >> 16;
	tmp += tmp >> 8;
	memset(array, tmp & 0xff, sizeof(uint32_t) * status_size);
	array[0] = seed;
	array[1] = hidden_seed;
	for (i = 1; i < status_size; i++) {
	    array[i] ^= UINT32_C(1812433253)
		* (array[i - 1] ^ (array[i - 1] >> 30))
		+ i;
	}
	idx = status_size - 1;
    }

    void mtgp32ex::reseed(const std::string& seed) {
	init_by_array<uint32_t, char, ini_func32_1, ini_func32_2, 0>(
	    array, status_size, rec_tbl,
	    seed.c_str(),
	    static_cast<int>(seed.size()),
	    func321, func322);
	idx = status_size - 1;
    }

    void mtgp32ex::reseed(const uint32_t *seed, int size) {
	init_by_array<uint32_t,
	    uint32_t,
	    ini_func32_1,
	    ini_func32_2,
	    0>(array,
	       status_size,
	       rec_tbl,
	       seed,
	       size,
	       func321,
	       func322);
	idx = status_size - 1;
    }

    int mtgp32ex::get_mexp() const {
	return mexp;
    }

    const std::string& mtgp32ex::get_sha1() const {
	return sha1;
    }

    void mtgp32ex::setup(const mtgp_param<uint32_t>& param) {
	mexp = param.mexp;
	status_size = param.mexp / bit_size(uint32_t) + 1;
	array_size = 1;
	array_size_mask = 0;
	for (int x = status_size; x > 0; x >>= 1) {
	    array_size <<= 1;
	    array_size_mask = (array_size_mask << 1) | 1;
	}
	array = new uint32_t[array_size];
	sh1 = param.sh1;
	sh2 = param.sh2;
	pos = param.pos;
	sha1 = param.sha1;
	mask = param.mask;
	memset(rec_tbl, 0, sizeof(uint32_t) * 16);
	memset(tmp_tbl, 0, sizeof(uint32_t) * 16);
	memset(flt_tbl, 0, sizeof(uint32_t) * 16);
	for (int i = 0; i < 16; i++) {
	    for(int j = 1, k = 0; j <= i; j <<= 1, k++) {
		if (i & j) {
		    rec_tbl[i] ^= param.tbl[k];
		}
	    }
	}
	for (int i = 0; i < 16; i++) {
	    for(int j = 1, k = 0; j <= i; j <<= 1, k++) {
		if (i & j) {
		    tmp_tbl[i] ^= param.tmp_tbl[k];
		}
	    }
	}
	for (int i = 0; i < 16; i++) {
	    flt_tbl[i] = (tmp_tbl[i] >> 9) | UINT32_C(0x3f800000);
	}
    }
    void mtgp32ex::print(ostream& os) {
	os << "-- mtgp32ex --" << endl;
	os << "mexp:" << dec << mexp << endl;
	os << "pos:" << dec << pos << endl;
	os << "sh1:" << dec << sh1 << endl;
	os << "sh2:" << dec << sh2 << endl;
	os << "mask:" << hex << setw(sizeof(uint32_t) * 2)
	   << setfill('0') << mask << endl;
	os << "status_size:" << dec << status_size << endl;
	os << "array_size:" << dec << array_size << endl;
	os << "size_mask:" << hex << setw(sizeof(uint32_t) * 2)
	   << setfill('0') << array_size_mask << endl;
	os << "index:" << dec << idx << endl;
	for (int i = 1, j = 0; i < 16; i <<= 1, j++) {
	    os << "rec_tbl[" << dec << j << "]"
	       << hex << setw(sizeof(uint32_t) * 2)
	       << setfill('0') << rec_tbl[i] << endl;
	}
	for (int i = 1, j = 0; i < 16; i <<= 1, j++) {
	    os << "tmp_tbl[" << dec << j << "]"
	       << hex << setw(sizeof(uint32_t) * 2)
	       << setfill('0') << tmp_tbl[i] << endl;
	}
	os << "sha1:" << sha1 << endl;
    }
    /*
     * MTGP64EX
     */
    mtgp64ex::mtgp64ex(const mtgp_param<uint64_t>& param, uint64_t seed) {
	setup(param);
	reseed(seed);
    }
    mtgp64ex::mtgp64ex(const mtgp_param<uint64_t>& param,
		       const uint64_t *seed, int size) {
	setup(param);
	reseed(seed, size);
    }
    mtgp64ex::mtgp64ex(const mtgp_param<uint64_t>& param,
		       const std::string& seed) {
	setup(param);
	reseed(seed);
    }
    mtgp64ex::mtgp64ex(const mtgp_param<uint64_t>& param) {
	setup(param);
	reseed(sha1);
    }
    mtgp64ex::~mtgp64ex() {
	delete[] array;
    }

    void mtgp64ex::reseed(uint64_t seed) {
	int i;
	uint64_t hidden_seed;
	uint64_t tmp;

	hidden_seed = rec_tbl[4] ^ (rec_tbl[8] << 16);
	tmp = hidden_seed >> 32;
	tmp += tmp >> 16;
	tmp += tmp >> 8;
	memset(array, tmp & 0xff, sizeof(uint64_t) * status_size);
	array[0] = seed;
	array[1] = hidden_seed;
	for (i = 1; i < status_size; i++) {
	    array[i] ^= UINT64_C(6364136223846793005) * (array[i - 1]
						^ (array[i - 1] >> 62))
		+ i;
	}
	idx = status_size - 1;
    }

    void mtgp64ex::reseed(const std::string& seed) {
	init_by_array<uint64_t, char, ini_func64_1, ini_func64_2, 32>(
	    array, status_size, rec_tbl,
	    seed.c_str(),
	    static_cast<int>(seed.size()),
	    func641, func642);
	idx = status_size - 1;
    }

    void mtgp64ex::reseed(const uint64_t *seed, int size) {
	init_by_array<uint64_t,
	    uint64_t,
	    ini_func64_1,
	    ini_func64_2,
	    32>(array,
		status_size,
		rec_tbl,
		seed,
		size,
		func641,
		func642);
	idx = status_size - 1;
    }

    int mtgp64ex::get_mexp() const {
	return mexp;
    }

    const std::string& mtgp64ex::get_sha1() const {
	return sha1;
    }

    void mtgp64ex::setup(const mtgp_param<uint64_t>& param) {
	mexp = param.mexp;
	status_size = param.mexp / bit_size(uint64_t) + 1;
	array_size = 1;
	array_size_mask = 0;
	for (int x = status_size; x > 0; x >>= 1) {
	    array_size <<= 1;
	    array_size_mask = (array_size_mask << 1) | 1;
	}
	array = new uint64_t[array_size];
	sh1 = param.sh1;
	sh2 = param.sh2;
	pos = param.pos;
	sha1 = param.sha1;
	mask = param.mask;
	memset(rec_tbl, 0, sizeof(uint64_t) * 16);
	memset(tmp_tbl, 0, sizeof(uint64_t) * 16);
	memset(flt_tbl, 0, sizeof(uint64_t) * 16);
	for (int i = 0; i < 16; i++) {
	    for(int j = 1, k = 0; j <= i; j <<= 1, k++) {
		if (i & j) {
		    rec_tbl[i] ^= param.tbl[k];
		}
	    }
	}
	for (int i = 0; i < 16; i++) {
	    for(int j = 1, k = 0; j <= i; j <<= 1, k++) {
		if (i & j) {
		    tmp_tbl[i] ^= param.tmp_tbl[k];
		}
	    }
	}
	for (int i = 0; i < 16; i++) {
	    flt_tbl[i] = (tmp_tbl[i] >> 12) | UINT64_C(0x3ff0000000000000);
	}
    }

    void mtgp64ex::print(ostream& os) {
	os << "-- mtgp64ex --" << endl;
	os << "mexp:" << dec << mexp << endl;
	os << "pos:" << dec << pos << endl;
	os << "sh1:" << dec << sh1 << endl;
	os << "sh2:" << dec << sh2 << endl;
	os << "mask:" << hex << setw(sizeof(uint64_t) * 2)
	   << setfill('0') << mask << endl;
	os << "status_size:" << dec << status_size << endl;
	os << "array_size:" << dec << array_size << endl;
	os << "size_mask:" << hex << setw(sizeof(uint32_t) * 2)
	   << setfill('0') << array_size_mask << endl;
	os << "index:" << dec << idx << endl;
	for (int i = 1, j = 0; i < 16; i <<= 1, j++) {
	    os << "rec_tbl[" << dec << j << "]"
	       << hex << setw(sizeof(uint64_t) * 2)
	       << setfill('0') << rec_tbl[i] << endl;
	}
	for (int i = 1, j = 0; i < 16; i <<= 1, j++) {
	    os << "tmp_tbl[" << dec << j << "]"
	       << hex << setw(sizeof(uint64_t) * 2)
	       << setfill('0') << tmp_tbl[i] << endl;
	}
	os << "sha1:" << sha1 << endl;
    }
}

