//----------------------------------------------------------------------------
//	Copyright (C) 2002, 2003, 2004 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_Shop.cpp

	\author Ralf Gerstenberger

	\date created at 2003/07/15

	\brief Shop example with ODEMx

	This example shows more advanced techniques to
	realize a process simulation with ODEMx.

\since 1.0
*/
/** \example Example_Shop.cpp
	In this example, a shop which is selling different
	goods to customers is simulated. Shop-assistants handle
	the final payment after a customer has gathered their
	goods from the shelves in the shop. Each shelf is
	holding a limited amount of one commodity. If a shelf
	runs out of its commodity, a shop-assistant has to
	restock the shelf. There is also a shopkeeper, who
	employs the shop-assistants and orders goods, if the
	store room of the shop is running out of any. Finally,
	there is a process generating new customers after a
	random delay.
*/

//
// Includes
//
// We include the main ODEMx header file odemx.h which in
// turn includes the other header files of ODEMx. All
// header files of ODEMx define a macro when included,
// to prevent repetitive inclusion. That means it doesn't
// hurt to include a header file more than once, because
// subsequent inclusions are ignored.
//
#include <odemx/odemx.h>
//
// Apart from ODEMx we use some C++ standard features in
// this simulation.
//
#include <iostream>
#include <fstream>
#include <list>
#include <string>
#include <vector>

//
// Namespaces
//
// All ODEMx classes reside in a sub-namespace of namespace odemx
// to prevent naming conflicts. In this simulation it is safe to
// import several odemx namespaces into the global namespace.
//
using namespace odemx::base;
using namespace odemx::data;
using namespace odemx::data::output;
using namespace odemx::random;
using namespace odemx::synchronization;
using namespace std;

//
// Global parameters
//
// To simplify matters we use global parameters in this example.
// That is, however, not recommended in general. A better place
// for simulation parameters is the specific simulation class.
//
// GOODS contains the different commodities sold in the shop.
//
static const string GOODS[] = {"Apple", "Bananas", "Bread", "Butter", "Candies",
							  "Cheese", "Chips", "Chocolate", "Mixed-pickles", "Fish",
							  "Jam", "Juice", "Lemon", "Lemonade", "Marmalade",
							  "Milk", "Orange", "Pepper", "Pineapples", "Pork",
							  "Salami", "Salt", "Sugar", "Tomatoes", "Water"};

//
// NUMBER_OF_GOODS is used in many for-loops as the end-condition.
//
static const int NUMBER_OF_GOODS = 25;

//
// INIT_STOCK is the initial amount of each good held in the
// store room.
//
static const int INIT_STOCK = 50;

//
// INIT_TILLS is the initial number of tills available.
//
static const int INIT_TILLS = 5;

//
// INIT_PERSONNEL is the initial number of shop-assistants
// employed by the shopkeeper.
//
static const int INIT_PERSONNEL = 8;

//
// SHELF_CAPACITY defines the capacity of a shelf. The capacity
// is measured in items, not in size. That means every shelf
// holds up to SHELF_CAPACITY items of its commodity.
//
static const int SHELF_CAPACITY = 15;

//
// ACCEPTED_QUEUE_LENGTH defines the queue length after
// which a shop-assistant will help out at the tills.
//
static const int ACCEPTED_QUEUE_LENGTH = 5;

//
// ACCEPTED_SHELF_STOCK defines the number of items in
// a shelf after which the shelf has to be refilled.
//
static const int ACCEPTED_SHELF_STOCK = 5;

//
// The following parameters define delays for payment,
// for refilling shelves, for taking goods from a shelf, for moving
// between shelves, and they define the time after which the shopkeeper
// is checking the store room of the shop again.
//
static const SimTime PAYMENT_DELAY = 5;
static const SimTime RESTOCK_DELAY = 15;
static const SimTime TAKE_DELAY = 1;
static const SimTime MOVE_DELAY = 1;
static const SimTime STOCKKEEPER_DELAY = 60;

//
// Pre-declarations
//
class Shelf;
class Tills;
class Shopkeeper;
class Assistant;
class Customer;
class CustomerGen;

//
// Classes
//

//
// The class ShopSim is derived from Simulation and provides
// access to some important objects of this simulation. All
// classes which have a pointer to ShopSim can use the get*
// methods to get these objects. In addition we could move
// the global parameters to this class. ShopSim also
// does some initializations in its constructor and the
// initSimulation() function.
//
class ShopSim : public Simulation
{
	// Shelves in the shop, one for each commodity
	vector<Shelf*>		shelves;
	// Back-store for refilling shelves
	vector<int>			stock;
	// Customers pay at one of the tills
	Tills*				tills;

	Shopkeeper*			boss;
	CustomerGen*		generator;

	// Random number generators
	RandomInt				*rng5, *rng10, *rng;
	NegativeExponential*				delay;

	// Assistant-management
	CondQ*				work;

public:
	// Construction and initialisation
	ShopSim();
	virtual void initSimulation();

	// get*() methods
	vector<Shelf*>& getShelves() {return shelves;}
	vector<int>& getStock() {return stock;}
	Tills* getTills() {return tills;}

	Shopkeeper* getBoss() {return boss;}
	CustomerGen* getCustomerGen() {return generator;}

	DiscreteDist* getRNG() {return rng;}
	DiscreteDist* getRNG5() {return rng5;}
	DiscreteDist* getRNG10() {return rng10;}
	ContinuousDist* getDelay() {return delay;}

	CondQ* getWork() {return work;}
};

//
// Shelf represents a shelf in our shop. Every shelf
// contains one commodity and provides methods for
// customers to take goods. The shop-assistants refill
// a shelf if its running out of stock.
// The management of commodity items held in a shelf
// is realised with the ODEMx standard component Res.
//
class Shelf
{
	// Pointer to ShopSim
	ShopSim* sim;

	// The type of commodity presented in this shelf
	int type;

	// The amount of goods left in this shelf
	Res commodity;

public:
	// Construction
	Shelf(ShopSim* s, int t);

	// Type of commodity presented in this shelf
	int getType() const {return type;}

	// Customers use this method to take goods.
	int takeCommodity(int n);

	// Assistants use this method to restock the shelf.
	int fillShelf(int n);

	// Assistants use this method to check the stock.
	int checkStock() const {return commodity.getTokenNumber();}
};

//
// After taking their goods, customers go to the tills
// to pay. There are several tills available but a till
// also needs to be handled by a shop-assistant to be active.
// The payment is managed with the ODEMx standard component
// WaitQ which realizes the synchronization between customers
// giving the money and shop-assistants taking it. The Tills
// also collect statistic data about the number of assistants
// by using the ODEMx class Accum.
//
class Tills
{
	// Pointer to ShopSim
	ShopSim* sim;

	// Customers wait in a WaitQ for service.
	WaitQ	queue;

	// Tills keep statistics about the assistants.
	odemx::statistics::Accumulate* statistic;

	// The number of assistants handling the tills.
	int cashier;

	// The number of tills available.
	int tills;

public:
	// Construction
	Tills(ShopSim* s);

	// Customers use the method pay() to enter the queue (enqueue) at the
	// tills and to pay.
	void pay();

	// Assistants use take to get the next customer.
	Customer* take();

	// The number of shop-assistants who take the payment changes,
	// updated with updateCashier().
	void updateCashier(int n);

	// get* methods
	int getQueueLength() const {return queue.getWaitingSlaves().size();}
	int getCashier() const {return cashier;}
	int getTills() const {return tills;}
};

//
// The shopkeeper is responsible for the employment of shop-assistants
// and the ordering of goods. The class Shopkeeper is derived from
// Process and defines the behaviour of the shopkeeper by implementing
// the function main().
//
class Shopkeeper : public Process
{
public:
	// Construction
	Shopkeeper(ShopSim& sim);

	// Behaviour
	virtual int main();
};

//
// Shop-assistants represent the work-force driving the shop. They
// refill the shelves or handle a till. The class Assistant defines
// the behavior of the shop assistants by implementing main().
// Shop assistants decide on their own which task they fulfil
// next (handle a till or refill shelves). If neither is required
// they wait for work in a conditional queue provided by ODEMx and
// kept in the ShopSim class (ShopSim::work).
//
class Assistant : public Process
{
public:
	// Construction
	Assistant(ShopSim& sim);

	// Behaviour
	virtual int main();

	// chooseWork and work are used to find the next task for an
	// assistant or to wait for work.
	int chooseWork();
	bool work();
};

//
// This class represents the customers in this simulation.
// Customers have (random) demand which they try to fulfil
// by taking goods from the shelves. Finally, a customer pays
// for the taken goods at the tills.
//
class Customer : public Process
{
	// The customer's demand.
	vector<int>	 demand;

	// The goods in the customer's basket.
	vector<int>	 basket;

public:
	// Construction
	Customer(ShopSim& sim);

	// Behaviour
	virtual int main();
};

//
// The Customers are randomly created by CustomerGen. By this means
// we achieve an approximation of the customer-flow in our shop.
//
class CustomerGen : public Process
{
public:
	// Construction
	CustomerGen(ShopSim& sim);

	// Behaviour
	virtual int main();
};

//
// Implementations of member functions
//

//
// Construction of a ShopSim object itself and some other major
// objects.
//
ShopSim::ShopSim() :
	Simulation("ShopSim"),
	shelves(NUMBER_OF_GOODS),
	stock(NUMBER_OF_GOODS)
{
	// Create and initialise shelves, one shelf for each commodity.
	for (int i=0; i<NUMBER_OF_GOODS; ++i)
	{
		shelves[i] = new Shelf(this, i);
		stock[i] = INIT_STOCK;
	}

	// Create the tills, the boss, and the customer generator.
	tills = new Tills(this);
	boss = new Shopkeeper(*this);
	generator = new CustomerGen(*this);

	// Create the random number generators.
	// RandomInt and NegativeExponential are provided by ODEMx.
	rng = new RandomInt(*this, "RNG", 0, NUMBER_OF_GOODS -1);
	rng5 = new RandomInt(*this, "RNG", 0, 5);
	rng10 = new RandomInt(*this, "RNG", 0, 10);
	delay = new NegativeExponential(*this, "Delay", 0.125);

	// Finally, the CondQ work is created and added to the report.
	work = new CondQ(*this, "Work");
}

//
// initSimulation is called before the simulation starts. We
// use this function to activate the boss and the customer
// generator. They in turn create the other processes.
// The initSimulation function is sort of an entry-point
// of a simulation. It is called before the simulation
// starts and is the best place to activate the first processes
// of a simulation.
//
void ShopSim::initSimulation()
{
	boss->activate();
	generator->activate();
}

//
// Construction of a Shelf object.
//
Shelf::Shelf(ShopSim* s, int t) :
	sim(s),
	type(t),
	commodity(*s, ("Shelf_" + GOODS[t]), 0, SHELF_CAPACITY)
{
	// Shelf is using a Res object to manage the amount of goods
	// available. Res is providing some report about its usage.
}

//
// fillShelf is used by the shop-assistants to restock the shelf.
// In this version the function is only a wrapper to the release
// method of the Res object which is actually managing the available
// goods.
//
int Shelf::fillShelf(int n)
{
	commodity.release(n);
	return commodity.getTokenNumber();
}

//
// takeCommodity is used by the customers to get the goods. We
// could just relay the call to commodity.acquire(). But then a
// customer would wait until there are enough items available.
// Instead we signal the shop-assistants the need to refill a
// shelf and give the customer only the items available.
//
int Shelf::takeCommodity(int n)
{
	int avail = commodity.getTokenNumber();

	if (avail > n)
	{
		commodity.acquire(n);
		return n;
	}
	else
	{
		commodity.acquire(avail);
		sim->getWork()->signal();
		return avail;
	}
}

//
// Construction of the Tills object.
//
Tills::Tills(ShopSim* s) :
	sim(s),
	queue(*s, "Tills queue"),
	cashier(0),
	tills(INIT_TILLS)
{
	// Tills is providing some additional statistics about
	// the number of assistants handling the tills.
	statistic = new odemx::statistics::Accumulate( *sim, "Tills statistics");
}

//
// Payment is done by the functions pay and take. There are
// two objects working together to handle payment. A customer
// giving the money and a shop-assistant taking it. Both have to
// be synchronised. Tills uses the standard ODEMx synchronisation
// object WaitQ to do this.
//
void Tills::pay()
{
	// Every time a customer wants to pay, waiting assistants
	// are signaled.
	sim->getWork()->signal();

	// The synchronisation between a customer (who wants
	// to pay) and an assistant is realised with WaitQ. WaitQ
	// manages not only the synchronisation but also provides
	// statistics. The wait() function used here adds the
	// current process (the one that has just called pay()) to the
	// queue.
	queue.wait();
}

//
// Assistants use take to get the next customer.
//
Customer* Tills::take()
{
	Process* p = 0;

	// The coopt() function of WaitQ returns the next Customer
	// in the queue(sorted according to priority and time of arrival).
	// If there is no customer left the current process
	// (the one that has just called take()) waits.
	p = queue.coopt();

	return (Customer*) p;
}

//
// The number of assistants working at the tills is updated
// with this function. In this function the statistic about
// assistants handling tills is also updated.
//
void Tills::updateCashier(int n)
{
	cashier += n;
	statistic->update(sim->getTime(), cashier);
}

//
// Construction of Assistant objects.
//
Assistant::Assistant(ShopSim& sim) :
	Process(sim, "Assistant")
{}

//
// Assistant is derived from Process. That means it has to
// provide its own main() function, where it defines the actions
// done by an Assistant.
//
int Assistant::main()
{
	//
	// At first we cache pointers to our simulation
	// and to the tills.
	//
	ShopSim& sim = dynamic_cast< ShopSim& >( getSimulation() );
	Tills* tills = sim.getTills();

	while (true)
	{
		//
		// Wait for something to do:
		// Assistants have to do two different tasks.
		// If required they handle a till to collect
		// payment from the customers. Otherwise they go
		// through the shop and refill empty shelves.
		// If neither is required they wait for work.
		// ShopSim uses the ODEMx synchronisation object
		// CondQ to keep track of waiting Assistants.
		// CondQ allows processes to wait for certain
		// conditions defined by a condition call-back
		// function. Assistant's member function work()
		// was designed to define the condition when an
		// Assistant has to act.
		// The first real action of an assistant is to
		// wait for a task.
		//
		sim.getWork()->wait((odemx::base::Condition)&Assistant::work);

		//
		// Choose your work:
		// The function chooseWork() determines what has
		// to be done. It returns 1 if an assistant is
		// needed at the tills and 2 if a shelf has to be
		// refilled.
		if (chooseWork()==1)
		{
			//
			// Cashier task
			//

			// update number of assistants at the tills
			tills->updateCashier(1);

			// serve customers as long as there are any
			while (tills->getQueueLength()>0)
			{
				// get the next customer in queue
				Customer* c = tills->take();

				// payment does take some time
				holdFor(PAYMENT_DELAY);

				// finally, activate the customer
				// to let them leave the store
				c->activate();
			}

			// update number of assistants at the tills
			tills->updateCashier(-1);
		}
		else
		{
			//
			// Restock task
			//

			// The assistant goes through all shelves and
			// refills if necessary and possible.
			vector<Shelf*>::iterator i;
			for (i=sim.getShelves().begin(); i!=sim.getShelves().end(); ++i)
			{
				// We cache some pointer.
				Shelf* shelf = *i;
				vector<int>& stock = sim.getStock();
				int type = shelf->getType();

				if (shelf->checkStock() < ACCEPTED_SHELF_STOCK &&
					stock[type] > 0)
				{
					// A shelf is filled up to its capacity
					// if possible.
					int n = SHELF_CAPACITY - shelf->checkStock();
					if (stock[type] < n)
						n = stock[type];

					shelf->fillShelf(n);
					stock[type] -= n;

					// refilling takes time
					holdFor(RESTOCK_DELAY);
				}
			}
		}
	}

	// This one will never be reached.
	return 0;
}

//
// chooseWork() decides which task should be done.
//
int Assistant::chooseWork()
{
	ShopSim& sim = dynamic_cast< ShopSim& >( getSimulation() );
	vector<Shelf*>::iterator i;

	// If the queue at the tills has exceeded an acceptable
	// length and we have a free till without an assistant,
	// the assistant should work at it.
	if (sim.getTills()->getQueueLength() >= ACCEPTED_QUEUE_LENGTH &&
		sim.getTills()->getCashier() < sim.getTills()->getTills())
		return 1;

	// Otherwise, if there is a shelf out of stock while we have
	// still goods left, the assistant should go on the refill-route.
	for (i=sim.getShelves().begin(); i!=sim.getShelves().end(); ++i)
	{
		if ((*i)->checkStock()<ACCEPTED_SHELF_STOCK &&
			(sim.getStock())[(*i)->getType()] > 0)
			return 2;
	}

	// If neither is required the assistant should wait.
	return 0;
}

//
// The condition call-back work() uses the function chooseWork()
// to find something to do.
//
bool Assistant::work()
{
	return chooseWork()!=0;
}

//
// Construction of the Shopkeeper object.
//
Shopkeeper::Shopkeeper(ShopSim& sim) :
	Process(sim, "Shopkeeper")
{}

//
// Like Assistant Shopkeeper is a Process and has to
// define its behaviour in the main() function.
//
int Shopkeeper::main()
{
	ShopSim& sim = dynamic_cast< ShopSim& >( getSimulation() );

	//
	// The first task of the shopkeeper is to employ
	// some shop-assistants.
	//
	int i;
	for (i=0; i<INIT_PERSONNEL; ++i)
	{
		Assistant* person = new Assistant(sim);
		person->activate();
	}

	//
	// Afterwards the only thing the shopkeeper has to do
	// is to look after the stock of goods in his shop.
	//
	while (true)
	{
		//
		// Order goods:
		// We check all goods; if something runs out
		// we fill it up again.
		//
		vector<int>& stock = sim.getStock();
		for (i=0; i<NUMBER_OF_GOODS; ++i)
		{
			if (stock[i]==0) stock[i]=INIT_STOCK;
		}

		//
		// Wait some time
		//
		holdFor(STOCKKEEPER_DELAY);
	}

	return 0;
}

//
// Construction of the Customer objects.
//
Customer::Customer(ShopSim& sim) :
	Process(sim , "Customer"),
	demand(NUMBER_OF_GOODS),
	basket(NUMBER_OF_GOODS)
{}

//
// The Customer wants a random amount of a random number of
// commodities. The customer tries to take the desired items
// from the shelves, pays at the tills, and finally, leaves the
// simulation.
//
int Customer::main()
{
	// We cache some pointer.
	ShopSim& sim = dynamic_cast< ShopSim& >( getSimulation() );
	vector<Shelf*>& shelves = sim.getShelves();
	Tills* tills = sim.getTills();
	DiscreteDist* rng = sim.getRNG();
	DiscreteDist* rng5 = sim.getRNG5();
	DiscreteDist* rng10 = sim.getRNG10();

	//
	// Init wish-list
	//
	int i;

	// A customer wants up to 10 different types of goods.
	int numberOfItems = rng10->sample();
	for (i=0; i<numberOfItems; ++i)
	{
		int type = rng->sample();
		int amount = rng5->sample();

		demand[type] = amount;
	}

	//
	// Get goods from shelves
	//
	for (i=0; i<NUMBER_OF_GOODS; ++i)
	{
		if (demand[i]==0)
		{
			holdFor(MOVE_DELAY);
			continue;
		}

		Shelf* shelf = shelves[i];
		basket[i] = shelf->takeCommodity(demand[i]);

		holdFor(TAKE_DELAY + MOVE_DELAY);
	}

	//
	// Pay at a till
	//
	tills->pay();

	return 0;
}

//
// Construction of the CustomerGen object.
//
CustomerGen::CustomerGen(ShopSim& sim) :
	Process(sim, "CustomerGen")
{}

//
// CustomerGen creates Customers.
//
int CustomerGen::main()
{
	// Cash the ShopSim pointers.
	ShopSim& sim = dynamic_cast< ShopSim& >( getSimulation() );
	Customer* c = 0;
	SimTime delay;

	while (true)
	{
		// Create and activate a new Customer.
		c = new Customer(sim);
		c->activate();

		// Wait a random time and update statistic.
		delay = static_cast< SimTime >( sim.getDelay()->sample() );
		holdFor(delay);
	}

	return 0;
}

//
// Main:
// In the global main() function we finally create an object
// of the type ShopSim, start the trace, run the simulation for
// two simulated days, stop trace and generate the report.
//
int main(int argc, char* argv[])
{
	//
	// Using a local variable to hold the ShopSim object is
	// safe because the stack of the global main() function
	// won't be touched during a simulation. The stacks of
	// the Process main() functions however are not available
	// to each other or to the global main(). You can of course
	// use dynamic allocation as seen in CustomerGen::main().
	//
	ShopSim* sim = new ShopSim();

	sim->enableDefaultLogging( XML, "Example_Shop_Log" );
	//
	// The Simulation class provides different functions to
	// compute the simulation. runUntil() starts computation
	// until the given simulation time is reached. We have to
	// use this method because our simulation does not terminate
	// on its own. Which would be the case, if a Process calls
	// exitSimulation() at ShopSim or if there would be no
	// process scheduled for execution.
	//
	sim->runUntil(60 * 24 * 2);

	//
	// The report is generated at this moment. All registered
	// objects must be alive and ready to add their parts to
	// the report.
	//
	sim->reportDefaultStatistics( XML, "Example_Shop.xml" );
	return 0;
}

