//------------------------------------------------------------------------------
//	Copyright (C) 2009 Humboldt-Universitaet zu Berlin
//
//	This library is free software; you can redistribute it and/or
//	modify it under the terms of the GNU Lesser General Public
//	License as published by the Free Software Foundation; either
//	version 2.1 of the License, or (at your option) any later version.
//
//	This library is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//	Lesser General Public License for more details.
//
//	You should have received a copy of the GNU Lesser General Public
//	License along with this library; if not, write to the Free Software
//	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//------------------------------------------------------------------------------

/**
 * @file Example_Res.cpp
 * @author Ronald Kluth
 * @date created at 2009/02/11
 * @brief Example for basic simulation techniques using a limited resource
 * @since 3.0
 */

/**
 * @example Example_Res.cpp
 *
 * This example shows the basic techniques for simulating a limited resource
 * with ODEMx. The class with the limited resource is a gas station with only
 * two gas pumps.
 *
 * Cars are generated by a self-scheduling event until its counter reaches 0.
 * When the car processes are activated, they enter the gas station and thereby
 * try to acquire a resource - one of the pumps. If both pumps are in use, they
 * will automatically wait their turn in a queue provided by an odemx::synchronization::Res
 * object.
 */

#include <odemx/odemx.h>

#include <iostream>
#include <vector>
//#include <cstdlib>
#ifdef _MSC_VER
#include <memory>
#else
#include <tr1/memory>
#endif


// short namespace alias
namespace Rand = odemx::random;

// forward declarations
class Car;
odemx::base::SimTime getRandomTime( Rand::ContinuousDist& dist );

//--------------------------------------------------------------------GasStation
//
// This passive class models a very simple gas station that only has two pumps
// and no other attributes. The pumps are represented by a Res object with two
// initial tokens and a maximum token limit of two.
//
class GasStation
{
public:
	typedef std::tr1::shared_ptr< GasStation > Ptr;

	GasStation(): gasPumps_( odemx::getDefaultSimulation(), "Gas Pumps", 2, 2 ) {}
	void enter();
	void leave();
private:
	odemx::synchronization::Res gasPumps_;
};

//---------------------------------------------------------------------------Car
//
// Cars are modeled as very simple processes that do nothing else but enter
// the station and leave it once they have been refueled. The only attributes
// of this class are a reference to the gas station it uses and another
// reference to a random number generator for randomized refuel periods.
//
class Car: public odemx::base::Process
{
public:
	typedef std::tr1::shared_ptr< Car > Ptr;

	Car( GasStation& station, Rand::ContinuousDist& refuelTime )
	:	Process( odemx::getDefaultSimulation(), "Car" )
	,	station_( station )
	,	refuelTime_( refuelTime )
	{}
	virtual int main();
	void refuel();
private:
	GasStation& station_;
	Rand::ContinuousDist& refuelTime_;
};

//-----------------------------------------------------------------------Arrival
//
// This event class describes the arrival of a new car. It is a generator for
// new cars, which are activated (scheduled) right after the event action is
// finished. The number of cars to be generated is set in the constructor.
// This class also provides memory management by using a vector to keep track
// of all cars it creates.
//
class Arrival: public odemx::base::Event
{
public:
	typedef std::tr1::shared_ptr< Arrival > Ptr;

	Arrival( GasStation& station, unsigned int count,
			Rand::ContinuousDist& arrivalTime,
			Rand::ContinuousDist& refuelTime )
	:	Event( odemx::getDefaultSimulation(), "Car Arrival Event" )
	,	station_( station )
	,	count_( count )
	,	arrivalTime_( arrivalTime )
	,	refuelTime_( refuelTime )
	{}
	virtual void eventAction();
	void scheduleAtRandomTime();
private:
	GasStation& station_;
	unsigned int count_;
	Rand::ContinuousDist& arrivalTime_;
	Rand::ContinuousDist& refuelTime_;
	// automatic memory management
	std::vector< Car::Ptr > generatedCars_;
};

//------------------------------------------------------------method definitions
//
// Entering the gas station is modeled by acquiring a resource (a gas pump).
// If none is available, the caller's execution will automatically be blocked
// and it will be inserted into a queue to wait its turn.
//
void GasStation::enter()
{
	gasPumps_.acquire( 1 );
}
//
// Leaving the gas station is modeled by the release of an acquired resource.
//
void GasStation::leave()
{
	gasPumps_.release( 1 );
}
//
// The car's described behavior only covers the entering and leaving of the
// gas station. If it cannot be serviced right away, the call to enter() will
// block the execution of main(). Once it was allowed to enter, the car can be
// refueled. After waiting for a random amount of refueling time, the car leaves
// the gas station.
//
int Car::main()
{
	info << log( "is entering the station" );
	station_.enter();
	info << log( "is refueling" );
	holdFor( getRandomTime( refuelTime_ ) );
	station_.leave();
	info << log( "has left the station" );
	return 0;
}
//
// The action associated with an arrival event is the creation and activation
// of a new car. After that, the event object reschedules itself after a random
// period of time.
//
void Arrival::eventAction()
{
	Car::Ptr car( new Car( station_, refuelTime_ ) );
	car->activate();
	info << log( "activated car" ).detail( "name", car->getLabel() );
	generatedCars_.push_back( car );

	if( count_ > 0 )
	{
		--count_;
		this->scheduleIn( getRandomTime( arrivalTime_ ) );
	}
}

//--------------------------------------------------------------------------main

int main( int argc, char* argv[] )
{
	using namespace odemx;
	using namespace odemx::data::output;
	using odemx::base::Simulation;
	//
	// Check command line arguments: we require a number for gas stations.
	//
	if( argc != 2 )
	{
		std::cerr << "Usage: " << argv[0] << " <station count>" << std::endl;
		return -1;
	}
	//
	// Get the station count and check for error.
	//
	int stationCount = std::atoi( argv[1] );
	if( stationCount < 1 )
	{
		std::cerr << "Error: station count < 1" << std::endl;
	}
	//
	// In our class definitions, we have only used Process and Event
	// constructors without a Simulation parameter, thereby using the
	// default simulation context. Here, we get a reference to it.
	//
	Simulation& sim = getDefaultSimulation();
	//
	// Every simulation run should be characterized by a description of the
	// goals to be achieved.
	//
	sim.setDescription( "Simulation of a basic limited resource." );
	//
	// The simulation context can handle logging automatically and will use
	// sensible defaults. All log records will be logged to a rotating
	// XML file writer, while warnings and errors are also sent to stderr.
	// Furthermore, all statistical records will be computed and buffered
	// internally. A call to reportStatistics() will create an XML report.
	//
	sim.enableDefaultLogging( STDOUT );
	//
	// Only after setting log data consumers to the channels will log records
	// be visible. We initialize all other simulation elements after calling
	// enableDefaultLogging() to make sure we capture all log data.
	//
	// We use two random number generators for random time periods. One
	// influences the arrival frequency of new cars, and the other is used for
	// random refueling periods.
	//
	Rand::Normal randomArrival( sim, "Arrival Time", 5, 2 );
	Rand::Normal randomRefueling( sim, "Refueling Time", 10, 5 );
	//
	// Create the needed objects: a number of gas stations, and the
	// corresponding car generators. (We use vectors with shared pointers to
	// objects for automatic memory management.)
	//
	std::vector< Arrival::Ptr > arrivalEvents;
	std::vector< GasStation::Ptr > stations;
	do
	{
		GasStation::Ptr newStation( new GasStation() );

		Arrival::Ptr newCarGenerator(
				new Arrival( *newStation, 10, randomArrival, randomRefueling ) );
		//
		// Schedule the car generator for its initial execution.
		//
		newCarGenerator->scheduleIn( getRandomTime( randomArrival ) );

		stations.push_back( newStation );
		arrivalEvents.push_back( newCarGenerator );
	}
	while( --stationCount );
	//
	// Start the simulation and let it run until the schedule is empty.
	//
	sim.run();
	//
	// Output a statistics report computed from the default log. In this case,
	// it will contain usage data about the random number generators, the
	// resource objects (gas pumps) and their internal queues.
	//
	sim.reportDefaultStatistics( STDOUT );
	sim.reportDefaultStatistics( XML, "basicResourceStatistics.xml" );
	return 0;
}
//
// Since random number generators sometimes also generate negative values,
// we cannot simply use samples as time values. This helper function ensures
// that only values greater than or equal to zero are returned.
//
odemx::base::SimTime getRandomTime( Rand::ContinuousDist& dist )
{
	odemx::base::SimTime retVal;
	do
	{
		retVal = (odemx::base::SimTime) dist.sample();
	}
	while( retVal <= 0 );

	return retVal;
}
