// xoshiro256** — David Blackman & Sebastiano Vigna, 2018
// Reference implementation: https://prng.di.unimi.it/xoshiro256starstar.c
//
// To the extent possible under law, the authors have dedicated all copyright
// and related and neighboring rights to this software to the public domain
// worldwide. This software is distributed without any warranty.
// See <http://creativecommons.org/publicdomain/zero/1.0/>.

#pragma once

#include <stdint.h>

#include <SDL3/SDL_assert.h>

struct crRandomState
{
    uint64_t s[ 4 ];
};

class crRandom
{
private:
    uint64_t _state[ 4 ];

public:
    crRandom() = default;

    // (Re)seeds using splitmix64 to expand a 64-bit value into the 4x64-bit state.
    // splitmix64: Sebastiano Vigna, public domain — https://prng.di.unimi.it/splitmix64.c
    void Seed( uint64_t s )
    {
        for( int32_t i = 0; i < 4; i++ )
        {
            s          += 0x9e3779b97f4a7c15ULL;
            uint64_t z  = s;
            z = ( z ^ ( z >> 30 ) ) * 0xbf58476d1ce4e5b9ULL;
            z = ( z ^ ( z >> 27 ) ) * 0x94d049bb133111ebULL;
            _state[ i ]   = z ^ ( z >> 31 );
        }
    }

    uint64_t NextUint64()
    {
        const uint64_t result = Rotl( _state[ 1 ] * 5, 7 ) * 9;
        const uint64_t t      = ( _state[ 1 ] << 17 );

        _state[ 2 ] ^= _state[ 0 ];
        _state[ 3 ] ^= _state[ 1 ];
        _state[ 1 ] ^= _state[ 2 ];
        _state[ 0 ] ^= _state[ 3 ];
        _state[ 2 ] ^= t;
        _state[ 3 ]  = Rotl( _state[ 3 ], 45 );

        return result;
    }

    // All bits of xoshiro256** output are high quality — the lower 32 are safe to truncate to.
    uint32_t NextUint32()
    {
        return ( uint32_t )NextUint64();
    }

    // [0, bound) — Lemire multiply-shift: fast, negligible bias.
    uint32_t NextUint32( uint32_t bound )
    {
        return static_cast<uint32_t>( ( static_cast<uint64_t>( NextUint32() ) * static_cast<uint64_t>( bound ) ) >> 32 );
    }

    // Requires maxExclusive > minInclusive.
    int32_t NextInt32( int32_t minInclusive, int32_t maxExclusive )
    {
        SDL_assert( maxExclusive > minInclusive );

        const uint32_t range  = static_cast<uint32_t>( maxExclusive ) - static_cast<uint32_t>( minInclusive );
        const uint32_t offset = NextUint32( range );
        return static_cast<int32_t>( static_cast<uint32_t>( minInclusive ) + offset );
    }

    // [0, 1) — uses the upper 24 bits to match float mantissa precision (2^24).
    float NextFloat32()
    {
        return ( float )( NextUint64() >> 40 ) * ( 1.0f / 16777216.0f );
    }

    // [0, max)
    float NextFloat32( float max )
    {
        return NextFloat32() * max;
    }

    // [min, max)
    float NextFloat32( float min, float max )
    {
        return min + ( ( max - min ) * NextFloat32() );
    }

    // 50/50 boolean (uses the top output bit).
    bool NextBool()
    {
        return ( NextUint64() >> 63 ) != 0;
    }

    // probability expected in [0, 1]
    bool NextBool( float probability )
    {
        return NextFloat32() < probability;
    }

    // Snapshot / restore the full generator state — for lockstep and replay.
    crRandomState GetState() const
    {
        crRandomState out;
        for( int32_t i = 0; i < 4; ++i )
            out.s[ i ] = _state[ i ];
        return out;
    }

    void SetState( crRandomState state )
    {
        for( int32_t i = 0; i < 4; ++i )
            _state[ i ] = state.s[ i ];
    }

    // Advances the state by 2^128 draws — yields a non-overlapping subsequence for an independent stream.
    void Jump()
    {
        static const uint64_t JUMP[ 4 ] =
        {
            0x180ec6d33cfd0abaULL, 0xd5a61266f0c9392cULL, 0xa9582618e03fc9aaULL, 0x39abdc4529b1661cULL
        };

        uint64_t s0 = 0;
        uint64_t s1 = 0;
        uint64_t s2 = 0;
        uint64_t s3 = 0;

        for( int32_t i = 0; i < 4; ++i )
        {
            for( int32_t b = 0; b < 64; ++b )
            {
                if( ( JUMP[ i ] & ( static_cast<uint64_t>( 1 ) << b ) ) != 0 )
                {
                    s0 ^= _state[ 0 ];
                    s1 ^= _state[ 1 ];
                    s2 ^= _state[ 2 ];
                    s3 ^= _state[ 3 ];
                }
                NextUint64();
            }
        }

        _state[ 0 ] = s0;
        _state[ 1 ] = s1;
        _state[ 2 ] = s2;
        _state[ 3 ] = s3;
    }

private:
    static uint64_t Rotl( uint64_t x, int32_t k )
    {
        return ( x << k ) | ( x >> ( 64 - k ) );
    }
};
