/*
 * shuffle.cpp
 *
 * Simple file-shuffling program
 *
 * This program provides a simple file-shuffling command.  If invoked
 * with no arguments, it reads an entire file from the standard input
 * device, shuffles it, and writes the scrambled file to standard
 * output.  Optionally, it can instead read from one or more files
 * named on the command line (concatenating them together like 'cat').
 *
 * Usage:
 *
 * ./shuffle [-d] [-n] [-s n] [file] .
 *
 * The specified file(s) (or standard input if none are given) are
 * read and saved internally.  After all files have been swallowed,
 * they are shuffled and the result is written to standard output.
 *
 * Options:
 *
 *      -d      Give debugging output.
 *      -n      Print a line number before each output line.  The line
 *              numbers are assigned as the files are read, and are
 *              not reset between files.  Thus, the line numbers will
 *              be unique, but will appear on the output in a
 *              scrambled order.
 *      -s n    Specify a numeric random seed for the pseudo
 *              random-number generator.  This allows the program to
 *              produce reproducible results, which is useful in
 *              testing.
 *
 * User interaction is handled by main, storing and fetching the lines is
 * handled by the LineShuffler class.
 *
 */

// C++ Headers

#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <cerrno>
using namespace std;

// Unix Headers

#include <sys/time.h>

// Our Headers

#include "lineshuffler.hpp"
#include "shuffle-private.hpp"		// Predeclarations only.

/*
 * Note to CS 70 students.  The code below uses a C++ struct to store some
 # data.  In C++, writing
 *
 *   struct Foo {
 *    	...
 *   };
 *
 * means the same as
 *
 *   class Foo {
 *   public:
 *  	...
 *   };
 *
 * In practice, we use structs for "plain-old-data", where we're there
 * is no behavior associated with that data (i.e., there are no member
 * functions).  Unlike classes, this data is public -- usually some
 * other mechanism prevents others from meddling with the data; in
 * this case, the fact that we declare the class inside the
 * implementation file (and not in a header) means that we don't have
 * to worry about erroneous access to this internal data structure.
 *
 * Sometimes we might write a default constructor (as we do here).
 * If we do, it's almost always simple enough to be written inline.
 */
 
/*
 * We'll keep the options for our class together in a local structure.
 * Other modules won't be able to see this structure.
 */

struct ShuffleOptions {
    bool showDebugInfo;		// Is debugging turned on?
    bool showLineNumbers;	// Are we printing lineNumbers?
    bool useFixedSeed;		// Should we use a special seed value?
    int  fixedSeed;		// The seed value to use.
    
    ShuffleOptions()		// Default option values
        : showDebugInfo(false),
          showLineNumbers(false),
          useFixedSeed(false)
    {
        // Nothing (else) to do
    }
        
};

/* main [global function & program entry point]:
 *     In:	int argc    		command-line argument count +1 
 *     In:	const char* argv	command-line arguments (C strings)
 *     Returns: void			
 * 
 * Reads the options, then starts the shuffler.
 */
int main (int argc, const char* argv[])
{
    int argNo = 1;			// By tradition, argument zero is
					// the name of the program.  The first
					// actual arguments start at one.

    ShuffleOptions options = getOptions(argNo, argc, argv);
					// Remember, getOptions modifies argNo.

    return doShuffling(options, argNo, argc, argv);
}


/* doShuffling [local function]:
 *     In:	ShuffleOptions options	the options controlling shuffling
 *     In:	int firstArg		index of first argument to look at
 *     In:    	int argc    		index of last command-line argument +1 
 *     In:    	const char* argv	command-line arguments (C strings)
 *     Returns:	int			0 => success, non-zero => errors
 * 
 * Read lines from the user until a blank line is entered and then outputs
 * those lines in random order.
 */
static int doShuffling(const ShuffleOptions& options, int firstArg, 
		       int argc, const char* argv[])
{
    int failure = 0;    		// non-zero if something went wrong

    LineShuffler randomLines;

    if (firstArg == argc) {
	/*
	 * No more arguments: read from standard input.
	 */
	readFile(cin, randomLines, options.showLineNumbers);
    } else {
	/*
	 * Read from each remaining argument in turn.
	 */
	for (int argNo = firstArg + 1;  argNo < argc;  ++argNo) {
	    /*
	     * Open the next file.  If we can't open it, issue a message.
	     */
	    ifstream nextFile(argv[argNo]);
	    if (!nextFile) {
		/*
		 * We couldn't open the file.  Issue an error message
		 * (the strerror call will produce an explanation;
		 * it's defined in errno.h).  Then set the failure
		 * flag for later and continue with the next file.
		 */
		cerr << "Couldn't open '" << argv[argNo] << "': "
		  << strerror(errno) << endl;
		failure = 1;
		continue;
	    }
	    readFile(nextFile, randomLines, options.showLineNumbers);

	    /*
	     * At the end of the loop, "nextFile" will be
	     * automatically destroyed.  As a side effect, the
	     * associated file will be closed.  So we don't have to
	     * worry about that detail.
	     */
	}
    }

    while(!randomLines.isEmpty())
	cout << randomLines.removeLine() << endl;

    return failure;
}


/* readFile [local function]:
 *     InOut:	istream& in   		input stream to read from 
 *     InOut:	LineShuffler& bag	the bag into which lines are read
 *     In:	bool addLineNumbers	prefix lines with their line numbers? 
 *     Returns: void			
 * 
 * Read lines from the stream and put them into the bag until EOF is reached.
 */
static void readFile(istream& in, LineShuffler& bag, bool addLineNumbers) 
{
    int lineNumber = 1;
    for (;;) {
	string line;
        getline(in, line);
        if (!in)			// Test for EOF
            break;
        if (addLineNumbers) {
            line = stringify(lineNumber) + "\t" + line;
        }
	bag.addLine(line);
        ++lineNumber;
    }
}


/* usage [local function]:
 *     In:	const char* progname	name the program was executed under
 *     Returns: NEVER RETURNS			
 * 
 * Print a usage message and exit.
 */
static void usage(const char* progname)
{
    cerr << "Usage: " << progname << " [-n] [-s n] [file] ...\n";
    cerr << "Switches:\n";
    cerr << "\t-d\tGive debugging output\n";
    cerr << "\t-n\tAdd line numbers to the printed output.\n";
    cerr << "\t-s n\tSpecify the random seed to be used.\n";
    exit(2);
}


/* getOptions [local function]:
 *     InOut:	int& argNo		starting argument
 *     In:    	int argc    		command-line argument count +1 
 *     In:    	const char* argv	command-line arguments (C strings)
 *     Returns:	ShuffleOptions		the selected options
 * 
 * This function reads the command line options until the first non-option
 * argument.  On exit, argNo is updated to that argument, or argc if there
 * are no non-option arguments.
 */
static ShuffleOptions getOptions(int& argNo, int argc, const char* argv[])
{
    /*
     * 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.
     */
    ShuffleOptions options;

    /*
     * 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 (    ;  argNo <= argc;  ++argNo) {
	if (argv[argNo][0] != '-')      // End of switches?  If so, exit
	    return options;             // option processing.

	/*
	 * 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.
	 *
	 * We use C++ strings in the comparison here primarily for
	 * illustrative purposes.  Note that we must explicitly typecast
	 * one of the arguments of the == operator to "string" or the
	 * comparison won't do what we want -- and we WILL NOT get any
	 * compiler error messages!  */
	if (argv[argNo] == string("--")) {
	    argNo++;                    // Skip over the --
	    return options;             // Go to non-option argument 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][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 'd': {
		struct timeval tv;
		struct timezone tz;
		(void) gettimeofday (&tv, &tz);
		cerr << tv.tv_sec << '.' << tv.tv_usec << endl;
                options.showDebugInfo = true;
		break;
	    }
            
	    case 'n': {                 // -n: number lines on output
		options.showLineNumbers = true;
		break;
	    }

	    case 's': {                 // -s n: specify random seed
		/*
		 * 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 an int (atoi) and store it
                 * as the seed for our random number generator.
		 */
                options.useFixedSeed = true;
		options.fixedSeed = atoi(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 options;
}


/* stringify<PRINTABLE> [generic local function]:
 *     In:	PRINTABLE x		a value that could be printed
 *     Returns:	string			the selected options
 * 
 * This function converts any type that can be printed into a string.
 */
template <typename PRINTABLE>
static string stringify(const PRINTABLE& x)
{
    ostringstream out;
    if (out << x)
        return out.str();
    // some sort of error handling goes here...
    return "<conversion error?>";
} 
