/*
 * CS 70, Fall 2001, Assignment 8
 *
 * Bar simulation
 *
 * This program simulates the operation of a small bar over a brief
 * period of time.  The bar has a single bartender, who mixes all
 * drinks, and an unspecified number of table servers.  The bartender
 * services both the table servers and the individual drinkers who sit
 * at the bar.  Table servers have priority; if a server arrives while
 * an individual drinker is being serviced, the bartender will suspend
 * making the indiviual's drink until the table server's order is
 * complete.
 *
 * The simulation operates by pre-generating a list of requests from
 * individual drinkers and a list of server requests.  These requests
 * are placed into two arrival queues before the actual simulation
 * begins.  The simulation is performed in the "barMonkey" function,
 * and is described more fully there.  It operates by stepping the
 * simulated time forward one second at a time, and at each step the
 * function decides what action to take.  Statistics are kept during
 * the run, and reported at the end of the simulation.
 *
 * All times used in the simulation, with the exception of the total
 * run time and the suspension penalty, are exponentially-distributed
 * random variables with a chosen mean.
 *
 * The parameters of the simulation can be modified with command-line
 * switches, as follows.
 *
 *	-D n	Specify the mean interarrival time for solo drinkers,
 *		in seconds.  (Default 1200 seconds, or 20 minutes.)
 *	-M n	Specify the maximum number of drinks a server can
 *		carry.  The number of drinks in any given request is
 *		uniformly distributed between 1 and this value,
 *		inclusive.  (Default 10 drinks.)
 *	-P n	Specify the penalty for suspending service to a
 *		drinker.  If an individual drinker is being serviced
 *		when a table server arrives, service to the individual
 *		drinker is suspended.  This parameter reflects the
 *		cost, in seconds, of that suspension.  The suspension
 *		cost is a constant, rather than a random variable.
 *		(Default 10 seconds).
 *	-r n	Specify the total run time for the simulation, in
 *		(fractional) hours.  Drinkers and table servers will
 *		stop arriving after this time interval elapses.  The
 *		simulation will usually continue running for a while
 *		after this time, due to service requests that remain
 *		to be completed.  (Default 5 hours.)
 *	-S n	Specify the random seed to be used to initialize the
 *		random-number generate.  If this switch is not given,
 *		the random seed will be chosen from the system time.
 *	-s n	Specify the mean interarrival time for table server,
 *		in seconds.  (Default 300 seconds, or 5 minutes.)
 *	-T n	Specify the mean service time needed by the bartender
 *		to mix a single drink.  Note that when the bartender
 *		is mixing drinks for a table server, several drink
 *		service times will be needed to complete service.
 *		(Default 40 seconds.)
 */

#include "barrequester.hh"
#include "list.hh"
#include "randomgeneration.hh"
#include "simulationconstants.hh"
#include "simulationtime.hh"

#include <assert.h>
#include <iostream>
#include <stdlib.h>
#include <string>

/*
 * Table of contents: the following functions are defined in this file.
 */
int			main(int argc, char* argv[]);
					// Main driver for the simulation
static int		processOptions(int argc, char* argv[],
			  double& drinkerArrivalTime,
			  double& serverArrivalTime,
			  int& serverMaxDrinks,
			  double& serviceTime,
			  SimulationTime& suspensionPenalty,
			  SimulationTime& simulationRunTime);
					// Process option arguments
static void		usage(char* progname);
					// Issue a usage message
static void		barMonkey(List<Drinker>& drinkerQueue,
			  List<TableServer>& serverQueue);
					// Perform the  actual function

/*
 * Main driver routine.  This function processes option arguments,
 * initializes the two request queues, and calls the barMonkey
 * function to perform the actual simulation.  Finally, it reports
 * statistics on the simulation to cout.
 */
int main(
    int			argc,		// Argument count
    char*		argv[])		// Argument vector.
    {
    /*
     * Parameters of the simulation.  All of these values are
     * (potentially) set by processOptions.
     */
    double		drinkerArrivalTime = DRINKER_ARRIVAL_TIME;
					// Interarrival time for solo drinkers
    double		serverArrivalTime = SERVER_ARRIVAL_TIME;
					// Interarrival time for table servers
    int			serverMaxDrinks = SERVER_MAX_DRINKS;
					// Maximum drinks a server can carry
    double		serviceTime = SERVICE_TIME;
					// Time needed to mix one drink
    SimulationTime	simulationRunTime = SIMULATION_RUN_TIME;
					// Total time simulation should run
    SimulationTime	suspensionPenalty = SUSPENSION_PENALTY;
					// Time penalty for suspending the
					// ..mixing of a drink

    /*
     * Process all option arguments.  Note that all but the first two
     * parameters are passed by reference, which means that the
     * function can (and does) modify them.  The return value is the
     * number of the first non-option argument on the command line.
     */
    int argNo = processOptions(argc, argv, drinkerArrivalTime,
      serverArrivalTime, serverMaxDrinks, serviceTime, suspensionPenalty,
      simulationRunTime);

    /*
     * There should be no non-switch arguments.
     */
    if (argNo != argc)
	usage(argv[0]);

    /*
     * Certain of our parameters need to be known to the BarRequester
     * class.  One way to make them available would be to put them in
     * global variables.  Another, chosen here, is to call a static
     * member function and let the class choose how to preserve them.
     */
    BarRequester::setParameters(serviceTime, suspensionPenalty);

    /*
     * Prepare for the simulation by generating a queue of individual
     * drinkers and a queue of servers.  It is both wasteful and
     * inaccurate to do things this way, but it's simpler.  It's
     * wasteful because we build long queues and use each element
     * once, rather than waiting to generate an element when it's
     * needed.  It's inaccurate because in real life a drinker doesn't
     * get in line to reorder until he or she has finished his drink,
     * and a table server doesn't return to the bar until the current
     * drinks have been distributed and a new order has been taken.  A
     * proper simulation would account for these phenomena, but doing
     * so would complicate the code (which is already too complex for
     * our purposes).
     */
    List<Drinker> drinkerQueue;		// This will hold all drinkers
    SimulationTime when = 0;		// This will be the next drinker's
					// ..arrival time
    while (when < simulationRunTime)
	{
	/*
	 * Since drinkerArrivalTime represents the INTER-arrival time
	 * for drinkers (i.e., the time between drinkers showing up),
	 * we can get the next arrival time by adding the interarrival
	 * time to the current one.  The expRand function returns an
	 * exponentially distributed random number.  We create the
	 * drinker and push him or her onto the tail of the queue in a
	 * single operation.
	 */
	when += (SimulationTime)expRand(drinkerArrivalTime);
	drinkerQueue.pushTail(Drinker(when));
	}

    /*
     * Repeat the above loop for table servers.
     */
    List<TableServer> serverQueue;	// This will hold all servers
    when = 0;
    while (when < simulationRunTime)
	{
	/*
	 * The calculation of the server arrival time is the same as
	 * above.  The second argument to the TableServer constructor
	 * is the number of drinks.  The expression we use will
	 * generate a uniform random distribution between 1 and the
	 * maximum number of drinks, inclusive.
	 */
	when += (SimulationTime)expRand(serverArrivalTime);
	serverQueue.pushTail(TableServer(when,
	  (int)((serverMaxDrinks - 1) * drand48()) + 1));
	}

    /*
     * We now have our arrival queues.  Run the simulation, and then
     * report final statistics to cout.
     */
    barMonkey(drinkerQueue, serverQueue);

    Drinker::reportStatistics(cout);
    TableServer::reportStatistics(cout);

    return 0;
    }

/*
 * Option processing.  Scan through the arguments, looking at
 * switches.  If we run into a non-switch argument, exit the function.
 *
 * If an illegal switch is detected, we will print a usage message to
 * cerr and terminate the program with an exit status of 2.
 *
 * The return value of this function is the number of the first
 * non-switch argument encountered.  If there are no non-switch
 * arguments, the return value equals argc.
 *
 * The reader will note that this function is fairly long, even though
 * it does relatively little.  That is generally true of
 * switch-processing functions, and it is not considered bad style so
 * long as the function limits itself to simple operations such as
 * setting Boolean flags.
 */
static int processOptions(
    int			argc,		// Argument count passed to main
    char*		argv[],		// Argument vector passed to main
	/*
	 * NOTE: All of the following arguments are modified if the
	 * associated flag appears.
	 */
    double&		drinkerArrivalTime,
					// Interarrival time for solo drinkers
    double&		serverArrivalTime,
					// Interarrival time for table servers
    int&		serverMaxDrinks,
					// Maximum drinks a server can carry
    double&		serviceTime,
					// Time needed to mix one drink
    SimulationTime&	suspensionPenalty,
					// Time penalty for suspending the
					// ..mixing of a drink
    SimulationTime&	simulationRunTime)
					// Total time simulation should run
    {
    /*
     * Before beginning the loop, we set defaults for all options.  By
     * doing the defaults in a single place, it is easy to change the
     * defaults later if necessary.
     *
     * One of the defaults is the random seed.  If the -s switch
     * appears, we can then clobber the random seed with the specified
     * value.
     */
    setRandomSeed();			// Pick a seed for the PRNG


    /*
     * Note that the loop index is modified inside the body of
     * the loop; this is generally poor style but is common in
     * argument processing.  In this case, the modification is done
     * inside the processing of the "s" switch.
     */
    for (int argNo = 1;  argNo < argc;  argNo++)
	{
	if (argv[argNo][0] != '-')	// End of switches?  If so, exit
	    return argNo;		// ..option processing

	/*
	 * All other switch options should consist of a single hyphen,
	 * an alphabetic character, and nothing else.  At this point
	 * we know that the hyphen exists.  Check to make sure that
	 * there is exactly one character following it.
	 */
	if (argv[argNo][1] == '\0'  ||  argv[argNo][2] != '\0')
	    usage(argv[0]);

	/*
	 * Process switch options.  We do this in a "switch" statement
	 * so that it is easy to add new options later.
	 */
	switch (argv[argNo][1])
	    {
	    case '-':			// --: end of arguments
		{
		/*
		 * By convention, the special switch option "--" means that
		 * all following arguments are non-option arguments, even if
		 * they start with a hyphen.  If we run into that switch, we
		 * must skip over it but then exit the option-processing loop.
		 */
		argNo++;		// Skip over the --
		return argNo;		// Go to non-option argument processing
		}

	    case 'D':			// -D n: drinker interarrival time
		{
		/*
		 * Processing an option that has an argument is a bit
		 * tricky.  First, we must make sure that there is
		 * actually a following argument.  Second, we must
		 * process that argument appropriately (in this case,
		 * by converting it to a "long" and passing it to
		 * srand48).  Third, we must make sure that the
		 * option-processing loop does not try to treat the
		 * argument as a switch itself.  We do that by
		 * incrementing the loop index, as noted in the
		 * comments before the loop.  The first and third
		 * operations are combined.
		 */
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		/*
		 * Convert the next argument to a double (atof) and
		 * save it.  If the next argument isn't a float, atof
		 * will probably return zero.  But that's OK, because
		 * the behavior of the program will still be
		 * reproducible.  This is a common Unix weakness,
		 * caused by the lack of a good library routine to
		 * check the syntax of a numeric string.
		 */
		drinkerArrivalTime = atof(argv[argNo]);
		break;
		}
	    case 'M':			// -M n: Max drinks per server
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		/*
		 * Here, we use atoi instead of atof because we're
		 * converting integers.
		 */
		serverMaxDrinks = atol(argv[argNo]);
		break;
		}
	    case 'P':			// -P n: suspension penalty
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		suspensionPenalty = atoi(argv[argNo]);
		break;
		}
	    case 'r':			// -r n: simulation run time, hours
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		simulationRunTime = (int)(atof(argv[argNo]) * 60.0 * 60.0);
		break;
		}
	    case 'S':			// -S n: specify random seed
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		/*
		 * Convert the next argument to a long (atol) and pass
		 * it to srand48.  If the next argument isn't an
		 * integer, atol will probably return zero.  But
		 * that's OK, because the behavior of the program will
		 * still be reproducible.
		 */
		srand48(atol(argv[argNo]));
		break;
		}

	    case 's':			// -s n: server interarrival time
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		serverArrivalTime = atof(argv[argNo]);
		break;
		}
	    case 'T':			// -T n: service time to mix a drink
		{
		if (++argNo >= argc)	// Make sure there's an argument
		    usage(argv[0]);	// ..if not, complain and exit

		serviceTime = atof(argv[argNo]);
		break;
		}
	    default:			// Default: unrecognized option
		{
		usage(argv[0]);
		break;
		}
	    }
	}

    /*
     * We can only get here if we ran out of arguments.  Return argc
     * to the caller, indicating that there are no non-option
     * arguments.
     */
    return argc;
    }

/*
 * Issue a usage message to cerr, and terminate the program with a
 * failure status.
 */
static void usage(			// Issue a usage message
    char*		progname)	// Name we were executed under
    {
    cerr << "Usage: " << progname
      << " [-D n] [-M n] [-P n] [-r n] [-S n] [-s n] [-T n]\n";
    cerr << "Switches:\n";
    cerr << "\t-D n\tSpecify solo drinker interarrival time.\n";
    cerr << "\t-M n\tSpecify maximum drinks a server can carry.\n";
    cerr << "\t-P n\tSpecify penalty for suspending service to a drinker.\n";
    cerr << "\t-r n\tSpecify simulation run time, in (floating) hours.\n";
    cerr << "\t-S n\tSpecify the random seed to be used.\n";
    cerr << "\t-s n\tSpecify table server interarrival time.\n";
    cerr << "\t-T n\tSpecify service time to mix a drink.\n";
    exit(2);
    }

/*
 * Simulate the operation of a bar.  The parameters to this function
 * are a queue of individual drinker requests and a queue of table
 * server requests.  These queues are destroyed by the function.
 *
 * // ADD STUFF
 */
static void barMonkey(
    List<Drinker>&	drinkerQueue,	// Queue of drinker requests
    List<TableServer>&	serverQueue)	// Queue of table server requests
    {
    /*
     * The single most important in the simulation is the simulated
     * time.  Here, we record the current simulated time (in seconds).
     */
    SimulationTime currentTime = 0;

    /*
     * THIS COMMENT SHOULD BE REMOVED FROM THE FINAL VERSION
     *
     * The main loop of the simulation should follow the following
     * rough pseudocode (note that you may need to add more variables):
     */

    // Note: "is still being served" does not equate to !isDone.  Why?
    while either queue has stuff OR somebody is still being served
	if (we are serving a drinker, and the dueTime of the next server
	  has arrived or passed)
	    suspend the current drinker and put him/her back on the head
	      of the drinker queue
	if (we are not serving somebody, and the dueTime of the next server
	  or drinker has arrived or passed)
	    extract somebody from the appropriate queue, giving priority
	      to table servers
	    start whomever we just extracted
	if we are serving somebody right now
	    if they are done
		call their finish function with cout and current time
	    else
		give them one second of service
		advance the current time by one second
	else
	    advance the current time by one second

    /*
     * THIS COMMENT SHOULD BE REMOVED FROM THE FINAL VERSION
     *
     * The above pseudocode can be improved, but as given it works.
     * (In particular, there are many situations where it is safe to
     * advance the time by more than just one second).
     */

    // ADD STUFF
    }
