// gradeLookUpDebug.cpp
//
// Illustrates use of output statements
// to debug a multi-function program.
//
// This program allows user to look up,
// modify, add, or delete student records
// records stored in text file quizRoster.txt.
// The records contain each student's ID,
// name, and 5 quiz scores.

#include "textUtility2.h"    // includes <iostream>
#include <fstream>

const int NUMBER_OF_QUIZZES = 5;
typedef float QuizArray[NUMBER_OF_QUIZZES];
const int MAX_NAME_LENGTH = 40;  // last name first;
                                 // includes spaces between
                                 // parts of name.
typedef char NameText[MAX_NAME_LENGTH + 1];

struct Student  {
   int iD;
   NameText name;
   QuizArray quizzes;
};

const int MAX_NUMBER_OF_STUDENTS = 40;
typedef Student Roster[MAX_NUMBER_OF_STUDENTS];

// ----------------------------------------
// ***** High-level tasks of program: *****
// ----------------------------------------
int lookUpStudent(const Roster roster,
                  int numberOfStudents);
void changeStudent(Roster roster,
                   int numberOfStudents);
void addNewStudent(Roster roster,
                   int& numberOfStudents);
void deleteStudent(Roster roster,
                   int& numberOfStudents);

// ------------------------------------------
// ***** Auxiliary to high-level tasks: *****
// ------------------------------------------
int askIDAndSearch(const Roster roster,
                   int numberOfStudents,
                   int& index);

// ----------------------------
// ***** Input functions: *****
// ----------------------------
bool readStudents(const char inputFilename[],
                  Roster roster,
                  int& numberOfStudents);
bool askForID(int& iD);
bool askForStudentData(Student& student);
bool userSaysYes(const char question[]);
char askSelection(const char question[],
                  const char choices[]);
void skipRestOfLine(istream& in);
void skipRestOfLine(istream& in, char& x);

// ------------------------------------
// ***** Computational functions: *****
// ------------------------------------
int searchForID(int iD,
                const Roster roster,
                int numberOfStudents);
char toUpperCase(char x);
bool equalsIgnoreCase(char x, char y);

// -----------------------------
// ***** Output functions: *****
// -----------------------------
void displayChoices();
void printStudent(const Student& student, ostream& out);
bool writeStudents(const char outputFilename[],
                   const Roster roster,
                   int numberOfStudents);

// ---------------------------------------------
// ******** End of declaration section *********
// ---------------------------------------------

/*
 * int main()
 *
 * This program allows user to look up,
 * modify, add, or delete student records
 * records stored in text file quizRoster.txt.
 * The records contain each student's ID,
 * name, and 5 quiz scores.
 *
 * The program first loads records from
 * text file, then displays menu of options.
 * After the user's requested task is complete,
 * the user is asked whether to display the
 * menu again.  The menu is thus displayed
 * repeatedly, each time allowing the user to
 * select a subsequent task, until the user asks
 * to quit or asks not to see the menu again.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *           An object of class istream,
 *           declared in header file <iostream>.
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 */
int main()
{
   // Read student data from data file:
   const char filename[] = "quizRoster.txt";
   Roster students;
   int numberOfStudents;
   if ( ! readStudents(filename,    // uses cout
                       students,
                       numberOfStudents) )
      return 1;

   // Later, whenever this program quits for
   // whatever reason, all modifications to
   // students, the Roster, should be saved
   // to the same data file from which it
   // was originally loaded.

   // Allow user to select tasks, in a loop
   // which quits when the user chooses or
   // when input fails:
   do {   // while user wants to continue

      // Display menu and prompt user selection:
      displayChoices();
      char question[] = "What do you want to do?";

      char choice = askSelection(question,    // uses cin
                                 "LCADQ");    // and cout

      // Perform the user-selected task:
      switch ( choice )
      {
         case 'L':
            lookUpStudent(students,           // uses cin
                          numberOfStudents);  // and cout
            break;

         case 'C':
            changeStudent(students,           // uses cin
                          numberOfStudents);  // and cout
            break;

         case 'A':
            addNewStudent(students,           // uses cin
                          numberOfStudents);  // and cout
            break;

         case 'D':
            deleteStudent(students,           // uses cin
                          numberOfStudents);  // and cout
            break;

         case 'Q':  // quit
            // Save modified data before quitting:
            writeStudents(filename,           // uses cout
                          students,
                          numberOfStudents);
            return 0;

         // No default case is needed, because the call
         // to askSelection ensures a valid response.

         default:  // ***** NEEDED FOR DEBUGGING ONLY *****
            cout << "switch in main: "
                    << "default case reached." << endl;
      } // switch

      // Quit if input has failed:
      if ( !cin )  {
         // Save modified data before quitting:
         writeStudents(filename,    // uses cout
                       students,
                       numberOfStudents);
         return 1;
      }  // if
   } while ( userSaysYes("Display menu again?") );


   // Save modified data before quitting:
   writeStudents(filename,    // uses cout
                 students,
                 numberOfStudents);

   return 0;
}  // function main


// ----------------------------------------
// ***** High-level tasks of program: *****
// ----------------------------------------


/*
 * int lookUpStudent(const Roster roster,
 *                   int numberOfStudents)
 *
 * Allows user to look up information about
 * a student in roster.  The user is prompted
 * for an ID number.  If a student with that
 * ID is found, the contents of the record
 * are displayed to the terminal.  Otherwise,
 * "Not found." is displayed.
 *
 * Parameters:
 *    roster - array of Student records.
 *    numberOfStudents - count of records
 *           in roster.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *           An object of class istream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cin is true,
 *           and cin is ready to begin a line.
 *      Postcondition:  The state of in is still
 *           true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *           true, and cout has NOT ended a line,
 *           so that input is typed next to prompt.
 *           (Echoed input then ends displayed line.)
 *
 * Returns:
 *    index of location of student with
 *       requested ID, if found.
 *    -1 if not found.
 */
int lookUpStudent(const Roster roster,
                  int numberOfStudents)
{
   // Ask user for student ID and search for it
   // in the roster, quitting if input fails:
   int index;
   askIDAndSearch(roster,    // uses cin and cout
                  numberOfStudents,
                  index);
   if ( !cin )   // (Error message has already been printed.)
      return -1;    // to indicate not found

   // Print information about student, if found;
   // otherwise indicate not found:
   if ( index == -1 )    // not found
      cout << "Not found." << endl;
   else
      printStudent(roster[index], cout);

   return index;
}  // function lookUpStudent


/*
 * void changeStudent(Roster roster,
 *                    int numberOfStudents)
 *
 * Modifies a student reoord in roster.
 * Prompts user for ID, then searches
 * for record with that ID.  If that
 * record is found, user is prompted
 * for new data for that record.
 * Otherwise, "Not found." is displayed.
 *
 * Parameters:
 *    roster - array of Student records.
 *       Precondition:  roster contains
 *          meaningful records in locations
 *          0 to (numberOfStudents-1).
 *       Postcondition:  one record has
 *          been modified, if requested ID
 *          was found.  Unchanged otherwise.
 *    numberOfStudents - count of records
 *          in roster.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *           An object of class istream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cin is true,
 *           and cin is ready to begin a line.
 *      Postcondition:  The state of in is still
 *           true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *           true, and cout has NOT ended a line,
 *           so that input is typed next to prompt.
 *           (Echoed input then ends displayed line.)
 */
void changeStudent(Roster roster,
                   int numberOfStudents)
{
   // Ask for ID of record to be changed,
   // then find its index in the roster
   // and display contents of record:
   int index = lookUpStudent(    // uses cin and cout
                      roster,
                      numberOfStudents);

   // Quit if student has not been found,
   // or if input has failed:
   if ( (index == -1) || !cin )
      return;   // (Error message has already been printed.)

   // We are now ready to ask the user for data
   // for a new student record and then copy it
   // into the roster, replacing old record.
   // A temporary second record is needed,
   // rather than immediate, direct modification
   // of the roster itself, to avoid an
   // incompletely modified entry in the roster
   // in the event of input failure while
   // interacting with the user.

   // Create new student record:
   Student student;
   student.iD = roster[index].iD;
   if ( ! askForStudentData(student) )   // uses cin
      return;                            // and cout

   // Copy into roster:
   roster[index] = student;

}  // function changeStudent


/*
 * void addNewStudent(Roster roster,
 *                    int& numberOfStudents)
 *
 * Adds a Student record to the roster, if
 * the requested new record is not a duplicate
 * of one already in the roster.  The user
 * is prompted for an ID number.  If there
 * is not already a record with that ID
 * number in the roster, the user is prompted
 * for other student data, and the new record
 * is added.
 *
 * Parameters:
 *    roster - array of Student records.
 *       Precondition:  roster contains
 *          meaningful records in locations
 *          0 to (numberOfStudents-1).
 *       Postcondition:  one record has
 *          been added, if there is not
 *          already a record with requested
 *          ID in roster.  Unchanged otherwise.
 *    numberOfStudents - count of records
 *          in roster.
 *       Precondition:  numberOfStudents is
 *          count of meaningful records
 *          previously in roster.
 *       Postcondition:  numberOfStudents
 *          is increased by 1 if a record
 *          is added.  Unchanged otherwise.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *           An object of class istream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cin is true,
 *           and cin is ready to begin a line.
 *      Postcondition:  The state of in is still
 *           true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *           true, and cout has NOT ended a line,
 *           so that input is typed next to prompt.
 *           (Echoed input then ends displayed line.)
 */
void addNewStudent(Roster roster,
                   int& numberOfStudents)
{
   // A student can be added only if the roster
   // is not already full:
   if ( numberOfStudents >= MAX_NUMBER_OF_STUDENTS )  {
      cout << "Roster full." << endl;
      return;
   }  // if

   // Ask user for student ID and search for it
   // in the roster, quitting if input fails:
   int index;
   int iD = askIDAndSearch(roster,     // uses cin and cout
                           numberOfStudents,
                           index);
   if ( ! cin )
      return;   // (Error message has already been printed.)

   // It's OK to add a new student with a particular
   // ID only if a student with that ID has not
   // been found.  If such a student has been
   // found, print an error message and quit.
   if ( index != -1 )  {
      cout << "Student already in roster:" << endl;
      printStudent(roster[index], cout);
      return;
   }  // if

   // Add a student at the next available
   // location, quitting if input fails:
   index = numberOfStudents;
   roster[index].iD = iD;
   if ( ! askForStudentData(roster[index]) )   // uses cin
      return;                                  // and cout

   // Now that a student has been
   // added successfully, increment
   // count of students in roster:
   numberOfStudents++;
}  // function addNewStudent


/*
 * Deletes a Student record from roster,
 * if a record with a user-specified ID is
 * found in the roster.  The user is
 * prompted for an ID number.  If it is
 * found, the contents of the record are
 * displayed, and the user is asked to
 * confirm desire to delete.  If the user
 * says yes, the record is deleted.  If
 * the user says no, or if a record with
 * the requested ID was not found, roster
 * is unchanged.
 *
 * Parameters:
 *    roster - array of Student records.
 *       Precondition:  roster contains
 *          meaningful records in locations
 *          0 to (numberOfStudents-1).
 *       Postcondition:  one record has
 *          been deleted, if requested ID
 *          was found, and all records with
 *          a higher index are moved one
 *          location down to fill gap.  If
 *          requested ID was not found,
 *          roster is unchanged.
 *    numberOfStudents - count of records
 *          in roster.
 *       Precondition:  numberOfStudents is
 *          count of meaningful records
 *          previously in roster.
 *       Postcondition:  numberOfStudents
 *          is decreased by 1 if a record
 *          is deleted.  Unchanged otherwise.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *          An object of class istream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cin is true,
 *          and cin is ready to begin a line.
 *       Postcondition:  The state of in is still
 *          true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *          An object of class ostream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *          and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *          true, and cout has NOT ended a line,
 *          so that input is typed next to prompt.
 *          (Echoed input then ends displayed line.)
 */
void deleteStudent(Roster roster,
                   int& numberOfStudents)
{
   // Ask for ID of record to be changed,
   // then find its index in the roster
   // and display contents of record:
   int index = lookUpStudent(    // uses cin and cout
                      roster,
                      numberOfStudents);

   // Quit if student has not been found,
   // or if input has failed:
   if ( (index == -1) || !cin )
      return;   // (Error message has already been printed.)

   // Assertion:  If we have reached this point,
   // a student record has been found and displayed.

   // Verify user's intent to delete this student:
   char question[] = "Do you really want to delete this student?";
   bool deleteOK = userSaysYes(question);

   // If user has said yes, delete student:
   if ( deleteOK )  {

      // Slide all higher-indexed student records down
      // one location each, overwriting record to delete:
      for ( int i = index + 1; i < numberOfStudents; i++ )
          roster[i-1] = roster[i];

      // Decrement count of students in roster:
      numberOfStudents--;
   }  // if

}  // function deleteStudent


// ------------------------------------------
// ***** Auxiliary to high-level tasks: *****
// ------------------------------------------


/*
 * int askIDAndSearch(const Roster roster,
 *                    int numberOfStudents,
 *                    int& index)
 *
 * Prompts user for a student ID, then
 * searches for it in roster.  Returns
 * requested ID and sets parameter
 * index to indicate location in roster.
 *
 * Parameters:
 *    roster - array of Student records.
 *    numberOfStudents - count of records
 *         in roster.
 *    index - location of requested student's
 *          record in roster.
 *       Preconditions:  none.
 *       Postconditions:  index is location
 *          of record of student with requested
 *          ID, if found; -1 if not found.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *          An object of class istream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cin is true,
 *          and cin is ready to begin a line.
 *       Postcondition:  The state of in is still
 *          true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *          An object of class ostream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *          and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *          true, and cout has NOT ended a line,
 *          so that input is typed next to prompt.
 *          (Echoed input then ends displayed line.)
 *
 * Returns:
 *    The ID number entered by the user,
 *       if user entered it successfully.
 *    -1 if input fails.
 */
int askIDAndSearch(const Roster roster,
                   int numberOfStudents,
                   int& index)
{
   // Ask user for student ID,
   // quitting if input fails:
   int iDToFind;
   if ( ! askForID( iDToFind ) )  {     // uses cin
                                        // and cout
      index = -1;   // to indicate not found
      return -1;    // to indicate input failure
   }  // if

   // Search for student with requested ID,
   // obtaining index of student in roster
   // (or -1 if not found), then return ID:
   index = searchForID(iDToFind,
                       roster,
                       numberOfStudents);
   return iDToFind;
}  // function askIDAndSearch


// ----------------------------
// ***** Input functions: *****
// ----------------------------


/*
 * bool readStudents(const char inputFilename[],
 *                   Roster roster,
 *                   int& numberOfStudents)
 *
 * Reads all the student information in
 * the specified text file and loads it,
 * as one record per student, into roster.
 *
 * An error message is printed if the
 * file is not found, is not readable,
 * or contains a number format error
 * beyond the first item on a line.
 *
 * Parameters:
 *    inputFilename - filename of text file
 *          to read from.  The format of the
 *          text file must be:  one student
 *          per line, with columns for (1) ID,
 *          (2) 5 quiz scores, and (3) names,
 *          last names first.  All data items
 *          on a line are separated by
 *          whitespace (spaces or tabs).
 *    roster - an array of Student records.
 *       Precondition:  none.
 *       Postcondition:  roster contains all
 *          the student information in the
 *          input file, if input has not failed.
 *    numberOfStudents - count of records in roster.
 *       Precondition:  none.
 *       Postcondition:  number of students
 *          is the number of records that
 *          have been read into roster.
 *
 * Global variable:
 *    cout - output stream to terminal window.
 *         An object of class ostream,
 *         declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *         and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *         true, and cout has ended a line.
 *
 * Returns:
 *    true if input was successful, false otherwise.
 */
bool readStudents(const char inputFilename[],
                  Roster roster,
                  int& numberOfStudents)
{
   // Prepare to read from input file:
   ifstream inFile;
   inFile.open(inputFilename);
   if ( !inFile )  {
      cout << "Could not read "
                  << inputFilename << "." << endl;
      return false;
   }  // if

   // Read data for each student into a location
   // in roster array corresponding to the number
   // of student records previously read:
   numberOfStudents = 0;    // records previously read
   while ( numberOfStudents < MAX_NUMBER_OF_STUDENTS )  {

      // Attempt to read ID, first item on line:
      inFile >> roster[numberOfStudents].iD;

      // Quit reading if at end of file:
      if ( !inFile )
         break;

      // Read quiz scores:
      for ( int i = 0; i < NUMBER_OF_QUIZZES; i++ )
         inFile >> roster[numberOfStudents].quizzes[i];

      // Check for format error:
      if ( !inFile )  {
          cout << inputFilename
                   << " format error at line "
                   << (numberOfStudents + 1) << endl;
          return 1;
      }  // if

      // skip white space:
      char x;
      do  {
         inFile.get(x);
      } while ( x == ' ' || x == '\t' );

      // Read name:
      int nameIndex = 0;
      while ( x != '\n' && nameIndex < MAX_NAME_LENGTH )
      {
         roster[numberOfStudents].name[nameIndex] = x;
         inFile.get(x);
         nameIndex++;
      }  // while

      // Skip any remaining characters on the line:
      skipRestOfLine(inFile, x);
      while ( x != '\n')
         inFile.get(x);

      // Terminate C-string:
      roster[numberOfStudents].name[nameIndex] = '\0';

      // next Student in array:
      numberOfStudents++;
   }  // while

   // Release handle on file, so it can be re-used:
   inFile.close();

   return true;
}  // function readStudents


/*
 * bool askForID(int& iD)
 *
 * Prompts the user to enter an ID number,
 * and then reads it.
 *
 * An error message is printed if input fails.
 *
 * Parameter:
 *    iD - a student's ID number.
 *       Precondition:  none.
 *       Postcondition:  iD is number typed by user.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *          An object of class istream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cin is true.
 *       Postcondition:  The state of in is still
 *          true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *          An object of class ostream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *          and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *          true, and cout has NOT ended a line,
 *          so that input is typed next to prompt.
 *          (Echoed input then ends displayed line.)
 *
 * Returns:
 *    true if input was successful,
 *    false otherwise.
 */
bool askForID(int& iD)
{
   cout << "Enter student ID number:>";
   cin >> iD;
   if ( !cin )  {
      cout << "You must enter integers only." << endl;
      return false;
   }  // if
   skipRestOfLine(cin);
   return true;
}  // function askForID


/*
 * bool askForStudentData(Student& student)
 *
 * Prompts user for all data about a student
 * except the ID number.
 *
 * An error message is printed if input fails.
 *
 * Parameter:
 *    student - a Student record.
 *       Preconditions:  none.
 *       Postconditions:  student contains
 *          data entered by user for all fields
 *          except iD, which is unchanged.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *           An object of class istream,
 *           declared in header file <iostream>.
 *       Precondition:  The state of cin is true,
 *           and cin is ready to begin a line.
 *       Postcondition:  The state of in is still
 *           true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *           true, and cout has NOT ended a line,
 *           so that input is typed next to prompt.
 *           (Echoed input then ends displayed line.)
 *
 * Returns:
 *    true if input is successful,
 *    false otherwise.
 */
bool askForStudentData(Student& student)
{
   // Prompt for student name:
   cout << "Enter student name (last name first): ";

   // Read name:
   char x;
   cin.get(x);
   int nameIndex = 0;
   while ( x != '\n' && nameIndex < MAX_NAME_LENGTH )
   {
      student.name[nameIndex] = toUpperCase(x);
      cin.get(x);
      nameIndex++;
   }  // while

   // Skip any remaining characters on the line:
   skipRestOfLine(cin, x);

   // Terminate C-string:
   student.name[nameIndex] = '\0';

   // Prompt for quiz scores:
   cout << "Enter " << NUMBER_OF_QUIZZES
                    << " quiz scores:";

   // Read quiz scores:
   for ( int i = 0; i < NUMBER_OF_QUIZZES; i++ )  {
      cin >> student.quizzes[i];
      if ( !cin )  {
         cout << "Non-numeric quiz score entry." << endl;
         return false;
      }  // if
   }  // for i

   // Finish input line and quit:
   skipRestOfLine(cin);
   return true;
}  // function askForStudentData


/*
 * bool userSaysYes(const char question[])
 *
 * Prompts user to answer a yes/no question,
 * then reads the user's answer of 'Y', 'y',
 * 'N', or 'n'.  If the user's answer is
 * anything other than 'Y', 'y','N', or 'n',
 * then the user is prompted repeatedly until
 * a valid response is obtained.
 *
 * Parameter:
 *    question - a yes/no question to ask user.
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *          An object of class istream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cin is true,
 *          and cin is ready to begin a line.
 *       Postcondition:  The state of in is still
 *          true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *          An object of class ostream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *          and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *          true, and cout has NOT ended a line,
 *          so that input is typed next to prompt.
 *          (Echoed input then ends displayed line.)
 *
 * Returns:
 *    true if user typed 'Y' or 'y'.
 *    false if user typed 'N' or 'n'.
 */
bool userSaysYes(const char question[])
{
   char choices[] = "YN";
   char x = askSelection(question, choices);

   cout << "userSaysYes: x=" << x << " returns: "; //****** DEBUG
   cout << "userSaysYes: returns "
             << equalsIgnoreCase(x, 'Y') << endl;  //****** DEBUG

   return equalsIgnoreCase(x, 'Y');
}  // function userSaysYes


/*
 * char askSelection(const char question[],
 *                   const char choices[])
 *
 * Prompts user to answer a question
 * by typing one of the characters in
 * a list of permitted responses.
 * If the user types an invalid response,
 * the user is prompted repeatedly until
 * a valid response is obtained.
 *
 * Parameters:
 *    question - question to ask user, not
 *       including list of valid responses.
 *    choices - array of valid character
 *       responses.  No two of the characters
 *       should be equal, ignoring case.
 *       (The array is terminated by '\0',
 *       like any C-string.)
 *
 * Global variables:
 *    cin - input stream from keyboard.
 *          An object of class istream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cin is true,
 *          and cin is ready to begin a line.
 *       Postcondition:  The state of in is still
 *          true, and cin has ended a line.
 *    cout - output stream to terminal window.
 *          An object of class ostream,
 *          declared in header file <iostream>.
 *       Precondition:  The state of cout is true,
 *          and cout is ready to begin a line.
 *       Postcondition:  The state of cout is still
 *          true, and cout has NOT ended a line,
 *          so that input is typed next to prompt.
 *          (Echoed input then ends displayed line.)
 *
 * Returns:
 *    the user's response, guaranteed to be one
 *    of the characters in choices, uppercased.
 */
char askSelection(const char question[],
                  const char choices[])
{
   // Determine length of choices string:
   int index = 0;
   while ( choices[index] != '\0' )
      index++;
   const int length = index;

   // It is pointless to call this function for
   // fewer than 2 choices.  In that case,
   // print an error message and quit:
   if (length < 2)  {
      cout << "Error: askSelection called with only "
                 << length << " choice." << endl;
      return choices[0];
   }  // if

   // Assertion: If we have reached this point,
   // the length of choices is >= 2.

   // Prompt user with question and choices:
   cout << question << " ("
                << toUpperCase(choices[0]);
   for ( int i = 1; i < length; i++ )
      cout << "/" << toUpperCase(choices[i]);
   cout << "):>";

   // Read user response.  If it's not a valid
   // selection, prompt user repeatedly until
   // a valid response is obtained.
   char x;
   while ( true )  {

      // Read character typed by user,
      cin.get(x);

      // Finish reading line, but only if
      // x is not already '\n'.
      char y = x;   // So that, if x is '\n'
      skipRestOfLine(cin, y);

      // Assertions:  cin has just read '\n';
      //    and y is now '\n', but x is still
      //    the first character on the line.

      cout << "askSelection: x=" << x << endl;  // ***** DEBUG

      // Determine whether character x
      // is a valid selection:
      bool match = false;
      for ( int i = 0; (i < length) && !match; i++ )  {
         if ( equalsIgnoreCase(x, choices[i]) )
            match = true;
         cout << "askSelection: choices[" << i << "]="
                << choices[i] << endl;  // ***** DEBUG
      }  // for i

      cout << "askSelection: match=" << match << endl;  // ***** DEBUG

      // If x is a valid selection, there
      // is no need to nag user, so quit,
      // returning user's selection:
      if ( match )
         return toUpperCase(x);

      // Nag user for valid selection:
      if ( length == 2 )
         cout << "Please enter "
              << toUpperCase(choices[0]) << " or "
              << toUpperCase(choices[1]) << ":>";
      else  {
         cout << "Please enter ";
            for ( int i = 0; i < length - 1; i++ )
               cout << toUpperCase(choices[i]) << ", ";
         cout << " or "
              << toUpperCase(choices[length-1])
              << ":>";
      }  // else
   }  // while
}  // function askSelection


/*
 * void skipRestOfLine(istream& in)
 *
 * Skips one character, then skips any
 * remaining characters on the line,
 * up to and including end-of-line marker.
 * If a call to this function is intended
 * only to finish the current line of input
 * and not skip an entire line, then cin
 * must NOT be ready to begin a new line.
 *
 * The end-of-line marker is assumed to
 * consist of or end with a newline
 * character ('\n').
 *
 * Parameter:
 *    in - an input stream.
 *       Precondition:  The state of in is true.
 *       Postcondition:  The state of in is still
 *           true, and in has ended a line.
 */
void skipRestOfLine(istream& in)
{
   char x;
   do  {
      in.get(x);
   } while ( x != '\n' );   // Unix end-of-line
}  // function skipRestOfLine(istream&)


/*
 * void skipRestOfLine(istream& in, char& x)
 *
 * Reads any remaining characters on the
 * current line of input.
 *
 * The end-of-line marker is assumed to
 * consist of or end with a newline
 * character ('\n').
 *
 *    in - an input stream.
 *       Precondition:  The state of in is true.
 *       Postcondition:  The state of in is still
 *          true, and in has ended a line.  If
 *          x was originally '\n' (which should
 *          be the case only if in was originally
 *          ready to begin a reading a line), then
 *          no more characters have been read.
 *    x - the character most recently read,
 *          either by this function (if it reads
 *          any characters) or previously.
 *       Precondition:  x is the character most
 *          recently read before this function
 *          was called.
 *       Postcondition:  x equals '\n'.
 */
void skipRestOfLine(istream& in, char& x)
{
   while ( x != '\n' )   // Unix end-of-line
      in.get(x);
}  // function skipRestOfLine(istream&, char&)


// ------------------------------------
// ***** Computational functions: *****
// ------------------------------------


/*
 * int searchForID(int iD,
 *                 const Roster roster,
 *                 int numberOfStudents)
 *
 * Searches roster for a student with
 * a particular ID number.
 *
 * Parameters:
 *    iD - ID number to search for.
 *    roster - array of Student records.
 *    numberOfStudents - count of records
 *       in roster.
 *
 * Returns:
 *    index of location of student with
 *       requested ID, if found.
 *    -1 if not found.
 */
int searchForID(int iD,
                const Roster roster,
                int numberOfStudents)
{
   for (int i = 0; i < numberOfStudents; i++)
      if ( iD == roster[i].iD )
         return i;
   return -1;
}  // function searchForID


/*
 * Returns an uppercase version of
 * a lowercase character, or the
 * character itself, if the original
 * character was not lowercase.
 *
 * Parameter:
 *    x - any character.
 *
 * Returns:
 *    An uppercase character corresponding
 *       to x, if x was lowercase.
 *    x, otherwise.
 */
char toUpperCase(char x)
{
   if ( x >= 'a' && x <= 'z' )  // if lowercase,
      return (x + 'A' - 'a');   // return uppercase version.
   return x;                    // else, return unchanged.
}  // function toUpperCase


/*
 * bool equalsIgnoreCase(char x, char y)
 *
 * Determines whether two characters
 * are equal, ignoreing case.
 *
 * Parameters:
 *    x - one of the characters to be compared.
 *    y - the other character to be compared.
 *
 * Returns:
 *    true if x and y are equal, ignoring case.
 *    false otherwise.
 */
bool equalsIgnoreCase(char x, char y)
{
   cout << "equalsIgnoreCase: x=" << x
                         << " y=" << y << endl;  // **** DEBUG
   return ( toUpperCase(x) == toUpperCase(y) );
}  // function equalsIgnoreCase


// -----------------------------
// ***** Output functions: *****
// -----------------------------

/*
 * void displayChoices()
 *
 * Displays, to the terminal windo, a menu of
 * this program's high-level capabilities.
 *
 * Global variable:
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *           true, and cout has ended a line.
 */
void displayChoices()
{
   cout << "Welcome to Quiz Grade Look-Up." << endl;
   cout << "You may do any of the following:"
          << endl;
   cout << endl;
   cout << "     L) Look up a student's quiz grades."
          << endl;
   cout << "     C) Change a student's record."
          << endl;
   cout << "     A) Add a new student." << endl;
   cout << "     D) Drop (delete) a student."
          << endl;
   cout << "     Q) Quit." << endl;
   cout << endl;
}  // function displayChoices


/*
 * void printStudent(const Student& student, ostream& out)
 *
 * Prints a line of text containing information
 * about one student, in a format conducive to neat
 * columns if this function is called repeatedly.
 * Prints a student's ID number, quiz scores, and name.
 *
 * Parameters:
 *    student - record containing information about
 *      one student.
 *    out - an output stream.
 *      Precondition:  The state of out is true,
 *           and out is ready to begin a line.
 *      Postcondition:  The state of out is still
 *           true, and out has ended a line.
 */
void printStudent(const Student& student, ostream& out)
{
   const int ID_WIDTH = 9;
   int leadingZeroes = ID_WIDTH - digitCount(student.iD);
   for ( int i = 0; i < leadingZeroes; i++ )
      out << "0";
   out << student.iD;
   out << "   ";
   for ( int i = 0; i < NUMBER_OF_QUIZZES; i++ )
      printRightJustified(student.quizzes[i], 1, 5, out);
   out << "     " << student.name << endl;
}  // function printStudent


/*
 * bool writeStudents(const char outputFilename[],
 *                    const Roster roster,
 *                    int numberOfStudents)
 *
 * Outputs all the student records in roster
 * to the specified text file.
 *
 * An error message is printed if the
 * file cannot be written to.
 *
 * Parameters:
 *    outputFilename - filename of text file
 *         to write to.
 *    roster - an array of Student records.
 *    numberOfStudents - count of records in roster.
 *
 * Global variable:
 *    cout - output stream to terminal window.
 *           An object of class ostream,
 *           declared in header file <iostream>.
 *      Precondition:  The state of cout is true,
 *           and cout is ready to begin a line.
 *      Postcondition:  The state of cout is still
 *           true, and cout has ended a line.
 *
 * Returns:
 *    true if output was successful, false otherwise.
 */
bool writeStudents(const char outputFilename[],
                   const Roster roster,
                   int numberOfStudents)
{
   // Prepare to write to file:
   ofstream outFile;
   outFile.open(outputFilename);
   if ( !outFile )  {
      cout << "Could not write to "
                  << outputFilename << "." << endl;
      return false;
   }  // if

   // Write all students in roster to output file:
   for ( int i = 0; i < numberOfStudents; i++ )
      printStudent(roster[i], outFile);

   // Release handle on file, so it can be re-used:
   outFile.close();

   return true;
}  // function writeStudents