using System;

namespace MagmaEngine.Math;

/// <summary>
///     Implements a XorShift* PRNG, with 64 bits of internal state.
///     See http://en.wikipedia.org/wiki/Xorshift
/// </summary>
public struct SIntRandom
{
    public SIntRandom(ulong seed)
    {
        if (seed == 0)
            throw new InvalidOperationException("Seed needs to be bigger than zero.");

        m_Seed = seed;
    }

    public ulong Next()
    {
        m_Seed ^= m_Seed >> 12;
        m_Seed ^= m_Seed << 25;
        m_Seed ^= m_Seed >> 27;
        return m_Seed * 2685821657736338717UL; // multiplier taken from wikipedia article on XorShift PRNGs
    }

    public ulong Next(ulong upperLimit)
    {
        if (upperLimit == 0)
        {
            return 0;
        }

        return Next() % upperLimit;
    }

    public FixPoint16 NextFixPoint16()
    {
        return new() { m_Value = (int)(Next() & 0xffffUL) };
    }

    public int RandomizedRound(FixPoint16 value)
    {
        int ret = value.ToIntFloor();
        if (NextFixPoint16() < FixPoint16.Fract(value))
            ret++;

        return ret;
    }

    public uint Next(uint upperLimit)
    {
        if (upperLimit == 0)
        {
            return 0;
        }

        return (uint)(Next() % upperLimit);
    }

    public uint Next(uint lowerLimit, uint upperLimit)
    {
        if (upperLimit == 0)
        {
            return 0;
        }

        if (lowerLimit == upperLimit)
        {
            return lowerLimit;
        }

        return lowerLimit + (uint)(Next() % (upperLimit - lowerLimit));
    }

    public int Next(int upperLimit)
    {
        if (upperLimit == 0)
        {
            return 0;
        }

        return (int)(Next() % (uint)(upperLimit & 0x7fffffff));
    }

    public int Next(int lowerLimit, int upperLimit)
    {
        if (upperLimit == 0)
        {
            return 0;
        }

        if (lowerLimit == upperLimit)
        {
            return lowerLimit;
        }

        return lowerLimit + (int)(Next() % (uint)((upperLimit - lowerLimit) & 0x7fffffff));
    }

    public double NextDouble()
    {
        return Next(int.MaxValue) * (1.0 / int.MaxValue);
    }

    public float NextSingle()
    {
        return (float)NextDouble();
    }

    public override readonly string ToString()
    {
        return $"0x{m_Seed:X}";
    }

    public readonly ulong Seed => m_Seed;

    private ulong m_Seed;
}