/**
* @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 <math.h>
#include "stdlib/random/base/shared.h"

/**
* Frees a PRNG's allocated memory.
*
* @param obj  PRNG object
*/
void stdlib_base_prng_free( struct BasePRNGObject *obj ) {
	if ( obj == NULL ) {
		return;
	}
	obj->prng->free( obj );
}

/**
* Returns a pseudorandom integer.
*
* ## Notes
*
* -   The function returns `0` if provided a `NULL` pointer.
*
* @param obj  PRNG object
* @return     pseudorandom integer
*/
uint64_t stdlib_base_prng_next( struct BasePRNGObject *obj ) {
	if ( obj == NULL ) {
		return 0;
	}
	uint64_t v;
	obj->prng->next( obj, &v );
	return v;
}

/**
* Returns a pseudorandom double-precision floating-point number on the interval `[0,1)`.
*
* ## Notes
*
* -   The function returns `NAN` if provided a `NULL` pointer.
*
* @param obj  PRNG object
* @return     pseudorandom number
*/
double stdlib_base_prng_normalized( struct BasePRNGObject *obj ) {
	if ( obj == NULL ) {
		return NAN;
	}
	double v;
	obj->prng->normalized( obj, &v );
	return v;
}

/**
* Returns a PRNG name.
*
* @param obj  PRNG object
* @return     PRNG name
*/
const char * stdlib_base_prng_name( const struct BasePRNGObject *obj ) {
	return obj->prng->name;
}

/**
* Returns the minimum possible integer value generated by a provided PRNG.
*
* @param obj  PRNG object
* @return     minimum possible integer value
*/
uint64_t stdlib_base_prng_min( const struct BasePRNGObject *obj ) {
	return (uint64_t)( obj->prng->min );
}

/**
* Returns the maximum possible integer value generated by a provided PRNG.
*
* @param obj  PRNG object
* @return     maximum possible integer value
*/
uint64_t stdlib_base_prng_max( const struct BasePRNGObject *obj ) {
	return (uint64_t)( obj->prng->max );
}

/**
* Returns the minimum possible double-precision floating-point number generated by a provided PRNG.
*
* @param obj  PRNG object
* @return     minimum possible double
*/
double stdlib_base_prng_normalized_min( const struct BasePRNGObject *obj ) {
	return (double)( obj->prng->normalized_min );
}

/**
* Returns the maximum possible double-precision floating-point number generated by a provided PRNG.
*
* @param obj  PRNG object
* @return     maximum possible double
*/
double stdlib_base_prng_normalized_max( const struct BasePRNGObject *obj ) {
	return (double)( obj->prng->normalized_max );
}

/**
* Returns the size of a provided PRNG's internal state.
*
* @param obj  PRNG object
* @return     state size
*/
size_t stdlib_base_prng_state_size( const struct BasePRNGObject *obj ) {
	return (size_t)( obj->prng->state_size );
}

/**
* Returns a copy of a PRNG's internal state.
*
* ## Notes
*
* -   The user is responsible for freeing the allocated memory.
*
* @param obj  PRNG object
* @return     pointer to a copy of a PRNG's internal state or, if unable to allocate memory, a null pointer
*/
void * stdlib_base_prng_state( const struct BasePRNGObject *obj ) {
	if ( obj == NULL ) {
		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 a PRNG's 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
*/
int8_t stdlib_base_prng_set( struct BasePRNGObject *obj, const void *vstate ) {
	if ( obj == NULL || vstate == NULL ) {
		return -1;
	}
	memcpy( obj->state, vstate, obj->prng->state_size );
	return 0;
}

/**
* Copies a PRNG.
*
* @param src  source PRNG object
* @return     pointer to a dynamically allocated PRNG or, if unable to allocate memory, a null pointer
*/
struct BasePRNGObject * stdlib_base_prng_copy( const struct BasePRNGObject *src ) {
	struct BasePRNGObject *out = (struct BasePRNGObject *)malloc( sizeof( struct BasePRNGObject ) );
	if ( out == NULL ) {
		return NULL;
	}
	// Allocate memory for the PRNG state...
	out->state = malloc( src->prng->state_size );
	if ( out->state == NULL ) {
		free( out ); // prevent memory leaks
		return NULL;
	}
	// Set the PRNG (includes PRNG properties and methods):
	out->prng = src->prng;

	// Copy the current source PRNG state:
	memcpy( out->state, src->state, src->prng->state_size );

	return out;
}

/**
* Copies a PRNG state from a source PRNG to a destination PRNG of the same kind.
*
* ## Notes
*
* -   The function returns `-1` if unable to copy a PRNG's state and `0` otherwise.
*
* @param dest  destination PRNG object
* @param src   source PRNG object
* @return      status code
*/
int8_t stdlib_base_prng_copy_state( struct BasePRNGObject *dest, const struct BasePRNGObject *src ) {
	// We can only copy state between compatible PRNGs...
	if ( dest->prng != src->prng ) {
		return -1;
	}
	// Copy the current source PRNG state:
	memcpy( dest->state, src->state, src->prng->state_size );

	return 0;
}
