/*
╔══════════════╗
║ Hogwarts OOP ║
╚══════════════╝
The Object-Orieted Programming SortingHatSim:
All dynamic using pointers and no fixed-length arrays.
Functions take only pointers as parameters.
Student and House are now proper C++ Object classes.
*/

#include <cstdlib> // rand() and srand()
#include <ctime>   // system time
#include <fstream>
#include <iomanip>  // setw() and formatting
#include <iostream> // basic i/o
#include <string>
#define MAX_HOUSE_SIZE 8
using namespace std;

class Student {
private:
	string lastname;
	string firstname;
	string house;
	string id;
	Student *nextStudent;

public:
	Student(){
		this->lastname = "";
		this->firstname = "";
		this->house = "";
		this->id = "";
		this->nextStudent = nullptr;
	}

	Student(string fname, string lname){
		Student(); // call the default constructor
		this->firstname = fname;
		this->lastname = lname;
	}

	void setFirstName(string fname){
		this->firstname = fname;
		return;
	}

	string getFirstName(){
		return this->firstname;
	}

	void setLastName(string lname){
		this->lastname = lname;
		return;
	}

	string getLastName(){
		return this->lastname;
	}

	void setNextStudent(Student *s){
		this->nextStudent = s;
		return;
	}

	Student* getNextStudent(){
		return this->nextStudent;
	}

	string getHouse(){
		return this->house;
	}

	void setHouse(string name){
		this->house = name;
		return;
	}

	string getID(){
		return this->id;
	}

	void setID(string id){
		this->id = id;
	}
};

class House {
private:
	string name;
	unsigned number;
	Student *first;
	Student *current;
	House *next;

public:
	House(){
		this->name = "";
		this->number = 0;
		this->first = nullptr;
		this->current = nullptr;
		this->next = nullptr;
	}

	House(string name){
		House();
		this->name = name;
	}

	void addStudent(Student *s) {
		if (this->number < MAX_HOUSE_SIZE) {
			// if we're adding the first student
			if (this->first == nullptr) {
				this->first = s;
				this->current = this->first;
			} else {
				this->current->setNextStudent(s);
			}
			this->current = s;
			this->number++;
		} else {
			cout << "ERROR: House is full.\n";
		}
		return;
	}

	Student* getFirstStudent(){
		return this->first;
	}

	void setFirstStudent(Student *s){
		this->first = s;
		return;
	}

	Student* getCurrentStudent(){
		return this->current;
	}

	void setCurrentStudent(Student *s){
		this->current = s;
		return;
	}

	House* getNextHouse(){
		return this->next;
	}

	void setNextHouse(House *h){
		this->next = h;
		return;
	}

	unsigned getNumber(){
		return this->number;
	}

	string getHouseName(){
		return this->name;
	}

	void setHouseName(string n){
		this->name = n;
		return;
	}
};


House *chooseRandomHouse(House *first) {
	House *curr = first;
	int houseNum = rand() % 4;
	while (houseNum > 0) {
		curr = curr->getNextHouse();
		houseNum--;
	}
	return curr;
};


Student *chooseRandomStudent(House *h) {
	Student *s;
	int studentNum = rand() % h->getNumber();
	s = h->getFirstStudent();
	while (studentNum > 0) {
		if (s->getNextStudent() != nullptr) {
			s = s->getNextStudent();
		}
		studentNum--;
	}
	return s;
}


int main() {
	Student *s = new Student;
	House *h;
	House *f; // first House
	House *c; // current House
	// House houses[4];
	string houseNames[] = {"Gryffindor", "Hufflepuff", "Ravenclaw", "Slytherin"};
	string temp;
	ifstream infile;
	string filename = "students.csv";
	int n = 0; // students to read from the file
	int m = 0; // printing the list of students in each house
	int houseNum;
	bool validHouse;
	string tempID = "";
	unsigned padding = 0;
	unsigned studentNum = 1;

	// seed the PRNG
	unsigned seed;
	seed = time(0); // the timestamp at runtime
	srand(seed);

	cout << "DEBUG: house dynamic allocation\n";
	cout << "DEBUG: index value: 0" << endl;
	h = new House(houseNames[0]);
	f = h; //first is Gryffindor
	c = f; //current is first
	// print the initial state of Houses
	cout << "Initializing Houses:\n";
	for (int i = 1; i < 4; i++) {
		cout << "DEBUG: house dynamic allocation\n";
		cout << "DEBUG: index value: " << i << endl;
		temp = houseNames[i];

		cout << "DEBUG: next house name string: " << temp << endl;

		h = new House(temp);

		cout << "DEBUG getHouseName():\n";
		cout << h->getHouseName() << endl;
		cout << "memory address: " << h << endl;
		cout << "number of members: " << h->getNumber() << endl;

		c->setNextHouse(h);
		cout << "next house: ";
		c = h;
		cout << c->getHouseName() << endl;

		if (i >= 3) {
			c->setNextHouse(f); // we're at the end; loop the pointer around back to first
			cout << "next house: " << c->getNextHouse()->getHouseName() << endl;
			// getline(cin,temp);
			// should be "Gryffindor"
		}
	}
	cout << "Houses are set.  Press a key to continue: ";
	getline(cin,temp);
	cout << "********************\n";

	// Get the filename from the user
	// cout << "Please enter the filename: ";
	// cin >> filename;

	// Open the file the user specified
	infile.open(filename);

	// If the file successfully opened, process it.
	if (infile) {
		// Read the members from the file and display them.
		cout << "\nThe sorting hat says:\n";
		while (n < 30) {
			// getline(string, token, delimiter);
			getline(infile, temp, ',');
			s->setLastName(temp);
			getline(infile, temp);
			s->setFirstName(temp);
			cout << setw(2) << right << n + 1 << "). " << s->getFirstName() << " "
					 << s->getLastName() << ": ";
			// add the student ID (starts with H00000001)
			tempID = to_string(studentNum);
			padding = 6-tempID.size();
			// assign to the student
			tempID = "H00" + tempID.insert(0, padding, '0');
			s->setID(tempID);
			// advance to the next student number
			studentNum++;

			cout << "Student ID (H-number) set to: " << s->getID() << endl;
			validHouse = false;
			houseNum = rand() % 4;
			cout << "House " << houseNames[houseNum] << endl;
			// Make c = f (the first House)
			c = f;
			// We have to get to the matching house.
			while (c->getHouseName() != houseNames[houseNum]) {
				cout << "DEBUG: advancing the house pointer.\nChecking: ";
				cout << c->getHouseName() << endl;
				c = c->getNextHouse();
			}

			do { // checking House members loop
				cout << "DEBUG: checking " << houseNames[houseNum] << endl;
				cout << "DEBUG: c->name: " << c->getHouseName() << endl;
				// only add the student if there's room
				if (c->getNumber() < MAX_HOUSE_SIZE) {
					cout << "DEBUG: " << c->getHouseName() << " is not full.\n";
					s->setHouse(c->getHouseName());
					validHouse = true;
					c->addStudent(s);
					cout << "Adding: " << s->getFirstName() << " " << s->getLastName()
							 << " to House " << c->getHouseName() << endl;
				} else {
					cout << c->getHouseName() << " is full...choosing another.\n";
					if (c->getNextHouse() != nullptr) { // if there's another in the list
						c = c->getNextHouse();            // advance
						if (c == f) {
							cout << "DEBUG: reached the end of the House list.\n";
						}
						cout << "DEBUG: advancing to House " << c->getHouseName() << endl;
						houseNum = ((houseNum + 1) % 4);
					}
				}
				// if the house is full, choose another!
			} while (not validHouse);
			cout << s->getHouse() << " now has " << c->getNumber() << " student(s).\n\n";
			n++;
			// now dynamically create the next student
			// Declaration of s: Student *s = new Student;
			s = new Student;
		}
		// Close the file.
		infile.close();
	} else {
		// Display an error message.
		cout << "Error opening " << filename << ".\n";
	}
	cout << "\nTotal registered students attending Hogwarts: " << n << ".\n\n";
	cout << "╔═════════════════════════════════╗\n";
	cout << "║ Dynamic List following pointers ║\n";
	cout << "╚═════════════════════════════════╝\n";
	c = f; // the first house
	c->setCurrentStudent(c->getFirstStudent()); // the first student
	cout << "DEBUG: Current house is now: " << c->getHouseName() << ", address: " << c
			 << endl;
	cout << "DEBUG: Current student is now: " << c->getCurrentStudent()->getFirstName() << " "
			 << c->getCurrentStudent()->getLastName() << endl;
	do {
		cout << "\n" << c->getHouseName() << " [" << c->getNumber() << "]:\n";
		m = 1;
		// if there is no next student, we're at the end.
		while (c->getCurrentStudent()->getNextStudent() != nullptr) {
			cout << setw(2) << right << m << "). " << c->getCurrentStudent()->getFirstName() << " "
					 << c->getCurrentStudent()->getLastName() << endl;
			c->setCurrentStudent(c->getCurrentStudent()->getNextStudent());
			m++;
		}
		// print the last student
		cout << setw(2) << right << m << "). " << c->getCurrentStudent()->getFirstName() << " "
				 << c->getCurrentStudent()->getLastName() << endl;
		// advance to the next house
		c = c->getNextHouse();
		c->setCurrentStudent(c->getFirstStudent()); // get the first student
		cout << "DEBUG: Current house is now: " << c->getHouseName() << ", address: " << c
				 << endl;
		cout << "DEBUG: Current student is now: " << c->getCurrentStudent()->getFirstName() << " "
				 << c->getCurrentStudent()->getLastName() << endl;
	} while (c != f); // if we looped back around, stop output.


	for (int x = 0; x < 10; x++) {
		cout << "\n************************************\n";
		cout << "Choosing random house: ";
		c = chooseRandomHouse(f);
		cout << c->getHouseName() << endl;
		cout << "Choosing random student from " << c->getHouseName() << ":\n";
		s = chooseRandomStudent(c);
		cout << s->getFirstName() << " " << s->getLastName() << ", id: " << s->getID() 
			 << ", House " << s->getHouse() << endl;
	}

	return 0;
}
