'''
Copyright 2013 Mark Dredze. All rights reserved.
This software is released under the 2-clause BSD license.
Mark Dredze, mdredze@cs.jhu.edu
'''
# Input- a whitelist for each area, a list of submissions per area, csv file with reviewer signups
# The area must add people to their whitelist that they approve of by using my other script
# Merge reviewer duplicates on input

# Algorithm:
# Find choices for every reviewer that respect the whitelist.
# Find every reviewer with only a single choice overall and assign them to their area
# assume a default of X papers by reviewer
# keep track of the number of reviewers assigned to each area and the number needed by each area
# for reviwers with 2 choices
# round robin between areas- for each area, find a reviewer who wants that area as #1, pick them
# Keep selecting area until it is full
# If no first round, then pick a second choice reviewer
# Once all areas are full, continue assignment but put an area in the round robin list propto the number of reviewers they need
# Start with an assumption of 3 * number of papers / load (4) but load is adjustable
from acl_check_reviewers import selectAreaName

import sys, os, csv, re, glob, random

class CsvLoader:
	def __init__(self, filename):
		self.__loadFile(filename)
		
	def __mapColumns(self, entry):
		self.names_to_columns = {}
		self.columns_to_names = {}
		for ii, name in enumerate(entry):
			self.names_to_columns[name.lower()] = ii
			self.columns_to_names[ii] = name.lower()

	def getColumn(self, entry, column):
		column_id = self.names_to_columns[column]
		if column_id >= len(entry):
			return ''
		return entry[column_id]
	
	def getColumnNames(self):
		return self.names_to_columns.keys()
		
	def __loadFile(self, filename):
		file = open(filename)
		reader = csv.reader(file)
		
		self.contents = []
		
		for ii, entry in enumerate(reader):
			if ii == 0:
				self.__mapColumns(entry)
				continue
			line = {}
			for jj in range(len(entry)):
				line[self.columns_to_names[jj]] = entry[jj]
			self.contents.append(line)

		file.close()
	
	def __iter__(self):
		return self.contents.__iter__()
	
	def __len__(self):
		return len(self.contents)
		
class ACLAssignGreedyReviewers:
	def __init__(self):
		self.want_string = 'Want to review (1st Choices)'
		self.willing_string = 'Willing to review (2nd Choices)'
		self.will_not_string = 'Will not review'
		self.name_field = 'name'
		self.email_field = 'email'
		self.start_account_username = 'start account username'
		pass
	
	
	def loadReviewerInformation(self, reviewer_csv_filename):
		csv_loader = CsvLoader(reviewer_csv_filename)
		
		
		column_names = csv_loader.getColumnNames()
		if 'name (first last)' in column_names:
			self.name_field = 'name (first 
last)'
		if 'email address' in column_names:
			self.email_field = 'email address'
		
		area_entry_to_name = {}
		for entry in column_names:
			if entry.star
tswith('areas ['):
				area_name = re.search('areas \[(.+?) \(', entry).group(1)
				area_entry_to_name[entry] = area_name
				
		
		names = set()
		emails = set()
		reviewer_to_area_choices = {}
		emails_to_reviewer_id_dict = {}
		from_reviewer_id_dict = {}
		reviewer_to_load = {}
		
		num_lines = 0
		for entry in csv_loader:
			num_lines += 1
			if len(entry) == 0:
				continue
			name = entry[self.name_field]
			email = entry[self.email_field].lower().strip()
			start_account_username = entry[self.start_account_username]
			
			load_for_reviewer = entry['reduced review load (optional)']
			
			reviewer_id = name.replace(' ', '_') + '_' + email.replace(' ', '_')
			
				
			# Is this a valid email address.
			if '@' not in email or ' ' in email:
				print 'Warning: Invalid email: %s (%s)' % (name, email)
			

			duplicate_reviewer = False
			if reviewer_id in from_reviewer_id_dict or name in names or email in emails:
				if email in emails_to_reviewer_id_dict:
					reviewer_id = emails_to_reviewer_id_dict[email]
					duplicate_reviewer = True
				else:
					print 'Warning: duplicate reviewer name: ', name
			
			try:
				load_for_reviewer = int(load_for_reviewer)
				reviewer_to_load[reviewer_id] = load_for_reviewer
				print 'Registered load limit for %s (%s): %d' % (name, email, load_for_reviewer)
			except:
				pass
			
			names.add(name)
			emails.add(email)
			
			emails_to_reviewer_id_dict[email] = reviewer_id
			tuple = (name, email, start_account_username)
			
			from_reviewer_id_dict[reviewer_id] = tuple
			area_choices = []
	
			for area_entry, area_name in area_entry_to_name.iteritems():
				if area_entry not in entry:
					continue
				choice = entry[area_entry]
				rating = None
				if choice == self.want_string:
					rating = 1
				elif choice == self.willing_string:
					rating = 2
				
				if rating != None:
					area_choices.append((area_name, rating))
			
			if duplicate_reviewer:
				# Merge the reveiwers choices by always taking their higher choice.
				old_area_choices = reviewer_to_area_choices[reviewer_id]
				new_area_choices = {}
				for area, rating in area_choices:
					new_area_choices[area] = rating
				for area, rating in old_area_choices:
					new_area_choices[area] = max(new_area_choices.setdefault(area, 0), rating)
				area_choices = []
				for area, choice in new_area_choices.iteritems():
					area_choices.append((area, choice))
					
			reviewer_to_area_choices[reviewer_id] = area_choices
		
		print 'Number of lines: %d' % num_lines
		print 'Loaded %d/%d reviewers.' % (len(from_reviewer_id_dict), len(reviewer_to_area_choices))
		return reviewer_to_area_choices, emails_to_reviewer_id_dict, from_reviewer_id_dict, reviewer_to_load
	
	def selectReviewerForArea(self, area, reviewers_per_area_lists, used_reviewers):
		while len(reviewers_per_area_lists[area]) > 0:
			reviewer = reviewers_per_area_lists[area].pop(0)
			if reviewer not in used_reviewers:
				return reviewer
		return None
	
	# To handle differences in number of papers, we want to have some areas get multiple
	# people per round so every area fills up at the same time.
	# This methods computes how many assignments are needed to reach that point.
	def computeNumAreaAssignmentPerRound(self, area_to_load, area_to_num_papers, area_to_paper_load, priority_areas):
		area_to_num_assignments_per_round = {}
		# Compute how many reviewers are needed in each area.
		reviewers_needed_per_area = {}
		min = None
		max = None
		for area in area_to_num_papers.keys():
			reviewers_needed_per_area[area] = (area_to_num_papers[area] * area_to_paper_load[area]) / area_to_load[area]

			if min == None or min > reviewers_needed_per_area[area]:
				min = reviewers_needed_per_area[area]
			if max == None or max < reviewers_needed_per_area[area]:
				max = reviewers_needed_per_area[area]
	
		if max / min < 2:
			# The max isn't even twice the min area, so scale things up.
			print 'Error: max is not greater than twice min. Using 1 for everything.'
		# The min area gets one reviewer per round and every other area gets int(area/min)
		for area, num_reviewers in reviewers_needed_per_area.iteritems():
			area_to_num_assignments_per_round[area] = int(num_reviewers / min)
		
		for area in area_to_num_assignments_per_round.keys():
			if priority_areas != None and area in priority_areas:
				area_to_num_assignments_per_round[area] *= self.increase_priority_factor
			
		return area_to_num_assignments_per_round
	
	def assignReviewers(self, reviewers_per_area_lists, \
						reviewer_load_constraint, area_to_load, area_to_num_papers, \
						area_to_num_assignments_per_round, area_to_paper_load, \
						assign_all_whitelist_reviewers_to_area, forced_reviewer_to_area, \
						min_reviewers_per_area):
		# For each area, we have a list of reviewers who selected that area ordered by first choice,
		# the second choice, then by the number of total areas they picked.
		
		# A dict between area name and the reviewers assigned to that area.
		assignments = {}
		
		areas = area_to_num_papers.keys()
		print 'Assigning to %d areas.' % len(areas)
		area_to_num_reviews_assigned = {}
		for area in areas:
			area_to_num_reviews_assigned[area] = 0
		
		# In each pass, find reviewers who have only n selections. This saves the more flexible
		# reviewer