-- LegacyWorlds Beta 6
-- PostgreSQL database scripts
--
-- Functions for the resource provider generator
--
-- Copyright(C) 2004-2012, DeepClone Development
-- --------------------------------------------------------



/*
 * Resource provider generator data
 * 
 * This data type is used to store statistics about the existing resource
 * providers. A single item of the type represents both statistics and
 * parameters for a given resource type.
 */
DROP TYPE IF EXISTS verse.resprov_generator_type CASCADE;
CREATE TYPE verse.resprov_generator_type AS (
		/* Type of natural resource */
	 	resource_name_id		INT ,

	 	/* Planets in the universe */
	 	planets					DOUBLE PRECISION ,
	 	
	 	/* Providers of this type in the universe */
	 	providers				DOUBLE PRECISION ,

	 	/* Presence probability (from the resource's definition) */
	 	presence				DOUBLE PRECISION ,

	 	/* Total maximal quantity of this type of resource in the whole
	 	 * universe.
	 	 */
	 	quantity				DOUBLE PRECISION ,
	 	
	 	/* Average quantity (from the resource's definition) */
	 	quantity_avg			DOUBLE PRECISION ,
	 	
	 	/* Maximal deviation from the average quantity (from the resource's
	 	 * definition)
	 	 */
	 	quantity_dev			DOUBLE PRECISION ,

	 	/* Total extraction difficulty for this type of resource in the whole
	 	 * universe.
	 	 */
	 	difficulty				DOUBLE PRECISION ,
	 	
	 	/* Average difficulty (from the resource's definition) */
	 	difficulty_avg			DOUBLE PRECISION ,
	 	
	 	/* Maximal deviation from the average difficulty (from the resource's
	 	 * definition)
	 	 */
	 	difficulty_dev			DOUBLE PRECISION ,

	 	/* Total recovery rate for this type of resource in the whole
	 	 * universe.
	 	 */
	 	recovery				DOUBLE PRECISION ,
	 	
	 	/* Average recovery rate (from the resource's definition) */
	 	recovery_avg			DOUBLE PRECISION ,
	 	
	 	/* Maximal deviation from the average recovery rate (from the
	 	 */
	 	recovery_dev			DOUBLE PRECISION
);



/*
 * Collect resource provider statistics
 * 
 * This procedure collects statistics about resource providers into a
 * temporary table named resource_statistics, using the resprov_generator_type
 * as the table's structure. The table will be dropped on commit.
 * 
 * This function is necessary because the statistics must be collected before
 * new planets are generated.
 */
DROP FUNCTION IF EXISTS verse.collect_resprov_statistics( );
CREATE FUNCTION verse.collect_resprov_statistics( )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $collect_resprov_statistics$

BEGIN
	 CREATE TEMP TABLE rp_stats
	 	OF verse.resprov_generator_type
	 	ON COMMIT DROP;

	INSERT INTO rp_stats
		SELECT resource_name_id ,
					_pcount.planets AS planets ,
					( CASE
						WHEN _rp_stats.providers IS NULL THEN 0
						ELSE _rp_stats.providers
					END ) AS providers ,
					natres_p_presence AS presence ,
					( CASE
						WHEN _rp_stats.tot_quantity IS NULL THEN 0
						ELSE _rp_stats.tot_quantity
					END ) AS quantity ,
					natres_quantity_avg AS quantity_avg ,
					natres_quantity_dev AS quantity_dev ,
					( CASE
						WHEN _rp_stats.tot_difficulty IS NULL THEN 0
						ELSE _rp_stats.tot_difficulty
					END ) AS difficulty ,
					natres_difficulty_avg AS difficulty_avg ,
					natres_difficulty_dev AS difficulty_dev ,
					( CASE
						WHEN _rp_stats.tot_recovery IS NULL THEN 0
						ELSE _rp_stats.tot_recovery
					END ) AS recovery ,
					natres_recovery_avg AS recovery_avg ,
					natres_recovery_dev AS recovery_dev
			FROM defs.natural_resources
				LEFT OUTER JOIN (
					SELECT resource_name_id ,
							COUNT(*) AS providers ,
							SUM( resprov_quantity_max ) AS tot_quantity ,
							SUM( resprov_difficulty ) AS tot_difficulty ,
							SUM( resprov_recovery ) AS tot_recovery
						FROM verse.resource_providers
						GROUP BY resource_name_id
					) AS _rp_stats USING ( resource_name_id )
				CROSS JOIN (
					SELECT COUNT(*) AS planets
						FROM verse.planets
				) AS _pcount;
END;
$collect_resprov_statistics$ LANGUAGE PLPGSQL;

REVOKE EXECUTE
	ON FUNCTION verse.collect_resprov_statistics( )
	FROM PUBLIC;



/* Compute a random delta for one of the resource provider parameters
 * 
 * This function computes the total change on one of the resource provider
 * parameters. The resulting value can then be split amongst resource
 * providers as they are created.
 * 
 * Parameters:
 *		_existing			Amount of existing resource providers
 *		_new				Amount of resource providers being created
 *		_total				Current total value for the parameter
 *		_p_average			Average parameter value (from the definition)
 *		_p_deviation		Parameter value deviation (from the definition)
 *
 * Returns:
 *		?					The total value to distribute amongst new resource
 *								providers
 */
DROP FUNCTION IF EXISTS verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
		DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.compute_rpp_delta(
			_existing		DOUBLE PRECISION ,
			_new			DOUBLE PRECISION ,
			_total			DOUBLE PRECISION ,
			_p_average		DOUBLE PRECISION ,
			_p_deviation	DOUBLE PRECISION )
		RETURNS DOUBLE PRECISION
		STRICT VOLATILE
		SECURITY INVOKER
	AS $compute_rpp_delta$

DECLARE
	_result	DOUBLE PRECISION;

BEGIN
	_result := verse.random_deviation( _p_average , _p_deviation )
					* ( _existing + _new ) - _total;

	IF _result < ( _p_average - _p_deviation ) * _new THEN
		_result := ( _p_average - _p_deviation ) * _new;
	ELSIF _result > ( _p_average + _p_deviation ) * _new THEN
		_result := ( _p_average + _p_deviation ) * _new;
	END IF;
	
	RETURN _result;
END;
$compute_rpp_delta$ LANGUAGE PLPGSQL;

REVOKE EXECUTE
	ON FUNCTION verse.compute_rpp_delta( DOUBLE PRECISION , DOUBLE PRECISION ,
			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION )
	FROM PUBLIC;



/*
 * Create a single resource provider
 * 
 * This function creates a single resource provider on some planet. It will
 * return the updated values for the amount of providers left to handle and
 * the totals.
 * 
 * Parameters:
 *		_planet			The identifier of the planet to create a provider on
 *		_data			The statistics and parameters for the type of resource
 *		_providers_left	The amount of resource providers that still need to be
 *							generated (including the current provider)
 *		_tot_quantity	The total value left to distribute for the providers'
 *							maximal quantity
 *		_tot_difficulty	The total value left to distribute for the providers'
 *							extraction difficulty
 *		_tot_recovery	The total value left to distribute for the providers'
 *							recovery rate
 *
 * Returns:
 *		_providers_left	The updated value for the amount of providers left
 *		_tot_quantity	The updated value for the total maximal quantity to
 *							distribute
 *		_tot_difficulty	The updated value for the total extraction difficulty
 *							to distribute
 *		_tot_recovery	The updated value for the total recovery rate to
 *							distribute
 */
DROP FUNCTION IF EXISTS verse.create_resource_provider(
	INT , verse.resprov_generator_type , INT , DOUBLE PRECISION ,
	DOUBLE PRECISION , DOUBLE PRECISION );
CREATE FUNCTION verse.create_resource_provider(
			_planet					INT ,
			_data					verse.resprov_generator_type ,
			INOUT _providers_left	INT ,
			INOUT _tot_quantity		DOUBLE PRECISION ,
			INOUT _tot_difficulty	DOUBLE PRECISION ,
			INOUT _tot_recovery		DOUBLE PRECISION )
		STRICT VOLATILE
		SECURITY INVOKER
	AS $create_resource_provider$

DECLARE
	_quantity		DOUBLE PRECISION;
	_difficulty		DOUBLE PRECISION;
	_recovery		DOUBLE PRECISION;

BEGIN
	_quantity := verse.get_random_part( _tot_quantity , _providers_left ,
		_data.quantity_avg , _data.quantity_dev );
	_difficulty := verse.get_random_part( _tot_difficulty , _providers_left ,
		_data.difficulty_avg , _data.difficulty_dev );
	_recovery := verse.get_random_part( _tot_recovery , _providers_left ,
		_data.recovery_avg , _data.recovery_dev );

	RAISE NOTICE 'Resource #% planet #%: quantity: %   difficulty: %   recovery: %',
		_data.resource_name_id , _planet ,
		_quantity , _difficulty , _recovery;

	INSERT INTO verse.resource_providers (
			planet_id , resource_name_id , resprov_quantity_max ,
			resprov_quantity , resprov_difficulty , resprov_recovery
		) VALUES (
			_planet , _data.resource_name_id , _quantity ,
			_quantity , _difficulty , _recovery
		);

	_tot_quantity := _tot_quantity - _quantity;
	_tot_difficulty := _tot_difficulty - _difficulty;
	_tot_recovery := _tot_recovery - _recovery;
	_providers_left := _providers_left - 1;
END;
$create_resource_provider$ LANGUAGE PLPGSQL;

REVOKE EXECUTE
	ON FUNCTION verse.create_resource_provider( INT ,
			verse.resprov_generator_type , INT , DOUBLE PRECISION ,
			DOUBLE PRECISION , DOUBLE PRECISION )
	FROM PUBLIC;



/*
 * Create resource providers for a given type of resource
 * 
 * This function will create resource providers for some specified type of
 * resource in an area of the universe. It tries to balance the generated
 * values according to the resource's definition.
 * 
 * Parameters:
 *		_area		The area to generate resource providers in
 *		_data		The identifier, definition and statistics for the type of
 *						resource
 */
DROP FUNCTION IF EXISTS verse.create_resource_providers(
	verse.generator_area_type , verse.resprov_generator_type );
CREATE FUNCTION verse.create_resource_providers(
			_area verse.generator_area_type ,
			_data verse.resprov_generator_type )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $create_resource_providers$

DECLARE
	_ncount			INT;
	_create			INT;
	_tot_quantity	DOUBLE PRECISION;
	_tot_difficulty	DOUBLE PRECISION;
	_tot_recovery	DOUBLE PRECISION;
	_planet			INT;

BEGIN
	_ncount := ( _area.x1 - _area.x0 + 1 ) * ( _area.y1 - _area.y0 + 1 ) * 5;

	-- Determine the amount of providers to create
	_create := FLOOR( ( _data.planets + _ncount ) * _data.presence - _data.providers )::INT;
	IF _create <= 0 THEN
		RETURN;
	ELSIF _create > _ncount THEN
		_create := _ncount;
	END IF;

	-- Compute the total delta for quantity, difficulty and recovery rate
	_tot_quantity := verse.compute_rpp_delta( _data.providers , _create ,
			_data.quantity , _data.quantity_avg , _data.quantity_dev );
	_tot_difficulty := verse.compute_rpp_delta( _data.providers , _create ,
			_data.difficulty , _data.difficulty_avg , _data.difficulty_dev );
	_tot_recovery := verse.compute_rpp_delta( _data.providers , _create ,
			_data.recovery , _data.recovery_avg , _data.recovery_dev );

	RAISE NOTICE 'Resource #%: % new provider(s), quantity: % (avg. %) , difficulty: % (avg. %), recovery: % (avg. %)',
		_data.resource_name_id , _create ,
		_tot_quantity , _tot_quantity / _create ,
		_tot_difficulty , _tot_difficulty / _create ,
		_tot_recovery , _tot_recovery / _create;

	-- Select random planets to add resource providers to
	FOR _planet IN SELECT * FROM verse.list_random_planets_in( _area , _create )
	LOOP
		SELECT INTO _create , _tot_quantity , _tot_difficulty , _tot_recovery
			* FROM verse.create_resource_provider( _planet , _data , _create ,
					_tot_quantity , _tot_difficulty , _tot_recovery );
	END LOOP;

END;
$create_resource_providers$ LANGUAGE PLPGSQL;

REVOKE EXECUTE
	ON FUNCTION verse.create_resource_providers( verse.generator_area_type ,
			verse.resprov_generator_type )
	FROM PUBLIC;



/*
 * Create resource providers in some area of the universe
 * 
 * This function creates resource providers in the specified area using the
 * statistics collected before the area was created to balance the resource
 * providers' parameters.
 * 
 * Parameters:
 *		_area		The area to generate resource providers in
 */
DROP FUNCTION IF EXISTS verse.create_resource_providers( verse.generator_area_type );
CREATE FUNCTION verse.create_resource_providers( _area verse.generator_area_type )
		RETURNS VOID
		STRICT VOLATILE
		SECURITY INVOKER
	AS $create_resource_providers$

DECLARE
	_rp_data		verse.resprov_generator_type;

BEGIN
	FOR _rp_data IN SELECT * FROM rp_stats
	LOOP
		PERFORM verse.create_resource_providers( _area , _rp_data );
	END LOOP;
END;
$create_resource_providers$ LANGUAGE PLPGSQL;


REVOKE EXECUTE
	ON FUNCTION verse.create_resource_providers( verse.generator_area_type )
	FROM PUBLIC;
