#include "obfuscate.h"
#include <stddef.h>

static uint16_t rotl16(uint16_t x, unsigned int n)
{
	n &= 15u;
	return (uint16_t)((x << n) | (x >> (16u - n)));
}

static uint64_t rotl40(uint64_t x, unsigned int n)
{
	n %= OBF_PAYLOAD_BITS;
	x &= OBF_PAYLOAD_MASK;
	if (n == 0u)
		return x;
	return ((x << n) | (x >> (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
}

static uint64_t rotr40(uint64_t x, unsigned int n)
{
	n %= OBF_PAYLOAD_BITS;
	x &= OBF_PAYLOAD_MASK;
	if (n == 0u)
		return x;
	return ((x >> n) | (x << (OBF_PAYLOAD_BITS - n))) & OBF_PAYLOAD_MASK;
}

/* Lightweight seed mixer — same output for same seed on any platform. */
static uint64_t derive_key(uint64_t seed)
{
	uint64_t x = seed ^ 0xA5A5A5A5A5A5A5A5ULL;

	x ^= x >> 33;
	x *= 0xFF51AFD7ED558CCDULL;
	x ^= x >> 33;
	x *= 0xC4CEB9FE1A85EC53ULL;
	x ^= x >> 33;
	return x & OBF_PAYLOAD_MASK;
}

static unsigned int derive_rotation(uint64_t seed)
{
	uint64_t mix = seed * 0x9E3779B97F4A7C15ULL;

	return (unsigned int)((mix >> 59) + 1u); /* 1..8 */
}

/*
 * 24-bit FEC-style tag: four 16-bit shards XOR-mixed with key and seed.
 * Detects single-bit flips and wrong seed with high probability.
 */
static uint32_t compute_tag(uint64_t val, uint64_t seed)
{
	uint64_t k = derive_key(seed);
	uint64_t a = (val & 0xFFFFULL) ^ (k & 0xFFFFULL);
	uint64_t b = ((val >> 16) & 0xFFFFULL) ^ ((k >> 16) & 0xFFFFULL);
	uint64_t c = ((val >> 32) & 0xFFFFULL) ^ ((k >> 32) & 0xFFFFULL);
	uint64_t d = ((val >> 48) & 0xFFFFULL) ^ ((k >> 48) & 0xFFFFULL);
	uint64_t p1 = a ^ rotl16((uint16_t)b, 3) ^ c;
	uint64_t p2 = b ^ rotl16((uint16_t)c, 7) ^ d;
	uint64_t p3 = c ^ rotl16((uint16_t)d, 11) ^ a;
	uint64_t mix = (p1 * 0x01000193ULL) ^ (p2 << 16) ^ (p3 << 8);

	mix ^= seed;
	mix *= 0x85EBCA77C2B2AE63ULL;
	mix ^= mix >> 33;
	return (uint32_t)(mix & 0xFFFFFFULL);
}

static uint64_t obfuscate(uint64_t val, uint64_t seed)
{
	uint64_t key = derive_key(seed);
	unsigned int rot = derive_rotation(seed);

	return rotl40(val & OBF_PAYLOAD_MASK, rot) ^ key;
}

static uint64_t deobfuscate(uint64_t obscured, uint64_t seed)
{
	uint64_t key = derive_key(seed);
	unsigned int rot = derive_rotation(seed);

	return rotr40(obscured ^ key, rot);
}

int32_t encode(uint64_t val, uint64_t seed, uint64_t *out)
{
	uint64_t packed;
	uint32_t tag;

	if (out == NULL)
		return OBF_ERR_NULL;
	if (val > OBF_PAYLOAD_MASK)
		return OBF_ERR_RANGE;

	tag = compute_tag(val, seed);
	packed = ((uint64_t)tag << OBF_PAYLOAD_BITS) | obfuscate(val, seed);
	*out = packed;
	return OBF_OK;
}

int32_t decode(uint64_t encoded_val, uint64_t seed, uint64_t *out)
{
	uint32_t stored_tag;
	uint32_t expected_tag;
	uint64_t obscured;
	uint64_t val;

	if (out == NULL)
		return OBF_ERR_NULL;

	stored_tag = (uint32_t)(encoded_val >> OBF_PAYLOAD_BITS);
	obscured = encoded_val & OBF_PAYLOAD_MASK;
	val = deobfuscate(obscured, seed);
	expected_tag = compute_tag(val, seed);

	if (stored_tag != expected_tag)
		return OBF_ERR_VERIFY;

	*out = val;
	return OBF_OK;
}
