## @file CalcModule.py
#  @brief Module that contains the functions sort, average and allocate that take the generated dictionaries and lists from Step 1.
#  @author Raymond Tu
#  @date 2019-01-18

import operator

## @brief Function that sorts the given list of dictionaries in descending order by GPA.
#  @details Uses the operator library to use the inbuilt sort function to sort the list of dictionaries S, by GPA in descending order.
#  @param S The given list of student dictionaries generated from readStdnts(s) in ReadAllocationData.py
#  @return Sorted list of student dictionaries in descending order by GPA.
def sort(S):

#code taken from https://stackoverflow.com/questions/72899/how-do-i-sort-a-list-of-dictionaries-by-a-value-of-the-dictionary
	from operator import itemgetter
	sorted_list = sorted(S, key=itemgetter('gpa'), reverse= True)
	
	return sorted_list

## @brief Function that calculates the average GPA for male or female students.
#  @details Loops through every male or female in the list of student dictionaries, adds up all the GPAs then divides by number of students to determine average.
#  @param L The given list of student dictionaries generated from readStdnts(s) in ReadAllocationData.py
#  @param g The desired gender of students to find the average GPA from.
#  @return The average of male or female students in the list of student dictionaries.
def average(L, g):

	male_list = []
	female_list = []
	
	if len(L) == 0:
		return 0

	for students in L:
		if students['gender'] == 'male':
			male_list.append(students)
		elif students['gender'] == 'female':
			female_list.append(students)
		
	if len(male_list) == 0:
		return 0
	
	if len(female_list) == 0:
		return 0
	
	num_males = 0
	num_females = 0
	male_total = 0
	female_total = 0
	male_average = 0
	female_average = 0
	
	if g == 'male':
		for males in male_list:
			male_total = male_total + males['gpa']
			num_males = num_males + 1
		male_average = male_total/num_males
		#assumption that the average GPA will be rounded to 1 decimal place
		rounded_male_average = round(male_average, 1)
		return rounded_male_average
			
	elif g == 'female':
		for females in female_list:
			female_total = female_total + females['gpa']
			num_females = num_females + 1
		female_average = female_total/num_females
		#assumption that the average GPA will be rounded to 1 decimal place
		rounded_female_average = round(female_average, 1)
		return rounded_female_average
		
## @brief Function that allocates students to their choice of program.
#  @details Function that allocates students to their choice of program, where students with free choice are given priority, followed by GPA.
#  @param S The given list of student dictionaries generated from readStdnts(s) in ReadAllocationData.py
#  @param F List of students with free choice by macID generated from function readFreeChoice.
#  @param C A dictionary of department capacities generated by readDeptCapacity.
#  @return The allocated list of students, listing every department and the students enrolled in them.
def allocate(S, F, C):
	elgible_std = []
	
	#assumption that students with a GPA of exactly 4.0 will still be allocated
	#assumption that even if student has free choice, if GPA is lower than 4 not elgible to be allocated
	for students in S:
		if students['gpa'] >= 4.0:
			elgible_std.append(students)
			
	allocated_list = {'civil':[], 'chemical':[], 'electrical':[], 'mechanical':[], 'software':[], 'materials':[], 'engphys':[]}
	
	#assumption that free choice students will receive their first choice no matter the circumstances, for example if the number of free choice students exceeds department capacity
	for free_students in F:
		for students in elgible_std:
			if students['macid'] == free_students:
				allocated_list[students['choices'][0]].append(students)
				#once allocated free choice students will not be allocated again, removed from general list
				elgible_std.remove(students)
	
	#after free choice students are allocated, allocate students based on highest to lowest GPA
	from operator import itemgetter
	sorted_list = sorted(elgible_std, key=itemgetter('gpa'), reverse= True)
	
	#Assumption that number of spots left are only checked after free choice is allocated, so potential for "negative capacity", in which case the department is said to be full
	civil_spots = C['civil'] - len(allocated_list['civil'])
	chem_spots = C['chemical'] - len(allocated_list['chemical'])
	elec_spots = C['electrical'] - len(allocated_list['electrical'])
	mech_spots = C['mechanical'] - len(allocated_list['mechanical'])
	soft_spots = C['software'] - len(allocated_list['software'])
	mat_spots = C['materials'] - len(allocated_list['materials'])
	engphys_spots = C['engphys'] - len(allocated_list['engphys'])
	
	for students in sorted_list:
		first_choice = students['choices'][0]
		second_choice = students['choices'][1]
		third_choice = students['choices'][2]
		
		if (first_choice == 'civil') and (civil_spots > 0):
			allocated_list['civil'].append(students)
			civil_spots = civil_spots - 1
		elif (first_choice == 'chemical') and (chem_spots > 0):
			allocated_list['chemical'].append(students)
			chem_spots = chem_spots - 1
		elif (first_choice == 'electrical') and (elec_spots > 0):
			allocated_list['electrical'].append(students)
			elec_spots = elec_spots - 1
		elif (first_choice == 'mechanical') and (mech_spots > 0):
			allocated_list['mechanical'].append(students)
			mech_spots = mech_spots - 1 
		elif (first_choice == 'software') and (soft_spots > 0):
			allocated_list['software'].append(students)
			soft_spots = soft_spots - 1
		elif (first_choice == 'materials') and (mat_spots > 0):
			allocated_list['materials'].append(students)
			mat_spots = mat_spots - 1
		elif (first_choice == 'engphys') and (engphys_spots > 0):
			allocated_list['engphys'].append(students)
			engphys_spots = engphys_spots - 1
		elif (second_choice == 'civil') and (civil_spots > 0):
			allocated_list['civil'].append(students)
			civil_spots = civil_spots - 1
		elif (second_choice == 'chemical') and (chem_spots > 0):
			allocated_list['chemical'].append(students)
			chem_spots = chem_spots - 1
		elif (second_choice == 'electrical') and (elec_spots > 0):
			allocated_list['electrical'].append(students)
			elec_spots = elec_spots - 1
		elif (second_choice == 'mechanical') and (mech_spots > 0):
			allocated_list['mechanical'].append(students)
			mech_spots = mech_spots - 1
		elif (second_choice == 'software') and (soft_spots > 0):
			allocated_list['software'].append(students)
			soft_spots = soft_spots - 1
		elif (second_choice == 'materials') and (mat_spots > 0):
			allocated_list['materials'].append(students)
			mat_spots = mat_spots - 1
		elif (second_choice == 'engphys') and (engphys_spots > 0):
			allocated_list['engphys'].append(students)
			engphys_spots = engphys_spots - 1
		elif (third_choice == 'civil') and (civil_spots > 0):
			allocated_list['civil'].append(students)
			civil_spots = civil_spots - 1
		elif (third_choice == 'chemical') and (chem_spots > 0):
			allocated_list['chemical'].append(students)
			chem_spots = chem_spots - 1
		elif (third_choice == 'electrical') and (elec_spots > 0):
			allocated_list['electrical'].append(students)
			elec_spots = elec_spots - 1
		elif (third_choice == 'mechanical') and (mech_spots > 0):
			allocated_list['mechanical'].append(students)
			mech_spots = mech_spots - 1
		elif (third_choice == 'software') and (soft_spots > 0):
			allocated_list['software'].append(students)
			soft_spots = soft_spots - 1
		elif (third_choice == 'materials') and (mat_spots > 0):
			allocated_list['materials'].append(students)
			mat_spots = mat_spots - 1
		elif (third_choice == 'engphys') and (engphys_spots > 0):
			allocated_list['engphys'].append(students)
			engphys_spots = engphys_spots - 1
			
	#assumption that after all students have been allocated, any students who were not able to be allocated in their first, second or third choice will be given an option to enter a department with open capacity, this is out of the scope of this function.
	
	return allocated_list
	
				
				
		
			
		
	
	

