/*
 * dbmanager.cpp
 *
 * Implementation of the RegDB_Manager class. See header file for function
 * descriptions.
 */

#include "dbmanager.hpp"
#include "course.hpp"
#include "student.hpp"
#include "regdb.hpp"

RegDB_Manager::RegDB_Manager(Registrar_DB& db)
    : db_(db)
{
    // Nothing (else) to do.
}


/*
 * Interactive interface to a registrar's database.  The user is
 * repeatedly prompted for a command.  The command is then decoded and
 * dispatched to an appropriate routine for execution.
 */
void RegDB_Manager::interact()
{
    string              command;        // Command currently being executed

    /*
     * Before beginning, print a command list so the user has an idea of
     * how this program works.
     */
    printCommandList(cerr);

    /*
     * Main loop. We repeatedly get a command from cin; if EOF is
     * encountered we will exit the loop.
     *
     * If a command was returned, the body of the loop will classify
     * and execute it.
     */
    for (;;) {
        /*
         * Get a command from the user.  If EOF is hit, leave the loop.
         */
        command = promptRead("\nYour command? ", cerr, cin);
        if (!cin)
            break;

        /*
         * Figure out what command was entered, and perform it.
         */
        if (command == "quit")
            return;

        else if (command == "help"  ||  command == "?")
            printCommandList(cerr);

        else if (command == "student")
            addStudent();

        else if (command == "course")
            addCourse();

        else if (command == "enroll")
            enroll();

        else if (command == "drop")
            drop();

        else if (command == "hair")
            changeHair();

        else if (command == "time")
            changeTime();

        else if (command == "show") {
            /*
             * The show command takes a parameter telling what to
             * show.  One can argue that the following code belongs in
             * a "doShow" routine.
             */
            string showWhat;
            if (!(cin >> showWhat))
                return;                 // EOF, give up

            if (showWhat == "students")
                showStudents();

            else if (showWhat == "courses")
                showCourses();

            else
                cerr << "Unrecognized argument to 'show' command: '"
                  << showWhat << "'\n";
        }

        else
            cerr << "Unrecognized command '" << command << "'.  Try again.\n";
    }
}

/*
 * Print a list of all commands in the interactive interface.
 */
void RegDB_Manager::printCommandList(ostream& out)         // Stream to print to
{
    out << '\n';
    out << "Commands are:\n";
    out << "  quit: terminate program\n";
    out << "  help: print this message\n";
    out << "  student: add new student\n";
    out << "  course: add new course\n";
    out << "  enroll: enroll student in course\n";
    out << "  drop: drop student from course\n";
    out << "  hair: change student's hair color\n";
    out << "  time: change course's meeting time\n";
    out << "  show students: display all students\n";
    out << "  show courses: display all courses\n";
}

/*
 * Ask the user for a string (using a caller-supplied prompt), read
 * it, and return it.  The hard work is done by the cin functions.
 */
string RegDB_Manager::promptRead(
    const string&       prompt,         // String to prompt with
    ostream&            out,  		// Stream to prompt to
    istream&            in)		// Stream to read answer from
{
    out << prompt << flush;
    string response;
    in >> response;
    return response;
}

/*
 * Perform the add-student command.  After error-checking, we prompt
 * for the student's parameters and then insert him/her in the
 * database using functions of Registrar_DB.
 */
void RegDB_Manager::addStudent()
{
    /*
     * Make sure there's room.
     */
    if (!db_.canAcceptStudents()) {
        cerr << "Student roster already full." << endl;
        return;
    }

    /*
     * Get student parameters.  Be careful to check for EOF at each step.
     */
    const string firstName = promptRead("First name? ", cerr, cin);
    if (!cin)
        return;

    const string lastName = promptRead("Last name? ", cerr, cin);
    if (!cin)
        return;

    const string hairColor = promptRead("Hair color? ", cerr, cin);
    if (!cin)
        return;

    /*
     * We have all the information we need.  Use the database's
     * functions to insert the student.
     */
    if (!db_.newStudent(firstName, lastName, hairColor)) {
        cerr << "Student " << lastName << ", " << firstName
          << " is already in the database\n";
        return;
    }
}

/*
 * Perform the add-course command.  After error-checking, we prompt
 * for the course's parameters and then insert it in the
 * database using functions of Registrar_DB.
 */
void RegDB_Manager::addCourse()
{
    if (!db_.canAcceptCourses()) {
        cerr << "Course list is already full." << endl;
        return;
    }

    /*
     * Get all parameters from the user.
     */
    string department = promptRead("Department? ", cerr, cin);
    if (!cin)
        return;

    cerr << "Course number? " << flush;
    int courseNumber;
    if (!(cin >> courseNumber))
        return;

    string meetingDays = promptRead("Meeting days? ", cerr, cin);
    if (!cin)
        return;

    string meetingHour = promptRead("Meeting time? ", cerr, cin);
    if (!cin)
        return;

    cerr << "Maximum enrollment? " << flush;
    int maxEnrollment;
    if (!(cin >> maxEnrollment))
        return;

    /*
     * We now have everything needed by the registrar database.  Add
     * the course.
     */
    if (!db_.newCourse(department, courseNumber,
      meetingDays + " " + meetingHour, maxEnrollment)) {
        cerr << "Course " << department << courseNumber
          << " is already in the database\n";
        return;
    }
}

/*
 * Perform the enroll-in-course command.  We prompt for the student's
 * name and course information, and then use functions of Registrar_DB
 * to perform the enrollment.
 */
void RegDB_Manager::enroll()
{
    /*
     * Prompt for the student and find him/her.
     */
    Student* student = getStudentByName();
    if (student == NULL)
        return;                         // Not found, error already printed

    /*
     * If the student would be overloaded, give up now.
     */
    if (student->hasMaximumLoad()) {
        cerr << "Student " << student->name()
          << " is at maximum load, can't add another course\n";
        return;
    }

    /*
     * Prompt for the course and find it.
     */
    Course* course = getCourseByName();
    if (course == NULL)
        return;                         // Not found, error already printed

    /*
     * If the course is oversubscribed, give up now.
     */
    if (course->isFull()) {
        cerr << "Course ";
        course->showName(cerr);
        cerr << " is full, can't add another student\n";
        return;
    }

    /*
     * Add the course to the student's schedule; as a side effect,
     * also insert him/her in the course's roster.
     *
     * The way the classes are written in the sample solution, we
     * could call either student->addCourseToSchedule or
     * course->addStudent with the same effect.  The choice is
     * arbitrary.
     */
    if (!student->addCourseToSchedule(course))
        cerr << "Student " << student->name() << " was already in course\n";
}

/*
 * Perform the drop-from-course command.  This is almost identical to
 * doEnroll (above), except we can omit some error checking.
 */
void RegDB_Manager::drop()
{
    /*
     * Prompt for the student and find him/her.
     */
    Student* student = getStudentByName();
    if (student == NULL)
        return;                         // Not found, error already printed

    /*
     * Prompt for the course info and find the course.
     */
    Course* course = getCourseByName();
    if (course == NULL)
        return;                         // Not found, error already printed

    /*
     * Deenroll the student by removing the course from his/her
     * schedule; as a side effect, also remove him/her from the
     * course's roster.
     *
     * If the student wasn't in the course, produce an error message.
     *
     * The way the classes are written in the sample solution, we
     * could call either student->removeCourseFromSchedule or
     * course->removeStudent with the same effect.  The choice is
     * arbitrary.
     */
    if (!student->removeCourseFromSchedule(course))
        cerr << "Student " << student->name() << " was not in course\n";
}

/*
 * Perform the change-hair-color command.  We use a common function to
 * locate the student, prompt for the new color, and use the student's
 * member function to perform the change.
 */
void RegDB_Manager::changeHair()
{
    Student* student = getStudentByName();
    if (student == NULL)
        return;                         // Not found, error already printed

    const string hairColor = promptRead("New hair color? ", cerr, cin);
    if (!cin)
        return;

    student->changeHairColor(hairColor);
}

/*
 * Perform the change-meeting-time command.  We use a common function
 * to locate the course, prompt for the new days and time, and use the
 * course's member function to perform the change.
 */
void RegDB_Manager::changeTime()
{
    Course* course = getCourseByName();
    if (course == NULL)
        return;                         // Not found, error already printed

    string meetingDays = promptRead("New meeting days? ", cerr, cin);
    if (!cin)
        return;

    string meetingHour = promptRead("New meeting time? ", cerr, cin);
    if (!cin)
        return;

    /*
     * Construct the meeting-time string and change the course to that time.
     */
    course->changeMeetingTime(meetingDays + " " + meetingHour);
}

/*
 * Perform the show-students command.  All the work is done by the database.
 */
void RegDB_Manager::showStudents()
{
    db_.showStudents(cout);
}

/*
 * Perform the show-courses command.  All the work is done by the database.
 */
void RegDB_Manager::showCourses()
{
    db_.showCourses(cout);
}

/*
 * Find a student by prompting the user for his/her name and then
 * looking him/her up in the registrar's database.
 *
 * If the student is found, returns a pointer to the Student record.
 * Otherwise, prints an error message to cerr and returns NULL.
 */
Student* RegDB_Manager::getStudentByName()
{
    /*
     * Prompt for the first and last name, giving up if EOF is hit.
     */
    const string firstName = promptRead("First name? ", cerr, cin);
    if (!cin)
        return NULL;
    const string lastName = promptRead("Last name? ", cerr, cin);
    if (!cin)
        return NULL;

    /*
     * Use the database's built-in function to search for the student.
     * If nobody matches, issue an error message.
     */
    Student* student = db_.findStudentByName(firstName, lastName);
    if (student == NULL)
        cerr << "Student " << lastName << ", " << firstName
          << " was not found\n";

    return student;
}

/*
 * Find a course by prompting the user for the ID and course number
 * and then looking it up in the registrar's database.
 *
 * If the course is found, returns a pointer to the Course record.
 * Otherwise, prints an error message to cerr and returns NULL.
 */
Course* RegDB_Manager::getCourseByName()
{
    /*
     * Prompt for the department (as a string) and the course number
     * (as an integer), giving up if EOF is hit.
     */
    const string department = promptRead("Department? ", cerr, cin);
    if (!cin)
        return NULL;
    cerr << "Course number? " << flush;
    int courseNumber;
    if (!(cin >> courseNumber))
        return NULL;

    /*
     * Use the database's built-in function to search for the course.
     * If nothing matches, issue an error message.
     */
    Course* course = db_.findCourseByName(department, courseNumber);
    if (course == NULL)
        cerr << "Course " << department << courseNumber << " was not found\n";

    return course;
}


/* Junk to make emacs use the right C++ style when editing this code:
 *   Local Variables:
 *   c-file-style: "stroustrup"
 *   End:
 */
