#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <getopt.h>
#include <stdexcept>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <NTL/GF2X.h>
#include <NTL/vec_GF2.h>
#include <NTL/GF2XFactoring.h>
#include "random_util.hpp"
#include "mtgp_param.h"
#include "mtgp32ex.h"
#include "mtgp64ex.h"

using namespace std;
using namespace mtgp;
using namespace NTL;
using namespace MTToolBox;

static bool check_pol = false;
static bool check_out = false;
static bool check_speed = false;
static int bit_size = 0;
static char *filename;
static int line_no;

bool parse_option(int argc, char **argv) {
    int c;
    bool error = false;
    string pgm = argv[0];
    static struct option longopts[] = {
	{"speed", no_argument, NULL, 's'},
	{"output", no_argument, NULL, 'o'},
	{"polynomial", no_argument, NULL, 'p'},
	{NULL, 0, NULL, 0}};
    for (;;) {
	c = getopt_long(argc, argv, "sop", longopts, NULL);
	if (error) {
	    break;
	}
	if (c == -1) {
	    break;
	}
	switch (c) {
	case 's':
	    check_speed = true;
	    break;
	case 'o':
	    check_out = true;
	    break;
	case 'p':
	    check_pol = true;
	    break;
	case '?':
	default:
	    error = true;
	    break;
	}
    }
    argc -= optind;
    argv += optind;
    if (argc < 3) {
	error = true;
    } else {
	bit_size = strtol(argv[0], NULL, 10);
	if (errno != 0) {
	    error = true;
	    cerr << "bit_size must be 32 or 64" << endl;
	} else {
	    if (bit_size != 32 && bit_size != 64) {
		error = true;
		cerr << "bit_size must be 32 or 64" << endl;
	    }
	}
	filename = argv[1];
	line_no = strtol(argv[2], NULL, 10);
	if (errno != 0) {
	    error = true;
	    cerr << "id must be a number between 0 and 2^32-1" << endl;
	}
    }
    if (error) {
	cerr << pgm
	     << " [-p] [-o] [-s] bit_size filename line_no" << endl;
	cerr << "--polynomial, -p       check polynomial and sha1" << endl;
	cerr << "--output, -o           check output" << endl;
	cerr << "--speed, -s            check speed" << endl;
	return false;
    }
    return true;
}


template<typename T>
T strto(const string& str, int base) {
    T r;
    r = static_cast<T>(strtoull(str.c_str(), NULL, base));
    if (errno) {
	throw new invalid_argument("ottama");
    }
    return r;
}

template<typename T>
bool read_param(mtgp_param<T>& p, istream& is, int line_no) {
    string line;
    for (;is && line_no > 0; line_no--) {
	getline(is, line);
    }
    if (!is) {
	return false;
    }
    getline(is, line);
    vector<string> vec;
    int start = 0;
    unsigned int pos = line.find(",", 0);
    while (pos != string::npos) {
	string q;
	q.assign(line, start, pos - start);
	vec.push_back(q);
	start = pos + 1;
	pos = line.find(",", start);
    }
    vec[0].assign(vec[0], 1, vec[0].size() -2);
    p.set_sha1(vec[0]);
    p.mexp = strto<int>(vec[1], 10);
    p.pos = strto<int>(vec[4], 10);
    p.sh1 = strto<int>(vec[5], 10);
    p.sh2 = strto<int>(vec[6], 10);
    p.id = strto<uint32_t>(vec[3], 10);
    for (int i = 0; i < 4; i++) {
	p.tbl[i] = strto<T>(vec[7 + i], 16);
    }
    for (int i = 0; i < 4; i++) {
	p.tmp_tbl[i] = strto<T>(vec[11 + i], 16);
    }
    p.mask = strto<T>(vec[15], 16);
    return true;
}

template<typename T, typename G>
bool check_polynomial(G& generator) {
    int mexp = generator.get_mexp();
    vec_GF2 vec;
    T r;
    vec.SetLength(mexp * 2);
    for (int i = 0; i < mexp * 2; i++) {
	r = generator.next() & 1;
	vec[i] = r;
    }
    GF2X minpoly;
    MinPolySeq(minpoly, vec, mexp);
    if (deg(minpoly) != mexp) {
	cout << "mexp not match!" << endl;
	cout << "degree is " << dec << deg(minpoly) << endl;
	return false;
    }
    if (IterIrredTest(minpoly)) {
	cout << "IrredTest OK!" << endl;
    } else {
	cout << "IrredTest NG!" << endl;
	return false;
    }
    string sha1;
    poly_sha1(sha1, minpoly);
    if (sha1 == generator.get_sha1()) {
	cout << "SHA1 OK!" << endl;
	return true;
    } else {
	cout << "SHA1 NG!" << endl;
	return false;
    }
}

template<typename T, typename G>
static void hex_output(G& generator, int count, int br_count) {
    bool end_line = false;
    for (int i = 0; i < count; i++) {
	end_line = false;
	cout << hex << setfill('0') << setw(sizeof(T) * 2)
	     << generator() << " ";
	if (i % br_count == br_count - 1) {
	    cout << endl;
	    end_line = true;
	}
    }
    if (!end_line) {
	cout << endl;
    }
}

template<typename T, typename G>
static void float_output(G& generator, int count, int br_count) {
    bool end_line = false;
    for (int i = 0; i < count; i++) {
	end_line = false;
	cout << dec << setprecision(sizeof(T) * 2 + 2)
	     << setiosflags(ios::fixed)
	     << setw(sizeof(T) * 2 + 4)
	     << generator() << " ";
	if (i % br_count == br_count - 1) {
	    cout << endl;
	    end_line = true;
	}
    }
    if (!end_line) {
	cout << endl;
    }
}

enum outtype {INT, FLOAT, FL_C1_O2, FL_OC, FL_OO};
template<typename T, typename G>
class generator {
public:
    generator(G& g, outtype o) {
	gen = &g;
	otype = o;
    };
    T operator()() {
	switch(otype) {
	case INT:
	    return gen->next();
	case FLOAT:
	    return gen->next_float_close_open();
	case FL_C1_O2:
	    return gen->next_float_close1_open2();
	case FL_OC:
	    return gen->next_float_open_close();
	case FL_OO:
	default:
	    return gen->next_float_open_open();
	}
    };
private:
    G *gen;
    outtype otype;
};

template<typename T, typename G>
void check_time_int(G& generator) {
    clock_t clo;
    T x = 0;
    const int TIC_COUNT = 50000000;
    clo = clock();
    for (int i = 0; i < TIC_COUNT; i++) {
	x ^= generator.next();
    }
    clo = clock() - clo;
    cout << "time for " << TIC_COUNT << " generation" << endl;
    cout <<  1.0 * clo / CLOCKS_PER_SEC << "s checksum("
	 << (x & 1) << ")" << endl;
}

template<typename T, typename F, typename G>
void check_all(ifstream& is, int line_no) {
    mtgp_param<T> p;
    bool success = read_param<T>(p, is, line_no);
    if (!success) {
	cout << "can't read parameter." << endl;
	return;
    }
    G ex(p);
    if (check_pol) {
	ex.print(cout);
	check_polynomial<T, G>(ex);
    }

    if (check_out) {
	ex.reseed(1);
	cout << "init:" << endl;
	generator<T, G>g1(ex, INT);
	hex_output<T, generator<T, G> >(g1, 1000, 3);
	T ar[4] = {1, 2, 3, 4};
	ex.reseed(ar, 4);
	cout << "init_by_array:" << endl;
	hex_output<T, generator<T, G> >(g1, 1000, 3);
	string s = "\1\2\3\4";
	ex.reseed(s);
	cout << "init_by_str:" << endl;
	hex_output<T, generator<T, G> >(g1, 1000, 3);
	cout << "[1, 2)" << endl;
	generator<F, G>g2(ex, FL_C1_O2);
	float_output<F, generator<F, G> >(g2, 1000, 3);
	cout << "[0, 1)" << endl;
	generator<F, G>g3(ex, FLOAT);
	float_output<F, generator<F, G> >(g3, 1000, 3);
	cout << "(0, 1]" << endl;
	generator<F, G>g4(ex, FL_OC);
	float_output<F, generator<F, G> >(g4, 1000, 3);
	cout << "(0, 1)" << endl;
	generator<F, G>g5(ex, FL_OO);
	float_output<F, generator<F, G> >(g5, 1000, 3);
    }
    if (check_speed) {
	check_time_int<T, G>(ex);
    }
}

int main(int argc, char** argv) {
    if (!parse_option(argc, argv)) {
	return 1;
    }
    ifstream is(filename);
    if (bit_size == 32) {
	check_all<uint32_t, float, mtgp32ex>(is, line_no);
    } else {
	check_all<uint64_t, double, mtgp64ex>(is, line_no);
    }
    is.close();
    return 0;
}
