#include <iostream>
#include <sstream>
#include <cstdint>
#include <random>
#include <string>
#include <iomanip>
#include <bitset>
using namespace std;

template<class T> 
T __ROL__(T value, uint32_t count) {
	const uint32_t nbits = sizeof(T)* 8;
	count %= nbits;

	T high = value >> (nbits - count);
	value <<= count;
	value |= high;
	return value;
}

template<class T> 
T __ROR__(T value, uint32_t count) {
	const uint32_t nbits = sizeof(T)* 8;
	count %= nbits;

	T low = value << (nbits - count);
	value >>= count;
	value |= low;
	return value;
}

class CRand32 {
public:
	uint32_t Random() {
		return dist(mt);
	}

protected:
	random_device rd;
	mt19937 mt{ rd() };
	uniform_int_distribution<uint32_t> dist;
};

CRand32 rnd;

class maple_exception : public exception {
protected:
	int code;

public:
	maple_exception(int code) : code(code) {};
	~maple_exception() {};
	const char *what() const throw() { 
		stringstream ss;
		// TODO: get actual exception descriptions used by maplestory
		ss << "MapleStory error " << code;
		return ss.str().c_str(); 
	};
};

template <class T, bool shortorbyte>
struct ZtlSecureImpl {
	uint8_t key[sizeof(T)];
	uint8_t encrypted_data[sizeof(T)];
} __attribute__((packed));

template <class T>
struct ZtlSecureImpl<T, false> {
	uint32_t key[sizeof(T) / sizeof(uint32_t)];
	uint32_t encrypted_data[sizeof(T) / sizeof(uint32_t)];
} __attribute__((packed));

template <class T>
struct ZtlSecure : ZtlSecureImpl<T, sizeof(T) <= 2> { } __attribute__((packed));

// set encrypted data
template <class T, bool shortorbyte>
struct ZtlSecureTearImpl {
	static uint32_t call(T value, ZtlSecure<T> *p) {
		auto pvalue = (uint8_t *)&value;
		uint32_t checksum = 0xBAADF00D;
		for (size_t i = 0; i < sizeof(T); i++) {
			p->key[i] = (uint8_t)rnd.Random();
			p->encrypted_data[i] = pvalue[i] ^ p->key[i];
			bitset<32> a(p->encrypted_data[i]);
			bitset<32> b(__ROR__(p->key[i] ^ checksum, 5));
			checksum = p->encrypted_data[i] + __ROR__(p->key[i] ^ checksum, 5);
			bitset<32> c(checksum);
			cout << "checksum = " << endl << a << " + " << endl << b << " = " << endl << c << endl;
		}
		return checksum;
	}
};

template <class T>
struct ZtlSecureTearImpl<T, false> {
	static uint32_t call(T value, ZtlSecure<T> *p) {
		auto pvalue = (uint32_t *)&value;
		uint32_t checksum = 0xBAADF00D;
		for (size_t i = 0; i < sizeof(T) / sizeof(uint32_t); i++) {
			p->key[i] = rnd.Random();
			p->encrypted_data[i] = __ROR__(pvalue[i] ^ p->key[i], 5);
			bitset<32> a(p->encrypted_data[i]);
			bitset<32> b(__ROR__(p->key[i] ^ checksum, 5));
			checksum = p->encrypted_data[i] + __ROR__(p->key[i] ^ checksum, 5);
			bitset<32> c(checksum);
			cout << "checksum = " << endl << a << " + " << endl << b << " = " << endl << c << endl;
		}
		return checksum;
	}
};

template <class T>
uint32_t ZtlSecureTear(T value, ZtlSecure<T> *p) {
	return ZtlSecureTearImpl<T, sizeof(T) <= sizeof(uint16_t)>::call(value, p);
}

// get encrypted data and check validity
template <class T, bool shortorbyte>
struct ZtlSecureFuseImpl {
	static T call(ZtlSecure<T> *p, uint32_t expected_checksum) {
		uint32_t checksum = 0xBAADF00D;
		uint8_t value[sizeof(T)] = { 0 };
		for (size_t i = 0; i < sizeof(T); i++) {
			value[i] = p->key[i] ^ p->encrypted_data[i];
			checksum = p->encrypted_data[i] + __ROR__(p->key[i] ^ checksum, 5);
		}
		if (expected_checksum != checksum) {
			throw maple_exception(5);
		}
		return *(T *)&value[0];
	}
};

template <class T>
struct ZtlSecureFuseImpl<T, false> {
	static T call(ZtlSecure<T> *p, uint32_t expected_checksum) {
		uint32_t checksum = 0xBAADF00D;
		uint32_t value[sizeof(T) / sizeof(uint32_t)] = { 0 };
		for (size_t i = 0; i < sizeof(T) / sizeof(uint32_t); i++) {
			value[i] = p->key[i] ^ __ROL__(p->encrypted_data[i], 5);
			checksum = p->encrypted_data[i] + __ROR__(p->key[i] ^ checksum, 5);
		}
		if (expected_checksum != checksum) {
			throw maple_exception(5);
		}
		return *(T *)&value[0];
	}
};

template <class T>
T ZtlSecureFuse(ZtlSecure<T> *p, uint32_t expected_checksum) {
	return ZtlSecureFuseImpl<T, sizeof(T) <= sizeof(uint16_t)>::call(p, expected_checksum);
}

string fmthex(uint32_t value, int width = 8) {
	stringstream sshex;
	sshex << hex << uppercase << setw(width) << setfill('0') << value;
	return sshex.str();
}

template<class T>
void runtest(const char *tname, T a, T b) {
	try {
		cout << tname << ":" << endl;

		ZtlSecure<T> sec;
		uint32_t check = ZtlSecureTear<T>(a, &sec);
		uint32_t oldcheck = check;
		cout << "ZtlSecureTear(" << +a << ") = " << fmthex(check) << endl;
		cout << "ZtlSecureFuse = " << +ZtlSecureFuse<T>(&sec, check) << endl;
		cout << endl;
		
		check = ZtlSecureTear<T>(b, &sec);
		cout << "ZtlSecureTear(" << +b << ") = " << fmthex(check) << endl;
		cout << "ZtlSecureFuse = " << +ZtlSecureFuse<T>(&sec, check) << endl;
		cout << endl;

		cout << "ZtlSecureFuse (invalid checksum) = ";
		cout << +ZtlSecureFuse<T>(&sec, oldcheck) << endl;
	}
	catch (const std::exception &e) {
		cout << e.what() << endl;
	}

	cout << endl;
}

int main() {
	runtest<int8_t>("int8_t", 42, -127);
	runtest<int16_t>("int16_t", 32767, -10);
	runtest<int32_t>("int32_t", 1337, 42);
	runtest<int64_t>("int64_t", 6666666666, 4242424242);
	runtest<double>("double", 1.337, 66666.66666);
	return 0;
}