import java.util.ArrayList;
import java.util.HashMap;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;

/* NOTE::editor: micro complains about all javafx things just ignore */
public class School {
	private ObservableList<Instructor> instructorList;
	private ObservableList<Course> courseList;
	private ObservableList<Student> studentList;
	private String name;
	
	private static School inst = null;

	private School(/*String file,*/ String schoolName) {
		instructorList = FXCollections.observableArrayList();
		courseList = FXCollections.observableArrayList();
		studentList = FXCollections.observableArrayList();

		this.name = schoolName;
	}
	
	// These two methods secure the singleton pattern for ur
	public static School getSchool() {
		return inst;
	}

	public static void createSchool(String schoolName) {
		if(inst == null) {
			inst = new School(schoolName);
		}
	}

	public void editCourse(Course c) {
		/* Helper func to notify an actual change for the ui */
		int k = courseList.indexOf(c);
		courseList.set(k,c);
	}

	public ObservableList<Instructor> uiGetInstructors() {
		return instructorList;
	}
	public ObservableList<Course> uiGetCourses() {
		return courseList;
	}
	public ObservableList<Student> uiGetStudents() {
		return studentList;
	}

	public void editStudent(Student s) {
		int k = studentList.indexOf(s);
		studentList.set(k,s);
	}
	public void readData(String filename) {
			try {
				BufferedReader reader = new BufferedReader(new FileReader(filename));
				// first line is how many instructors we want to add
				int inst_count = Integer.parseInt(reader.readLine());
				for(int i =0;i<inst_count;i++) {
					// now we shold begin to populate out instructor list
					String[] data = reader.readLine().split(",");
					int tmp_id = Integer.parseInt(data[0]);

					// ignoring the return value as those custom messages are in the method itself
					this.addInstructor(tmp_id, data[1], data[2],data[3]);
				}
				// next line should be number of courses to add
				int course_count = Integer.parseInt(reader.readLine());
				for(int i =0;i<course_count;i++) {
					String[] data = reader.readLine().split(",");
					int tmp_id = Integer.parseInt(data[0]);
					int inst_id = Integer.parseInt(data[2]);

					this.addCourse(tmp_id, data[1], inst_id, data[3]);
				}
				// finally the amount of students we want to add is next
				int student_count = Integer.parseInt(reader.readLine());
				for(int i =0;i<student_count;i++) {
					// parse items from the current line
					String[] data = reader.readLine().split(",");
					int stud_id = Integer.parseInt(data[0]);
					int cour_id = Integer.parseInt(data[2]);
					float fl_grade = Float.parseFloat(data[3]);

					// attempt to add the student
					this.addStudent(stud_id, data[1], cour_id, fl_grade, data[4]);
				}
				System.out.println("Done.");
			}
			catch(IOException e) {
				e.printStackTrace();
			}
		}
	public boolean addInstructor(int id, String name, String email, String phone) {
		// make sure that the new instructor isn't already in the system
		for(Instructor i : this.instructorList) {
			if(id == i.id) {
				System.out.println("Instructor ID already in use");
				return false;
			}
		}
		Instructor new_inst = new Instructor(id, name, email, phone);
		instructorList.add(new_inst);
		return true;
	}
	public boolean addCourse(int id, String title, int instructorID, String location) {
		// make sure we don't the same course twice
		for(Course c : this.courseList) {
			if(c.id == id) {
				System.out.println("Course addition failed – Course number already used.");
				return false;
			}
		}
		// check if the instructor exists
		boolean instructorExists = false;
		for(Instructor i : this.instructorList) {
			if(i.id == instructorID) {
				instructorExists = true;
			}
		}
		if(!instructorExists) {System.out.println("Course addition failed – Non-existing instructor.");}
		// check the instructor
		Course new_course = new Course(id, title, instructorID, location);
		courseList.add(new_course);
		return true;
	}



	public String getInstructor(int id) {
		// find the instructor name given an id
		for(Instructor i : this.instructorList) {
			if(i.id == id) {
				return i.name;
			}
		}
		return "Not Found";
	}
	public float courseAverage(int id) {
		// gets average of all the students grades for a given course by id
		float sum = 0;
		int count = 0;
		for(Student s : this.studentList) {
			for(Integer i : s.grades.keySet()) {
				if(i == id) {
					sum += s.grades.get(id).num_grade;
					count++;
				}
			}
		}
		if(count == 0) {
			return 0.00f;
		}

		return sum / (float)count;
	}
	public int courseEnrolled(int id) {
		int count = 0;
		for(Student s : this.studentList) {
			// everytime a student has the given id in their grades add 1 to the counter
			if(s.grades.get(id) != null) {
				count++;
			}
		}
		return count;
	}
	
	public void courseInfo(int id) {
		boolean found = false;
		// look for the course given by the id
		for(Course i : this.courseList) {
			found = true;
			if(i.id == id) { 
				System.out.println("Course number: " + i.id);
				System.out.println("Instructor: "+ this.getInstructor(i.instructorID));
				System.out.println("Course title: " + i.title);
				System.out.println("Room: " + i.location);
				System.out.println("Total enrolled: " + this.courseEnrolled(id));
				System.out.println("Course average: " + this.courseAverage(id));
			}
		}
		if(!found) {
			System.out.println("Course not found: [id = " + id + "]");
		}
	}

	public void courseInfo() {
		System.out.println("Number of courses: " + this.courseList.size());
		for(Course c : this.courseList) {
			System.out.println(c.id + ": " + this.courseEnrolled(c.id) + " enrolled");
		}
	}
	public Course getCourse(int id) {
		for(int i =0;i<this.courseList.size();i++) {
			if(this.courseList.get(i).id == id) {
				return this.courseList.get(i);
			}
		}
		return null;
	}
	public boolean courseEmpty(int courseID) {
		for(Student s : this.studentList) {
			if(s.grades.get(courseID) != null) {
				return false;
			}
		}
		return true;
	}
	public boolean deleteCourse(int id) {
		if(!courseEmpty(id)) {
			System.out.println("Course " + id + " is not empty!");
			return false;
		}
		// next look for the course to delete
		for(int i =0; i<courseList.size();i++) {
			if(courseList.get(i).id == id) {
				courseList.remove(i);
				for(Student s : studentList) {
					s.grades.remove(id);
				}
				return true;
			}
		}
		return false;
	}
	public boolean addStudent(int id, String name, int courseID, float floatGrade, String letterGrade) {
		for(Student i : this.studentList) {
			if(i.id == id) {
				// attempt to regitser the student in case they're trying to add a new course
				// this method should also give us proper error msgs about registering
				if(this.register(id, courseID, floatGrade, letterGrade)) {
					return true;
				}
				return false;
			}
		}
		// check if the course even exists at all 
		boolean cFound = false;
		for(Course c : this.courseList) {
			if(c.id == courseID) {
				cFound = true;
			}
		}
		if(!cFound) {
			System.out.println("Course does not exist");
			return false;
		}

		// finally we can add the student into the system
		// this is assuming we didn't register them earlier
		Student new_student = new Student(id, name, courseID, floatGrade, letterGrade);
		studentList.add(new_student);
		return true;
	}
	public Student getStudentInfo(int id) {
		for(Student s : this.studentList) {
			if(s.id == id) {
				return s;
			}
		}
		return null;
	}
	public boolean graduateStudent(int id) {
		// remove the target id from all the courses
		Student tmp =  getStudentInfo(id);
		if(tmp == null) {
			System.out.println("Student not found");
			return false;
		}
		
		tmp.graduate();
		editStudent(tmp);
		return true;
	}

	public boolean register(int studentID, int courseID, float floatGrade, String letterGrade) {
		Student tmp = this.getStudentInfo(studentID);
		if(tmp.grades.keySet().contains(courseID)) {
			System.out.println("Student already enrolled");
			return false;
		}
		// make sure we don't try to regitster for a course that doesn't exist
		if(this.getCourse(courseID) == null) {
			return false;
		}
		// if the student is there but not enrolled in that course then just create a new student with new data
		tmp.enroll(courseID, floatGrade, letterGrade);
		editStudent(tmp);
		return true;
	}
	public void drop(int studentID, int courseID) {
		for(Student s : studentList) {
			if(s.id == studentID) {
				s.grades.remove(courseID);
				editStudent(s);
			}
		}
	}
	public void assign(int instructorID, int courseID) {
		Course c = getCourse(courseID);
		c.instructorID = instructorID;
		editCourse(c);	
	}
	public void unassign(int instructorID, int courseID) {
		Course c = getCourse(courseID);
		c.instructorID = 0;
		editCourse(c);
	}
	// basic printout of school infor
	public void schoolInfo() {
		System.out.println("School name: " + this.name);
		// instructors
		System.out.println("Instructor info");
		for(Instructor i : this.instructorList) {
			System.out.println(i.name);
		}
		// courses
		System.out.println("Course Information");
		for(Course c : this.courseList) {
			System.out.println(c.title);
		}
		// students
		System.out.println("Student Information");
		for(Student s : this.studentList) {
			// check what courses that student is in
			for(Integer i : s.grades.keySet()) {
				Course c_rep = this.getCourse(i);
				System.out.println(s.name + ": " + c_rep.title);
			}
		}
	}
}
