#ifndef MTGP32EX_H
#define MTGP32EX_H

#include <stdint.h>
#include <string>
#include <iostream>
#include "mtgp_param.h"

namespace mtgp {
    using namespace std;

    /**
     * @class mtgp32ex
     * @brief Mersenne Twister for Graphic Processors 32-bit executor
     */
    class mtgp32ex {
    public:
	mtgp32ex(const mtgp_param<uint32_t> & param, uint32_t seed);
	mtgp32ex(const mtgp_param<uint32_t> & param,
		 const uint32_t *seed, int size);
	mtgp32ex(const mtgp_param<uint32_t> & param,
		 const string& seed);
	mtgp32ex(const mtgp_param<uint32_t> & param);
	~mtgp32ex();
	uint32_t next() {
	    idx = (idx + 1) & array_size_mask;
	    uint32_t r = recur(array[(array_size - status_size + idx)
				     & array_size_mask],
			       array[(array_size - status_size + idx + 1)
				     & array_size_mask],
			       array[(array_size - status_size + idx + pos)
				     & array_size_mask]);
	    array[idx] = r;
	    r = temper(r, array[(array_size - status_size + idx + pos - 1)
				& array_size_mask]);
	    return r;
	};
	float next_float() {
	    return next_float_close1_open2() - 1.0f;
	};
	float next_float_close_open() {
	    return next_float();
	};
	float next_float_open_close() {
	    return 2.0f - next_float_close1_open2();
	};
	float next_float_open_open() {
	    idx = (idx + 1) & array_size_mask;
	    uint32_t r = recur(array[(array_size - status_size + idx)
				   & array_size_mask],
			       array[(array_size - status_size + idx + 1)
				     & array_size_mask],
			       array[(array_size - status_size + idx + pos)
				     & array_size_mask]);
	    array[idx] = r;
	    float f
		= float_temper_open_open(
		    r,
		    array[(array_size - status_size + idx + pos - 1)
			  & array_size_mask]);
	    return f;
	};
	float next_float_close1_open2() {
	    idx = (idx + 1) & array_size_mask;
	    uint32_t r = recur(array[(array_size - status_size + idx)
				   & array_size_mask],
			       array[(array_size - status_size + idx + 1)
				     & array_size_mask],
			       array[(array_size - status_size + idx + pos)
				     & array_size_mask]);
	    array[idx] = r;
	    float f
		= float_temper(r,
			       array[(array_size - status_size + idx + pos - 1)
				     & array_size_mask]);
	    return f;
	};
	void reseed(uint32_t seed);
	void reseed(const std::string& seed);
	void reseed(const uint32_t *seed, int size);
	int get_mexp() const;
	const std::string& get_sha1() const;
	void print(ostream& os);
    private:
	uint32_t recur(uint32_t p1, uint32_t p2, uint32_t p3) {
	    p1 = (p1 & mask) ^ p2;
	    p1 ^= p1 << sh1;
	    p3 = p1 ^ (p3 >> sh2);
	    return p3 ^ rec_tbl[p3 & 0x0f];
	}
	uint32_t temper(uint32_t v, uint32_t h) {
	    h = h ^ (h >> 16);
	    h = h ^ (h >> 8);
	    return v ^ tmp_tbl[h & 0x0f];
	};
	float float_temper(uint32_t v, uint32_t h) {
	    union {
		uint32_t u;
		float f;
	    } w;
	    h = h ^ (h >> 16);
	    h = h ^ (h >> 8);
	    w.u = (v >> 9) ^ flt_tbl[h & 0x0f];
	    return w.f;
	};
	float float_temper_open_open(uint32_t v, uint32_t h) {
	    union {
		uint32_t u;
		float f;
	    } w;
	    h = h ^ (h >> 16);
	    h = h ^ (h >> 8);
	    w.u = ((v >> 9) ^ flt_tbl[h & 0x0f]) | 1;
	    return w.f - 1.0f;
	};
	void setup(const mtgp_param<uint32_t>& param);
	uint32_t *array;
	uint32_t rec_tbl[16];
	uint32_t tmp_tbl[16];
	uint32_t flt_tbl[16];
	uint32_t mask;
	int mexp;
	int sh1;
	int sh2;
	int pos;
	int status_size;
	int array_size;
	int array_size_mask;
	int idx;
	std::string sha1;
    };
}

#endif
