#include "Random.hpp"
#include <bit>
#include <cstdint>

TSE::Random::Random(uint seed)
{
    state = static_cast<uint>(static_cast<std::uint32_t>(seed));
}

TSE::uint TSE::Random::nextUInt(uint minInc, uint maxInc)
{
    constexpr std::uint32_t multiplier = 1664525u;
    constexpr std::uint32_t increment = 1013904223u;

    std::uint32_t nextState = static_cast<std::uint32_t>(state);
    nextState = nextState * multiplier + increment;
    state = static_cast<uint>(nextState);

    if(minInc == 0u && maxInc == 0u)
    {
        return state;
    }

    if(minInc > maxInc)
    {
        uint temp = minInc;
        minInc = maxInc;
        maxInc = temp;
    }

    const std::uint64_t span = static_cast<std::uint64_t>(maxInc) - static_cast<std::uint64_t>(minInc) + 1ull;
    const std::uint64_t offset = static_cast<std::uint64_t>(nextState) % span;
    return static_cast<uint>(static_cast<std::uint64_t>(minInc) + offset);
}

int TSE::Random::nextInt(int minInc, int maxInc)
{
    const std::uint32_t bits = static_cast<std::uint32_t>(nextUInt());

    if(minInc == 0 && maxInc == 0)
    {
        return static_cast<int>(std::bit_cast<std::int32_t>(bits));
    }

    if(minInc > maxInc)
    {
        int temp = minInc;
        minInc = maxInc;
        maxInc = temp;
    }

    const std::int64_t span = static_cast<std::int64_t>(maxInc) - static_cast<std::int64_t>(minInc) + 1ll;
    const std::uint64_t offset = static_cast<std::uint64_t>(bits) % static_cast<std::uint64_t>(span);
    const std::int64_t value = static_cast<std::int64_t>(minInc) + static_cast<std::int64_t>(offset);
    return static_cast<int>(value);
}

short TSE::Random::nextShort(short minInc, short maxInc)
{
    const std::uint32_t bits = static_cast<std::uint32_t>(nextUInt());
    const std::uint16_t lowerBits = static_cast<std::uint16_t>(bits);

    if(minInc == 0 && maxInc == 0)
    {
        return static_cast<short>(std::bit_cast<std::int16_t>(lowerBits));
    }

    if(minInc > maxInc)
    {
        short temp = minInc;
        minInc = maxInc;
        maxInc = temp;
    }

    const std::int32_t span = static_cast<std::int32_t>(maxInc) - static_cast<std::int32_t>(minInc) + 1;
    const std::uint32_t offset = static_cast<std::uint32_t>(lowerBits) % static_cast<std::uint32_t>(span);
    const std::int32_t value = static_cast<std::int32_t>(minInc) + static_cast<std::int32_t>(offset);
    return static_cast<short>(value);
}

TSE::byte TSE::Random::nextByte(byte minInc, byte maxInc)
{
    const std::uint32_t bits = static_cast<std::uint32_t>(nextUInt());
    const std::uint8_t lowerBits = static_cast<std::uint8_t>(bits);

    if(minInc == 0 && maxInc == 0)
    {
        return static_cast<byte>(lowerBits);
    }

    if(minInc > maxInc)
    {
        byte temp = minInc;
        minInc = maxInc;
        maxInc = temp;
    }

    const std::uint16_t span = static_cast<std::uint16_t>(maxInc) - static_cast<std::uint16_t>(minInc) + 1u;
    const std::uint16_t offset = static_cast<std::uint16_t>(lowerBits) % span;
    return static_cast<byte>(static_cast<std::uint16_t>(minInc) + offset);
}

float TSE::Random::nextFloat01()
{
    constexpr float invRange = 1.0f / 4294967296.0f;
    return static_cast<float>(static_cast<std::uint32_t>(nextUInt())) * invRange;
}
