//------------------------------------------------------------------------------
//	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_BinT.cpp
 * @author Ronald Kluth
 * @date created at 2009/04/24
 * @brief Example displaying the basic usage of an unlimited resource with a
 * user-defined token type (odemx::synchronization::BinT)
 * @since 3.0
 */

/**
 * @example Example_BinT.cpp
 * The basic usage of an unlimited resource is introduced.
 *
 * There are two processes involved in this simulation. One takes the role of
 * a producer of a certain type of items, while the other takes the role of a
 * consumer of such items. These two are linked by a temporary storage space,
 * i.e. an unlimited resource, where the producer puts new items, which will
 * eventually be used by the consumer.
 */

#include <odemx/odemx.h>
using odemx::base::Process;
using odemx::base::Simulation;
using odemx::data::output::OStreamWriter;
using odemx::toString;

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


// forward declaration
odemx::base::SimTime getRandomTime( odemx::random::ContinuousDist& dist );

//-----------------------------------------------------------------------globals
//
// We use two random number generators to model the time it takes to produce
// or consume items.
//
odemx::random::Normal productionPeriod( odemx::getDefaultSimulation(),
		"Production Period", 10, 2 );
odemx::random::Normal consumptionPeriod( odemx::getDefaultSimulation(),
		"Consumption Period", 3, 2 );

//--------------------------------------------------------------------------Item
//
// The struct Item is just a simple type holding the name of some item. It is
// used as token type for the BinT class template.
//
struct Item
{
	Item( const std::string& name ): name( name ) {}
	std::string name;
};

//-------------------------------------------------------------------ItemStorage
//
// The storage space is simply an instantiation of BinT with Item as its token
// type. We will use it to have the consumer wait for new items to be produced.
// BinT handles this transparently by keeping the consumer in a queue while
// there are not enough items in storage. All the consumer has to do is request
// a number of items. It will be awakened as soon as these become available.
//
typedef odemx::synchronization::BinT< Item > ItemStorage;

//------------------------------------------------------------------ItemProducer
//
// The producer process creates new named items and puts them into the storage.
//
class ItemProducer: public Process
{
public:
	ItemProducer( ItemStorage& storage )
	:	Process( odemx::getDefaultSimulation(), "The Producer" )
	,	storage_( &storage )
	{}

protected:
	virtual int main()
	{
		static std::size_t itemCount = 0;
		while( true )
		{
			//
			// Wait for as long as production requires.
			//
			holdFor( getRandomTime( productionPeriod ) );
			//
			// Fill a vector with newly created items.
			//
			std::vector< Item > newItems;
			for( int i = 5; i > 0; --i )
			{
				newItems.push_back(
						Item( std::string("Item ") + toString( ++itemCount ) ) );
			}
			//
			// Add items to the storage bin.
			//
			storage_->give( newItems );
			//
			// Log production of new items
			//
			info << log( "produced 5 new items" );
		}
		return 0;
	}

private:
	ItemStorage* storage_;
};

//------------------------------------------------------------------ItemConsumer
//
// The consumer process simply waits for items to arrive at the temporary item
// storage. When the requested number of items are available, they are taken
// from the storage and the consumer logs their names upon use.
//
class ItemConsumer: public Process
{
public:
	ItemConsumer( ItemStorage& storage )
	:	Process( odemx::getDefaultSimulation(), "The Consumer" )
	,	storage_( &storage )
	{}

protected:
	virtual int main()
	{
		while( true )
		{
			//
			// By calling take on the BinT object, the consumer enters a queue
			// and waits until the requested amount of tokens becomes available.
			// It receives them in a vector.
			//
			std::auto_ptr< std::vector< Item > > newItems = storage_->take( 2 );
			//
			// Consumption of the two items is logged to the info channel.
			//
			info << log( "consumed 2 items" )
					.detail( "first", newItems->front().name )
					.detail( "second", newItems->back().name );
			//
			// Wait for as long as it takes to consume items.
			//
			holdFor( getRandomTime( consumptionPeriod ) );
		}
		return 0;
	}

private:
	ItemStorage* storage_;
};

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

int main( int argc, char* argv[] )
{
	using namespace odemx;
	//
	// Since we are using subclasses of Process without providing a simulation
	// context, we also have to use the implicitly assumed DefaultSimulation
	// here.
	//
	Simulation& sim = getDefaultSimulation();
	//
	// Since we are logging data to the info channel, we must set a data
	// consumer. Here, we use a simple writer to std::cout.
	//
	sim.addConsumer( data::channel_id::info, OStreamWriter::create( std::cout ) );
	//
	// Initialize item storage as well as producer and consumer processes.
	//
	ItemStorage tempStorage( sim, "Temporary Storage" );
	ItemProducer producer( tempStorage );
	ItemConsumer consumer( tempStorage );
	//
	// Schedule the processes for immediate execution at SimTime 0.
	//
	producer.activate();
	consumer.activate();
	//
	// Run the simulation with a time limit. Otherwise, both processes would
	// run forever and the simulation would never stop.
	//
	sim.runUntil( 45 );
	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( odemx::random::ContinuousDist& dist )
{
	odemx::base::SimTime retVal;
	do
	{
		retVal = (odemx::base::SimTime) dist.sample();
	}
	while( retVal <= 0 );

	return retVal;
}
