/*
 * CS 70, Fall 2002, Assignment 8
 *
 * Implementation of the BarRequester and related classes.
 *
 * These classes take care of direct and indirect bar customers:
 * individual drinkers and table servers.  See the associated header
 * file for a specification of the external behavior of the classes.
 */

#include "barrequester.hh"
#include "randistrs.h"
#include "simulationconstants.hh"
#include "simulationtime.hh"
#include <cassert>
#include <cmath>
#include <iostream>
#include <string>

using namespace std;

/*
 * Allocation and initialization of static member variables.
 */
double			BarRequester::serviceTime = SERVICE_TIME;
					// Mean service time for mixing a drink
mt_distribution		BarRequester::servicePRNG;
					// PRNG for generating service times
SimulationTime		BarRequester::suspensionPenalty = SUSPENSION_PENALTY;
					// (Constant) time penalty for
					// ..suspending service in the middle
					// ..of mixing a drink.

int			Drinker::totalServices = 0;
					// Total number of individual drinkers
					// ..served
TimeStatistics		Drinker::serviceStats;
					// General statistics on individual
					// ..drinkers

int			TableServer::totalServices = 0;
					// Total number of table servers
					// ..serviced by bartender
int			TableServer::totalDrinksServed = 0;
					// Total number of drinks mixed on
					// ..behalf of table servers
TimeStatistics		TableServer::serviceStats;
					// General statistics on table servers

/*
 * Default constructor for TimeStatistics
 */
TimeStatistics::TimeStatistics()
  : totalTime(0),
    sumSquares(0),
    minTime(0),
    maxTime(0),
    count(0)
    {
    }

/*
 * Destructor for TimeStatistics
 */
TimeStatistics::~TimeStatistics()
    {
    }

/*
 * Record a time event, collecting the information needed to generate
 * statistics later:
 *
 * 1. The time is added to totalTime for use in calculating the mean.
 * 2. The square of the time is added to sumSquares for use in
 *    calculating the standard deviation.
 * 3. minTime and maxTime are set if either (a) count was zero,
 *    indicating that this is the first time that we've seen, or (b)
 *    the value given is a new maximum or minimum.
 * 4. The count is incremented for use in calculating the mean and
 *    standard deviation.
 */
void TimeStatistics::recordTime(
    SimulationTime	time)		// Observed time to be added to
					// ..statistics
    {
    assert(time >= 0);

    totalTime += time;
    sumSquares += (double)time.toInt() * time.toInt();

    if (count == 0  ||  time < minTime)
	minTime = time;

    /*
     * Since times can only be positive, we don't need to check the
     * count.  The first nonzero time we see will reset the maximum.
     */
    if (time > maxTime)
	maxTime = time;

    /*
     * We save incrementing the count until last, so that the check
     * above for a zero count will work properly.
     */
    count++;
    }

/*
 * Report collected statistics to a stream.  The statistics reported
 * include the mean, standard deviation (unbiased estimator), minimum,
 * and maximum.  No statistics are output if none have been collected,
 * and the standard deviation is not produced if only one sample was
 * seen.
 *
 * For convenience in formatting the output, the caller can specify a
 * string prefix that is prepended to every line generated by this
 * routine.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
void TimeStatistics::reportStatistics(
    ostream&		stream,		// Stream to report to
    const string&	prefix)		// Prefix to prepend to every line
  const
    {
    if (count == 0)			// Don't even try if nothing was seen
	return;

    /*
     * Produce the (rounded) mean.
     */
    double mean = (double)totalTime.toInt() / count;
    stream << prefix << "mean time " << SimulationTime((int)(mean + 0.5))
      << endl;

    /*
     * Produce the standard deviation, if we have enough samples to
     * calculate it.
     */
    if (count > 1)
	{
	double variance = sumSquares - count * mean * mean;
	variance /= count - 1;

	stream << prefix << "standard deviation "
	  << SimulationTime((int)(sqrt(variance) + 0.5))
	  << endl;
	}

    /*
     * Finally, do the minimum and maximum.
     */
    stream << prefix << "minimum time " << minTime << endl;
    stream << prefix << "maximum time " << maxTime << endl;
    }


/*
 * Default constructor for BarRequester.  We set actualStartTime to
 * -1 so we can distinguish an unset start time from a start time of
 * zero.
 */
BarRequester::BarRequester()
  : scheduledStartTime(0),
    scheduledServiceTime(0),
    remainingServiceTime(0),
    actualStartTime(-1)
    {
    }

/*
 * From-parameters constructor for BarRequester.
 */
BarRequester::BarRequester(
    SimulationTime	scheduledStartTime_,
					// Time requester "arrives"
    SimulationTime	serviceTime)	// Service time needed to mix drink
  : scheduledStartTime(scheduledStartTime_),
    scheduledServiceTime(serviceTime),
    remainingServiceTime(serviceTime),
    actualStartTime(-1)
    {
    }

/*
 * Copy constructor for BarRequester.
 */
BarRequester::BarRequester(
    const BarRequester&	source)		// Requester to copy
  : scheduledStartTime(source.scheduledStartTime),
    scheduledServiceTime(source.scheduledServiceTime),
    remainingServiceTime(source.remainingServiceTime),
    actualStartTime(source.actualStartTime)
    {
    }

/*
 * Destructor for BarRequester.
 */
BarRequester::~BarRequester()
    {
    }

/*
 * Assignment operator for BarRequester.
 */
BarRequester& BarRequester::operator=(
    const BarRequester&	source)		// Requester to assign from
    {
    if (this != &source)
	{
	scheduledStartTime = source.scheduledStartTime;
	scheduledServiceTime = source.scheduledServiceTime;
	remainingServiceTime = source.remainingServiceTime;
	actualStartTime = source.actualStartTime;
	}

    return *this;
    }

/*
 * Return the time that a BarRequester is scheduled to begin service.
 */
SimulationTime BarRequester::dueTime()
  const
    {
    return scheduledStartTime;
    }

/*
 * Begin service to a BarRequester.  This is a virtual function that
 * is overridden by the derived classes.  The base functionality
 * merely records the time when service began.  Note that it is
 * possible to start service several times (if service to an
 * individual drinker was interrupted by the arrival of a table
 * server), so we must be careful to record only the first start.  We
 * do that by taking advantage of the fact that the constructor
 * initializes actualStartTime to -1.
 */
void BarRequester::start(
    ostream&		,		// Stream to report activity to
    SimulationTime	when)		// Time when service was started
    {
    assert(when >= scheduledStartTime);

    if (actualStartTime < 0)
	actualStartTime = when;
    }

/*
 * Finish service to a BarRequester.  This is a virtual function that
 * is overridden by the derived classes.  The base functionality
 * merely reports the end of service.  If the service took longer than
 * the scheduled service time, the amount of the excess is also
 * reported.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
void BarRequester::finish(
    ostream&		stream,		// Stream to report activity to
    SimulationTime	when)		// Time when service was finished
    {
    /*
     * Report type of requester, intended start time, and actual
     * service time.
     */
    stream << type() << " scheduled for " << scheduledStartTime
      << " serviced in " << actualServiceTime(when);

    /*
     * If the actual service time is excessive (due to queueing or
     * suspensions), report the excess.
     */
    if (actualServiceTime(when) > scheduledServiceTime)
      stream << " (" << actualServiceTime(when) - scheduledServiceTime
	<< " extra)";

    stream << endl;
    }

/*
 * Provide service to a BarRequester.  We simply update the amount of
 * service remaining to be provided.
 */
void BarRequester::giveService(
    SimulationTime	howMuchService)	// Amount of service to provide
    {
    assert(howMuchService > 0);

    remainingServiceTime -= howMuchService;
    }

/*
 * Find out if a BarRequester is done receiving service, which is
 * indicated by a remainingServiceTime of 0.
 */
bool BarRequester::isDone()
  const
    {
    assert(remainingServiceTime >= 0);

    return remainingServiceTime == 0;
    }

/*
 * Suspend service to a BarRequester, and report that fact to a
 * stream.  Suspension is handled by adding a penalty to the remaining
 * service time.  The remainder of suspension (i.e., putting the
 * requester back on the queue) is done by the simulation loop.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
void BarRequester::suspend(
    ostream&		stream,		// Stream to report suspension to
    SimulationTime	when)		// When suspension happened
    {
    remainingServiceTime += suspensionPenalty;

    stream << "Suspending " << type() << " scheduled for " << dueTime()
      << " at " << when << endl;
    }

/*
 * Set the static parameters for the BarRequester class.  These
 * parameters are used to control the behavior of the simulation.
 */
void BarRequester::setParameters(
    double		serviceTime_,	// Mean default service time
    const mt_distribution&
			servicePRNG_,	// PRNG to use for service times
    SimulationTime	suspensionPenalty_)
					// Penalty for suspending service
    {
    serviceTime = serviceTime_;
    servicePRNG = servicePRNG_;
    suspensionPenalty = suspensionPenalty_;
    }

/*
 * Calculate the actual amount of time needed to service a request.
 * This is defined as the difference between the scheduled start time
 * and the current time (which is presumably the completion time).
 */
SimulationTime BarRequester::actualServiceTime(
    SimulationTime	when)		// Time when service was completed
  const
    {
    assert (when >= scheduledStartTime);

    return when - scheduledStartTime;
    }

/*
 * Default constructor for Drinker.
 */
Drinker::Drinker()
  : BarRequester()
    {
    }

/*
 * From-parameters constructor for Drinker.  The scheduled start time
 * is provided by the caller; the service time is calculated as an
 * exponentially-distributed random number.
 */
Drinker::Drinker(
    SimulationTime	scheduledStartTime_)
					// Time drinker "arrives"
  : BarRequester(scheduledStartTime_,
      (int)(servicePRNG.exponential(serviceTime) + 0.5))
    {
    }

/*
 * Copy constructor for Drinker.
 */
Drinker::Drinker(
    const Drinker&	source)		// Drinker to copy
  : BarRequester(source)
    {
    }

/*
 * Destructor for Drinker.
 */
Drinker::~Drinker()
    {
    }

/*
 * Assignment operator for Drinker.
 */
Drinker& Drinker::operator=(
    const Drinker&	source)		// Drinker to assign from
    {
    /* 
     * Since we have no additional data, the BarRequester assignment
     * operator does all the work.
     */
    (BarRequester&)*this = source;
    return *this;
    }

/*
 * Return a string indicating the type of a Drinker.  This is a pure
 * virtual function overridden from the BarRequester class.
 */
string Drinker::type()
  const
    {
    return "drinker";
    }

/*
 * Begin service to a Drinker, and report that fact to a stream.  This
 * is a virtual function that is overridden from the base class.  Note
 * that we still use the base functionality.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
void Drinker::start(
    ostream&		stream,		// Stream to report activity to
    SimulationTime	when)		// Time when service was started
    {
    BarRequester::start(stream, when);	// Do the common part

    stream << "Starting drinker scheduled for " << dueTime()
      << " at " << when << endl;
    }

/*
 * Finish service to a Drinker.  This is a virtual function that
 * is overridden from the base class.  Note that we still use the base
 * functionality.
 */
void Drinker::finish(
    ostream&		stream,		// Stream to report activity to
    SimulationTime	when)		// Time when service was completed
    {
    BarRequester::finish(stream, when);	// Do the common part

    /* 
     * Record statistics about the service provided.
     */
    totalServices++;
    serviceStats.recordTime(actualServiceTime(when));
    }

/*
 * Report statistics regarding the overall service provided to all
 * Drinkers.  This is a static function that is called once at the end
 * of the simulation.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
ostream& Drinker::reportStatistics(
    ostream&		stream)		// Stream to report statistics to
    {
    stream << "Drinker statistics:\n";
    stream << '\t' << totalServices << " customers served" << endl;

    serviceStats.reportStatistics(stream, "\t");

    return stream;
    }

/*
 * Default constructor for TableServer.
 */
TableServer::TableServer()
  : BarRequester()
    {
    }

/*
 * From-parameters constructor for TableServer.  The scheduled start
 * time is provided by the caller.  The service time is calculated as
 * the sum of nDrinks exponentially-distributed random numbers; for
 * convenience this calculation is done by a private member function.
 */
TableServer::TableServer(
    SimulationTime	scheduledStartTime_,
    int			nDrinks_)
  : BarRequester(scheduledStartTime_, drinkServiceTime(nDrinks_)),
    nDrinks(nDrinks_)
    {
    }

/*
 * Copy constructor for TableServer.
 */
TableServer::TableServer(
    const TableServer&	source)		// TableServer to copy
  : BarRequester(source),
    nDrinks(source.nDrinks)
    {
    }

/*
 * Destructor for TableServer.
 */
TableServer::~TableServer()
    {
    }

/*
 * Assignment operator for TableServer.
 */
TableServer& TableServer::operator=(
    const TableServer&	source)
    {
    /* 
     * The BarRequester assignment operator does nearly all of the work.
     */
    (BarRequester&)*this = source;
    nDrinks = source.nDrinks;
    return *this;
    }

/*
 * Return the number of drinks requested by a TableServer.
 */
int TableServer::drinkCount()
  const
    {
    return nDrinks;
    }

/*
 * Return a string indicating the type of a TableServer.  This is a
 * pure virtual function overridden from the BarRequester class.
 */
string TableServer::type()
  const
    {
    return "server";
    }

/*
 * Begin service to a TableServer, and report that fact to a stream.
 * This is a virtual function that is overridden from the base class.
 * Note that we still use the base functionality.
 *
 * We finish our output lines with endl rather than '\n' so that we
 * will be able to see partial output if we crash.  This is a minor
 * tradeoff of performance for debuggability.
 */
void TableServer::start(
    ostream&		stream,		// Stream to report activity to
    SimulationTime	when)		// Time when service was started
    {
    BarRequester::start(stream, when);

    stream << "Starting server scheduled for " << dueTime()
      << " with " << drinkCount() << " drink(s) at " << when << endl;
    }

/*
 * Finish service to a TableServer.  This is a virtual function that
 * is overridden from the base class.  Note that we still use the base
 * functionality.
 */
void TableServer::finish(
    ostream&		stream,		// Stream to report activity to
    SimulationTime	when)		// Time when service was completed
    {
    BarRequester::finish(stream, when);

    /* 
     * Record statistics about the service provided.
     */
    totalServices++;
    serviceStats.recordTime(actualServiceTime(when));
    totalDrinksServed += nDrinks;
    }

/*
 * Report statistics regarding the overall service provided to all
 * TableServers.  This is a static function that is called once at the
 * end of the simulation.
 */
ostream& TableServer::reportStatistics(
    ostream&		stream)
    {
    stream << "Table server statistics:\n";
    stream << '\t' << totalServices << " servers sold "
      << totalDrinksServed << " drinks" << endl;

    serviceStats.reportStatistics(stream, "\t");

    return stream.flush();
    }

/*
 * Calculate the total amount of time that will be needed to provide
 * service to a TableServer.  This time is calculated as the sum of
 * the service times needed  to mix each of the individual drinks.
 * (Note that, for even moderately large numbers of drinks, the
 * Central Limit Theorem tells us that the total service time
 * approaches a Gaussian distribution rather than the exponential
 * distribution of the individual-drink service time.
 */
SimulationTime TableServer::drinkServiceTime(
    int			nDrinks)	// Number of drinks to calculate the
					// ..service time for
    {
    SimulationTime totalServiceTime = 0;

    for (int i = 0;  i < nDrinks;  i++)
	totalServiceTime +=
	  (SimulationTime)(int)(servicePRNG.exponential(serviceTime) + 0.5);

    assert (totalServiceTime >= 0);

    return totalServiceTime;
    }
