/**
* @license Apache-2.0
*
* Copyright (c) 2018 The Stdlib Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// Note: keep project includes in alphabetical order...
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "stdlib/random/base.h"
#include "stdlib/random/base/minstd.h"

// Forward declarations:
static inline int8_t next( struct BasePRNGObject *obj, uint64_t *out );
static inline int8_t normalized( struct BasePRNGObject *obj, double *out );
static inline void minstd_free( struct BasePRNGObject *obj );

// Define the LCG multiplier:
static const uint32_t A = 16807;

// Define the maximum signed 32-bit integer: 2147483647 => 0x7fffffff => 01111111111111111111111111111111
static const uint32_t MAX_INT32 = 0x7fffffff;

// Define the normalization constant:
static const double NORMALIZATION_CONSTANT = 2147483646.0; // MAX_INT32 - 1

/**
* MINSTD PRNG.
*
* @private
*/
static const struct BasePRNG minstd_prng = {
	"minstd",                                     // name
	(uint64_t)1,                                  // min
	(uint64_t)MAX_INT32-1,                        // max: (2^{31}-1) - 1
	0.0,                                          // min (normalized)
	(MAX_INT32-2) / NORMALIZATION_CONSTANT,       // max (normalized): (MAX-1)/MAX
	sizeof( stdlib_base_random_minstd_state_t ),  // state_size
	&next,                                        // next()
	&normalized,                                  // normalized()
	&minstd_free                                  // free()
};

/**
* Returns a pseudorandom integer.
*
* ## Notes
*
* -   The function returns `-1` if unable to generate a pseudorandom integer and `0` otherwise.
*
* @private
* @param obj  PRNG object
* @param out  output address
* @return     status code
*/
static inline int8_t next( struct BasePRNGObject *obj, uint64_t *out ) {
	if ( obj == NULL || obj->prng != &minstd_prng ) {
		return -1;
	}
	// Retrieve the state object:
	stdlib_base_random_minstd_state_t *so = (stdlib_base_random_minstd_state_t *)( obj->state );

	// Retrieve the current state:
	uint32_t state = so->state;

	// Explicitly cast to 64-bit to handle integer overflow:
	state = (A*(uint64_t)state) % MAX_INT32;

	// Update the PRNG state:
	so->state = state;

	// Set the output value:
	*out = (uint64_t)state;

	return 0;
}

/**
* Returns a pseudorandom double-precision floating-point number on the interval `[0,1)`.
*
* ## Notes
*
* -   The function returns `-1` if unable to generate a pseudorandom number and `0` otherwise.
*
* @private
* @param obj  PRNG object
* @param out  output address
* @return     status code
*/
static inline int8_t normalized( struct BasePRNGObject *obj, double *out ) {
	uint64_t state;
	int8_t status = next( obj, &state );
	if ( status != 0 ) {
		return -1;
	}
	// Note: casting `state` to a double here is fine, as `state` will never exceed the maximum "safe" double-precision floating-point number:
	*out = ((double)state-1.0) / NORMALIZATION_CONSTANT;

	return 0;
}

/**
* Frees a PRNG's allocated memory.
*
* @private
* @param obj  PRNG object
*/
static inline void minstd_free( struct BasePRNGObject *obj ) {
	if ( obj == NULL || obj->prng != &minstd_prng ) {
		return;
	}
	free( obj->state );
	free( obj );
}

/**
* Returns a pointer to a dynamically allocated PRNG.
*
* ## Notes
*
* -   The user is responsible for freeing the allocated memory.
* -   A provided `seed` is mapped to the interval `[1,2147483646]`.
*
* @param seed  PRNG seed
* @return      pointer to a dynamically allocated PRNG or, if unable to allocate memory, a null pointer
*
* @example
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
* #include "stdlib/random/base.h"
* #include "stdlib/random/base/minstd.h"
*
* // Create a PRNG:
* struct BasePRNGObject *obj = stdlib_base_random_minstd_allocate( 12345 );
* if ( obj == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* uint64_t r;
* int8_t status = obj->prng->next( obj, &r );
* if ( status != 0 ) {
*     fprintf( stderr, "Unexpected result.\n" );
*     exit( 1 );
* }
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* // Free allocated memory:
* stdlib_base_random_minstd_free( obj );
*/
struct BasePRNGObject * stdlib_base_random_minstd_allocate( const int32_t seed ) {
	uint32_t iseed;

	struct BasePRNGObject *obj = malloc( sizeof( struct BasePRNGObject ) );
	if ( obj == NULL ) {
		return NULL;
	}
	stdlib_base_random_minstd_state_t *state = malloc( sizeof( stdlib_base_random_minstd_state_t ) );
	if ( state == NULL ) {
		free( obj ); // prevent memory leaks
		return NULL;
	}
	// Ensure that the provided seed is within allowed bounds...
	if ( seed == 0 ) {
		iseed = 1;
	} else if ( seed == MAX_INT32 ) {
		iseed = MAX_INT32 - 1;
	} else if ( seed < 0 ) {
		iseed = -seed;
	} else {
		iseed = seed;
	}
	state->seed = (uint32_t)iseed;
	state->state = (uint32_t)iseed;

	obj->prng = &minstd_prng;
	obj->state = state;

	return obj;
}

/**
* Frees a PRNG's allocated memory.
*
* @param obj  PRNG object
*
* @example
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
* #include "stdlib/random/base.h"
* #include "stdlib/random/base/minstd.h"
*
* // Create a PRNG:
* struct BasePRNGObject *obj = stdlib_base_random_minstd_allocate( 12345 );
* if ( obj == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* uint64_t r;
* int8_t status = obj->prng->next( obj, &r );
* if ( status != 0 ) {
*     fprintf( stderr, "Unexpected result.\n" );
*     exit( 1 );
* }
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* // Free allocated memory:
* stdlib_base_random_minstd_free( obj );
*/
void stdlib_base_random_minstd_free( struct BasePRNGObject *obj ) {
	if ( obj == NULL || obj->prng != &minstd_prng ) {
		return;
	}
	obj->prng->free( obj );
}

/**
* Returns a PRNG seed.
*
* ## Notes
*
* -   The function returns `-1` if unable to resolve a PRNG seed and `0` otherwise.
*
* @param obj  PRNG object
* @param out  output address
* @return     status code
*
* @example
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
* #include "stdlib/random/base.h"
* #include "stdlib/random/base/minstd.h"
*
* // Create a PRNG:
* struct BasePRNGObject *obj = stdlib_base_random_minstd_allocate( 12345 );
* if ( obj == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* int32_t seed;
* int8_t status = stdlib_base_random_minstd_seed( obj, &seed );
* if ( status != 0 ) {
*     fprintf( stderr, "Error encountered when attempting to retrieve the PRNG seed.\n" );
*     exit( 1 );
* }
*
* // Use the seed to, e.g., create another PRNG which will generate the same sequence...
*
* // Free allocated memory:
* stdlib_base_random_minstd_free( obj );
*/
int8_t stdlib_base_random_minstd_seed( const struct BasePRNGObject *obj, int32_t *out ) {
	if ( obj == NULL || obj->prng != &minstd_prng ) {
		return -1;
	}
	// Retrieve the MINSTD state object:
	const stdlib_base_random_minstd_state_t *state = (stdlib_base_random_minstd_state_t *)( obj->state );

	// Set the output value:
	*out = (int32_t)( state->seed );

	return 0;
}

/**
* Returns a **copy** of the current PRNG state.
*
* ## Notes
*
* -   The user is responsible for freeing the allocated memory.
*
* @param obj  PRNG object
* @return     pointer to a copy of the PRNG's internal state or, if unable to allocate memory, a null pointer
*
* @example
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
* #include "stdlib/random/base.h"
* #include "stdlib/random/base/minstd.h"
*
* // Create a PRNG:
* struct BasePRNGObject *obj = stdlib_base_random_minstd_allocate( 12345 );
* if ( obj == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* void *state = stdlib_base_random_minstd_state( obj );
* if ( state == NULL ) {
*     fprintf( stderr, "Unable to retrieve PRNG state.\n" );
*     exit( 1 );
* }
*
* // Use the captured state to, e.g., sync another PRNG or to reset a PRNG to a particular state in order to "replay" generated values at a later point in time...
*
* // Free allocated memory:
* stdlib_base_random_minstd_free( obj );
* free( state );
*/
void * stdlib_base_random_minstd_state( const struct BasePRNGObject *obj ) {
	if ( obj == NULL || obj->prng != &minstd_prng ) {
		return NULL;
	}
	void *state = malloc( obj->prng->state_size );
	if ( state == NULL ) {
		return NULL;
	}
	memcpy( state, obj->state, obj->prng->state_size );
	return state;
}

/**
* Sets the PRNG state.
*
* ## Notes
*
* -   The function returns `-1` if unable to set a PRNG state and `0` otherwise.
*
* @param obj    PRNG object
* @param state  state
* @return       status code
*
* @example
* #include <stdlib.h>
* #include <stdio.h>
* #include <stdint.h>
* #include "stdlib/random/base.h"
* #include "stdlib/random/base/minstd.h"
*
* // Create a PRNG:
* struct BasePRNGObject *obj = stdlib_base_random_minstd_allocate( 12345 );
* if ( obj == NULL ) {
*     fprintf( stderr, "Error allocating memory.\n" );
*     exit( 1 );
* }
*
* uint64_t r;
* int8_t status = obj->prng->next( obj, &r );
* if ( status != 0 ) {
*     fprintf( stderr, "Unexpected result.\n" );
*     exit( 1 );
* }
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* // Retrieve the current PRNG state...
* void *state = stdlib_base_random_minstd_state( obj );
* if ( state == NULL ) {
*     fprintf( stderr, "Error encountered when attempting to retrieve PRNG state.\n" );
*     exit( 1 );
* }
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* // Reset the PRNG to a previous state...
* status = stdlib_base_random_minstd_set( obj, state );
* if ( status != 0 ) {
*     fprintf( stderr, "Error encountered when attempting to set PRNG state.\n" );
*     exit( 1 );
* }
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* status = obj->prng->next( obj, &r );
*
* // ...
*
* // Free allocated memory:
* stdlib_base_random_minstd_free( obj );
* free( state );
*/
int8_t stdlib_base_random_minstd_set( struct BasePRNGObject *obj, const void *state ) {
	if ( obj == NULL || state == NULL || obj->prng != &minstd_prng ) {
		return -1;
	}
	memcpy( obj->state, state, obj->prng->state_size );
	return 0;
}
