using System;
using System.Numerics;
using System.Runtime.CompilerServices;

namespace Pinta.Core;

/// <summary>
/// Encapsulates an <see cref="int" /> value
/// to be used as a seed in effects whose algorithms
/// have a degree of randomness
/// </summary>
public readonly struct RandomSeed
{
	public readonly int Value { get; }
	public RandomSeed (int value)
	{
		// Note: these range checks correspond to
		// the range of values generated by Random.Next()
		ArgumentOutOfRangeException.ThrowIfNegative (value);
		ArgumentOutOfRangeException.ThrowIfEqual (value, int.MaxValue);
		Value = value;
	}
	public override readonly bool Equals (object? obj)
	{
		if (obj is not RandomSeed other) return false;
		return Value == other.Value;
	}
	public override readonly int GetHashCode ()
		=> Value.GetHashCode ();

	public static bool operator == (RandomSeed left, RandomSeed right)
		=> left.Equals (right);

	public static bool operator != (RandomSeed left, RandomSeed right)
		=> !left.Equals (right);

	/// <summary>
	/// Produces a new random seed based on <see cref="Value"/>  and the specified region.
	/// This can be useful for tiled effects.
	/// </summary>
	public int GetValueForRegion (in RectangleI region)
	{
		// Note that HashCode.Combine() can't be used because it is random per-process and would
		// produce inconsistent results for unit tests.
		// This is the same implementation from HashCode.cs, but without the randomization.
		const uint PRIME_2 = 2246822519U;
		const uint PRIME_3 = 3266489917U;
		const uint PRIME_4 = 668265263U;

		[MethodImpl (MethodImplOptions.AggressiveInlining)]
		static uint MixFinal (uint hash)
		{
			hash ^= hash >> 15;
			hash *= PRIME_2;
			hash ^= hash >> 13;
			hash *= PRIME_3;
			hash ^= hash >> 16;
			return hash;
		}

		[MethodImpl (MethodImplOptions.AggressiveInlining)]
		static uint QueueRound (uint hash, uint queuedValue)
		{
			return BitOperations.RotateLeft (hash + queuedValue * PRIME_3, 17) * PRIME_4;
		}

		uint hash = 374761393U;
		hash += 12;

		hash = QueueRound (hash, (uint) Value);
		hash = QueueRound (hash, (uint) region.Left);
		hash = QueueRound (hash, (uint) region.Top);

		hash = MixFinal (hash);

		return (int) hash;
	}
}
