#include "crypt.h"
#include "hash.h"
#include "util.h"

void key_schedule(uint64_t* const k, const uint32_t initial_key, const int n)
{
	uint8_t* const kbuf = reinterpret_cast<uint8_t*>(k);

	uint32_t key = initial_key;
	for (int i = 0; i < n; ++i) {
		const uint8_t x = key & 0xFF;
		key = (x << (n - 8)) | (key >> 8);
		kbuf[i] = x;
	}

	memcpy(kbuf + n, kbuf, n);
	memcpy(kbuf + (n << 1), kbuf, (n << 1) & ~0x1);
	memcpy(kbuf + (n << 2), kbuf, (n << 2) & ~0x3);
}

uint32_t checksum(const void* const data, const size_t size)
{
	const uint32_t result = ~crc32_0x04c11db7(0, data, size);
	return result;
}

uint32_t shuffle_bits(const uint32_t data)
{
	uint32_t x = data;
	uint32_t crc = 0;
	for (int i = 0; i < 4; ++i) {
		crc = (crc << 8) ^ kCRC32_TAB_0x04C11DB7[(rotl(x ^ crc, 10) & 0x3FC) >> 2];
		x <<= 8;
	}
	return ~crc;
}

uint32_t crypt_block(const uint32_t x, const uint32_t y)
{
	const uint32_t result = x ^ shuffle_bits(y);
	return result;
}

uint32_t xor_shift_loop(const uint32_t x, const uint32_t y)
{
	uint32_t result = x;
	for (int i = 0; i < 32; ++i) {
		const uint32_t upper_bit = result & 0x80000000u;
		result <<= 1;
		if (upper_bit)
			result ^= y;
	}
	return result;
}

uint32_t inversed_xor_shift_loop(const uint32_t x, const uint32_t y)
{
	const uint32_t result = ~xor_shift_loop(x, y);
	return result;
}

bool block_crypt(const void* const src, void* const dst, const size_t size)
{
	const uint32_t* src_block = static_cast<const uint32_t*>(src);
	const uint32_t* const src_block_end = src_block + size / sizeof(uint32_t);
	if (src_block + 1 >= src_block_end)
		return false;
	uint32_t* dst_block = static_cast<uint32_t*>(dst);

	uint32_t prev = host_to_big(*src_block++);
	*dst_block++ = big_to_host(prev);

	while (src_block < src_block_end) {
		const uint32_t cur = host_to_big(*src_block++);
		*dst_block++ = big_to_host(crypt_block(cur, prev));
		prev = cur;
	}

	return true;
}

void data_keygen(const char* seed, const uint32_t* const key, const uint32_t x, uint32_t* const out_key)
{
	uint32_t c0 = checksum(seed, strlen(seed)) ^ x;
	uint32_t c1 = inversed_xor_shift_loop(c0, key[0]);
	uint32_t c2 = inversed_xor_shift_loop(c1, key[1]);
	uint32_t c3 = inversed_xor_shift_loop(c2, key[2]);
	uint32_t c4 = inversed_xor_shift_loop(c3, key[3]);

	c1 &= 0x1FFFFu;
	c2 &= 0x7FFFFu;
	c3 &= 0x7FFFFFu;
	c4 &= 0x1FFFFFFFu;

	out_key[0] = c1;
	out_key[1] = c2;
	out_key[2] = c3;
	out_key[3] = c4;
}

void data_crypt(const uint32_t* const key, const void* const src, void* const dst, const size_t size)
{
	uint32_t c1 = key[0];
	uint32_t c2 = key[1];
	uint32_t c3 = key[2];
	uint32_t c4 = key[3];

	const uint8_t* src_byte = static_cast<const uint8_t*>(src);
	const uint8_t* const src_byte_end = src_byte + size;
	uint8_t* dst_byte = static_cast<uint8_t*>(dst);

	while (src_byte < src_byte_end) {
		*dst_byte++ = (((c1 ^ c2) ^ *src_byte++) ^ (c3 ^ c4)) & 0xFF;
		c1 = ((rotl(c1, 9) & 0x1FE00u) | (c1 >> 8));
		c2 = ((rotl(c2, 11) & 0x7F800u) | (c2 >> 8));
		c3 = ((rotl(c3, 15) & 0x7F8000u) | (c3 >> 8));
		c4 = ((rotl(c4, 21) & 0x1FE00000u) | (c4 >> 8));
	}
}
