/*-------------------------------------------------------------------------
 *
 * pseudorandomfuncs.c
 *	  Functions giving SQL access to a pseudorandom number generator.
 *
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/pseudorandomfuncs.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <math.h>

#include "common/pg_prng.h"
#include "miscadmin.h"
#include "utils/fmgrprotos.h"
#include "utils/numeric.h"
#include "utils/timestamp.h"

/* Shared PRNG state used by all the random functions */
static pg_prng_state prng_state;
static bool prng_seed_set = false;

/*
 * initialize_prng() -
 *
 *	Initialize (seed) the PRNG, if not done yet in this process.
 */
static void
initialize_prng(void)
{
	if (unlikely(!prng_seed_set))
	{
		/*
		 * If possible, seed the PRNG using high-quality random bits. Should
		 * that fail for some reason, we fall back on a lower-quality seed
		 * based on current time and PID.
		 */
		if (unlikely(!pg_prng_strong_seed(&prng_state)))
		{
			TimestampTz now = GetCurrentTimestamp();
			uint64		iseed;

			/* Mix the PID with the most predictable bits of the timestamp */
			iseed = (uint64) now ^ ((uint64) MyProcPid << 32);
			pg_prng_seed(&prng_state, iseed);
		}
		prng_seed_set = true;
	}
}

/*
 * setseed() -
 *
 *	Seed the PRNG from a specified value in the range [-1.0, 1.0].
 */
Datum
setseed(PG_FUNCTION_ARGS)
{
	float8		seed = PG_GETARG_FLOAT8(0);

	if (seed < -1 || seed > 1 || isnan(seed))
		ereport(ERROR,
				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				errmsg("setseed parameter %g is out of allowed range [-1,1]",
					   seed));

	pg_prng_fseed(&prng_state, seed);
	prng_seed_set = true;

	PG_RETURN_VOID();
}

/*
 * drandom() -
 *
 *	Returns a random number chosen uniformly in the range [0.0, 1.0).
 */
Datum
drandom(PG_FUNCTION_ARGS)
{
	float8		result;

	initialize_prng();

	/* pg_prng_double produces desired result range [0.0, 1.0) */
	result = pg_prng_double(&prng_state);

	PG_RETURN_FLOAT8(result);
}

/*
 * drandom_normal() -
 *
 *	Returns a random number from a normal distribution.
 */
Datum
drandom_normal(PG_FUNCTION_ARGS)
{
	float8		mean = PG_GETARG_FLOAT8(0);
	float8		stddev = PG_GETARG_FLOAT8(1);
	float8		result,
				z;

	initialize_prng();

	/* Get random value from standard normal(mean = 0.0, stddev = 1.0) */
	z = pg_prng_double_normal(&prng_state);
	/* Transform the normal standard variable (z) */
	/* using the target normal distribution parameters */
	result = (stddev * z) + mean;

	PG_RETURN_FLOAT8(result);
}

/*
 * int4random() -
 *
 *	Returns a random 32-bit integer chosen uniformly in the specified range.
 */
Datum
int4random(PG_FUNCTION_ARGS)
{
	int32		rmin = PG_GETARG_INT32(0);
	int32		rmax = PG_GETARG_INT32(1);
	int32		result;

	if (rmin > rmax)
		ereport(ERROR,
				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				errmsg("lower bound must be less than or equal to upper bound"));

	initialize_prng();

	result = (int32) pg_prng_int64_range(&prng_state, rmin, rmax);

	PG_RETURN_INT32(result);
}

/*
 * int8random() -
 *
 *	Returns a random 64-bit integer chosen uniformly in the specified range.
 */
Datum
int8random(PG_FUNCTION_ARGS)
{
	int64		rmin = PG_GETARG_INT64(0);
	int64		rmax = PG_GETARG_INT64(1);
	int64		result;

	if (rmin > rmax)
		ereport(ERROR,
				errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				errmsg("lower bound must be less than or equal to upper bound"));

	initialize_prng();

	result = pg_prng_int64_range(&prng_state, rmin, rmax);

	PG_RETURN_INT64(result);
}

/*
 * numeric_random() -
 *
 *	Returns a random numeric value chosen uniformly in the specified range.
 */
Datum
numeric_random(PG_FUNCTION_ARGS)
{
	Numeric		rmin = PG_GETARG_NUMERIC(0);
	Numeric		rmax = PG_GETARG_NUMERIC(1);
	Numeric		result;

	initialize_prng();

	result = random_numeric(&prng_state, rmin, rmax);

	PG_RETURN_NUMERIC(result);
}
