# -----------------------------------------------------------------------------
# Creation and seeding
# -----------------------------------------------------------------------------

"""
    rng_create()
    rng_create(engine; bitexact=false, full_mantissa=false)

Create a new RNG using the selected engine. The default engine is `x256++simd`. The
`engine` argument is a string naming the RNG engine to use. Call `Randompack.engines()` to
list available engine names. If `bitexact` is true, the RNG uses bitexact log/exp for
distributions that rely on them. If `full_mantissa` is true, uniform floating draws use
53-bit mantissas for `Float64` and 24-bit mantissas for `Float32`.

Use `Randompack.engines()` to list available names.

# Examples
```julia
rng = rng_create()
rng = rng_create("pcg64")
rng = rng_create("pcg64"; bitexact=true)  # make samples bit-identical across platforms (x==y true)
rng = rng_create("pcg64"; full_mantissa=true)
```

# See also
- Randompack.engines
- rng_seed!
"""
function rng_create(engine::AbstractString; bitexact::Bool=false,
                    full_mantissa::Bool=false)
  p = ccall(_sym(:randompack_create), RNGPtr, (Cstring,), engine)
  if p == C_NULL
    throw(ErrorException("unknown RNG engine '$engine' (check spelling)"))
  end
  err = _last_error(p)
  if err !== nothing
    _free(p)
    throw(ErrorException(err))
  end
  if bitexact
    ok = ccall(_sym(:randompack_bitexact), Bool, (RNGPtr, Bool), p, true)
    _check_ok(ok, p, "randompack_bitexact failed")
  end
  if full_mantissa
    ok = ccall(_sym(:randompack_full_mantissa), Bool, (RNGPtr, Bool), p, true)
    _check_ok(ok, p, "randompack_full_mantissa failed")
  end
  rng = RNG(p)
  finalizer(rng) do r
    _free(r.ptr)
    r.ptr = C_NULL
  end
  return rng
end

rng_create(; bitexact::Bool=false, full_mantissa::Bool=false) =
  rng_create("x256++simd"; bitexact=bitexact, full_mantissa=full_mantissa)

"""
    rng_seed!(rng, seed)
    rng_seed!(rng, seed; spawn_key)

Seed an RNG with the given integer `seed`. The seed value must fit in a 32-bit signed
integer. The optional `spawn_key` argument is an integer vector used to derive independent
streams. If provided, its values are range-checked and converted to `UInt32` before
calling the C library. Omitting `spawn_key` is equivalent to passing an empty spawn_key.

# Examples
```julia
rng_seed!(rng, 123)
rng_seed!(rng, 123; spawn_key=[1, 2])
```

# See also
- Randompack.serialize
- Randompack.deserialize!
- Randompack.randomize!
"""
function rng_seed!(rng::RNG, seed::Integer; spawn_key=nothing)
  rng.ptr == C_NULL && throw(ErrorException("RNG pointer is NULL"))
  if seed < typemin(Cint) || seed > typemax(Cint)
    throw(ArgumentError("seed out of range for C int (must fit in Int32)"))
  end
  s = Cint(seed)
  if spawn_key === nothing
    ok = ccall(_sym(:randompack_seed), Bool,
               (Cint, Ptr{UInt32}, Cint, RNGPtr),
               s, Ptr{UInt32}(C_NULL), Cint(0), rng.ptr)
    _check_ok(ok, rng.ptr, "randompack_seed failed")
    return
  end
  sk = if spawn_key isa Vector{UInt32}
    spawn_key
  elseif spawn_key isa AbstractVector{<:Integer}
    _u32_vec_from_ints(spawn_key)
  else
    throw(ArgumentError("spawn_key must be an integer vector"))
  end
  n = length(sk)
  if n == 0
    ok = ccall(_sym(:randompack_seed), Bool,
               (Cint, Ptr{UInt32}, Cint, RNGPtr),
               s, Ptr{UInt32}(C_NULL), Cint(0), rng.ptr)
  else
    if n > typemax(Cint)
      throw(ArgumentError("spawn_key too long"))
    end
    ok = GC.@preserve sk begin
      ccall(_sym(:randompack_seed), Bool,
            (Cint, Ptr{UInt32}, Cint, RNGPtr),
            s, pointer(sk), Cint(n), rng.ptr)
    end
  end
  _check_ok(ok, rng.ptr, "randompack_seed failed")
  return
end
