// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright (C) 2026 Andrea Mazzoleni

/*
 * Derivative work from MuseAir 0.4-rc4 in the "Bfast" configuration
 *
 * This version extends the original MuseAirLoong to accept a full 128-bit seed.
 *
 * Note: The original algorithm's behavior can be perfectly replicated by
 * setting the low and high part of the seed to the same 64-bit value.
 *
 * https://github.com/eternal-io/museair
 */

/*
 * MuseAir hash algorithm itself and its reference implementation (this file)
 * by  K--Aethiax  are released into the public domain under the CC0 1.0 license.
 * To view a copy of this license, visit: https://creativecommons.org/publicdomain/zero/1.0/
 */

#define MUSEAIR_ALGORITHM_VERSION "0.4-rc4"

#define u64x(N) (N * 8)

/* AiryAi(0) fractional part calculated by Y-Cruncher */
static const uint64_t MUSEAIR_CONSTANT[7] = {
	0x5ae31e589c56e17aULL,
	0x96d7bb04e64f6da9ULL,
	0x7ab1006b26f9eb64ULL,
	0x21233394220b8457ULL,
	0x047cb9557c9f3b43ULL,
	0xd24f2590c0bcee28ULL,
	0x33ea8f71bb6016d8ULL
};

#define mult64_128(l, h, a, b) \
	do { \
		l = a; \
		h = b; \
		util_mum(&l, &h); \
	} while (0)

#define likely(a) tommy_likely(a)
#define unlikely(a) tommy_unlikely(a)

static void MuseAirLoong(const void* bytes, size_t len, const uint8_t* seed, uint8_t* out)
{
	const uint8_t* p = bytes;
	size_t q = len;

	/* EXTENSION: This algo doesn't support shorter lengths */
	assert(len >= 32);

	uint64_t i, j, k;

	uint64_t lo0, lo1, lo2, lo3, lo4, lo5 = MUSEAIR_CONSTANT[6];
	uint64_t hi0, hi1, hi2, hi3, hi4, hi5;

	/*
	 * EXTENSION: Initialize primary state with 128-bit seed (seed0 and seed1)
	 * Ensures both halves influence the hash.
	 *
	 * Original code was:
	 * uint64_t state[6] = {MUSEAIR_CONSTANT[0] + seed, MUSEAIR_CONSTANT[1] - seed, MUSEAIR_CONSTANT[2] ^ seed,
	 *                      MUSEAIR_CONSTANT[3] + seed, MUSEAIR_CONSTANT[4] - seed, MUSEAIR_CONSTANT[5] ^ seed};
	 */
	uint64_t seed0 = util_read64(seed);
	uint64_t seed1 = util_read64(seed + 8);
	uint64_t state[6] = { MUSEAIR_CONSTANT[0] + seed0, MUSEAIR_CONSTANT[1] - seed1, MUSEAIR_CONSTANT[2] ^ seed0,
			      MUSEAIR_CONSTANT[3] + seed1, MUSEAIR_CONSTANT[4] - seed0, MUSEAIR_CONSTANT[5] ^ seed1 };

	if (unlikely(q > u64x(12))) {
		do {
			state[0] ^= util_read64(p + u64x(0));
			state[1] ^= util_read64(p + u64x(1));
			mult64_128(lo0, hi0, state[0], state[1]);
			state[0] = lo5 ^ hi0;

			state[1] ^= util_read64(p + u64x(2));
			state[2] ^= util_read64(p + u64x(3));
			mult64_128(lo1, hi1, state[1], state[2]);
			state[1] = lo0 ^ hi1;

			state[2] ^= util_read64(p + u64x(4));
			state[3] ^= util_read64(p + u64x(5));
			mult64_128(lo2, hi2, state[2], state[3]);
			state[2] = lo1 ^ hi2;

			state[3] ^= util_read64(p + u64x(6));
			state[4] ^= util_read64(p + u64x(7));
			mult64_128(lo3, hi3, state[3], state[4]);
			state[3] = lo2 ^ hi3;

			state[4] ^= util_read64(p + u64x(8));
			state[5] ^= util_read64(p + u64x(9));
			mult64_128(lo4, hi4, state[4], state[5]);
			state[4] = lo3 ^ hi4;

			state[5] ^= util_read64(p + u64x(10));
			state[0] ^= util_read64(p + u64x(11));
			mult64_128(lo5, hi5, state[5], state[0]);
			state[5] = lo4 ^ hi5;

			p += u64x(12);
			q -= u64x(12);

		} while (likely(q > u64x(12)));

		state[0] ^= lo5;
	}

	lo0 = 0, lo1 = 0, lo2 = 0, lo3 = 0, lo4 = 0, lo5 = 0;
	hi0 = 0, hi1 = 0, hi2 = 0, hi3 = 0, hi4 = 0, hi5 = 0;

	if (likely(q > u64x(4))) {
		state[0] ^= util_read64(p + u64x(0));
		state[1] ^= util_read64(p + u64x(1));
		mult64_128(lo0, hi0, state[0], state[1]);

		if (likely(q > u64x(6))) {
			state[1] ^= util_read64(p + u64x(2));
			state[2] ^= util_read64(p + u64x(3));
			mult64_128(lo1, hi1, state[1], state[2]);

			if (likely(q > u64x(8))) {
				state[2] ^= util_read64(p + u64x(4));
				state[3] ^= util_read64(p + u64x(5));
				mult64_128(lo2, hi2, state[2], state[3]);

				if (likely(q > u64x(10))) {
					state[3] ^= util_read64(p + u64x(6));
					state[4] ^= util_read64(p + u64x(7));
					mult64_128(lo3, hi3, state[3], state[4]);
				}
			}
		}
	}

	state[4] ^= util_read64(p + q - u64x(4));
	state[5] ^= util_read64(p + q - u64x(3));
	mult64_128(lo4, hi4, state[4], state[5]);

	state[5] ^= util_read64(p + q - u64x(2));
	state[0] ^= util_read64(p + q - u64x(1));
	mult64_128(lo5, hi5, state[5], state[0]);

	i = state[0] - state[1];
	j = state[2] - state[3];
	k = state[4] - state[5];

	int rot = len & 63;
	i = util_rotl64(i, rot);
	j = util_rotr64(j, rot);
	k ^= len;

	i += lo3 ^ hi3 ^ lo4 ^ hi4;
	j += lo5 ^ hi5 ^ lo0 ^ hi0;
	k += lo1 ^ hi1 ^ lo2 ^ hi2;

	mult64_128(lo0, hi0, i, j);
	mult64_128(lo1, hi1, j, k);
	mult64_128(lo2, hi2, k, i);

	util_write64(out, lo0 ^ lo1 ^ hi2);
	util_write64(out + 8, hi0 ^ hi1 ^ lo2);
}

