#!/usr/bin/python
# -*- coding: utf-8 -*-
import pyplwn, random

class WBSTQuestion:
	"""
	Simple class acting as a container for words of single question of WBST-based test
	"""
	def __init__(self, question, correct, detractors):
		"""
		Initalizes WBSTQuestion
		"""
		self.question = question
		self.correct = correct
		self.detractors = detractors

	def to_csv_string(self):
		"""
		Converts WBSTQuestion to unicode string in format:
		question;correct_answer;d_1;d_2;d_n...
		Correct answer is repeated within d_i.
		d_i are sorted alphabetically
		"""
		w = [self.correct]
		w.extend(self.detractors)
		w = sorted(w)
		str = self.question + u";" + self.correct
		for d in w :
			str += u";" + d
		return str

class WBSTGeneratorBase:
	"""
	Base class for WBST questions generators.
	"""
	def __init__(self, db, candidate_words, detractor_words, no_of_detractors = 3):
		"""

		Params:
		db - pyplwn.Database object
		candidate_words - words for questions only
		detractor_words - words for answers and detractors
		no_of_detractors - number of detractors
		"""
		self.db = db
		self.candidate_words = candidate_words
		self.detractor_words = detractor_words
		self.no_of_detractors = no_of_detractors
		self.qapairs = {}
		self.lemmas = set( db.LexicalUnits.get_all_lemmas() )

	def can_add_question(self, w1, w2):
		"""
		Returns True if there is no question-answer or answer-question
		pair for given words. Stores internal collection of qa pairs, 
		so it should be called for every pair of qa.
		"""
		if (w1 in self.qapairs and w2 in self.qapairs[w1]) or \
				(w2 in self.qapairs and w1 in self.qapairs[w2]):
			return False

		if w1 in self.qapairs:
			self.qapairs[w1].add(w2)
		else:
			self.qapairs[w1] = set([w2])
		return True

class HWBSTGenerator (WBSTGeneratorBase):
	"""
	Generates WBST or HWBST tests. 
	"""
	def __init__(self, db, candidate_words, detractor_words, no_of_detractors = 3, add_hypernyms = True):
		"""

		Params:
		db - pyplwn.Database object
		candidate_words - words for questions only
		detractor_words - words for answers and detractors
		no_of_detractors - number of detractors
		add_hypernyms - should synsets be extended by their's direct hyperynyms
		"""
		WBSTGeneratorBase.__init__(self, db, candidate_words, detractor_words, no_of_detractors )
		self.hyperonyms_directed_graph = pyplwn.wngraph.WNGraph( db.Relations.get_relation_by_name("hiperonimia"), \
																  db, False )
		self.hwbst = add_hypernyms

	def generate(self):
		"""
		Generates all possible (H)WBST questions from wordnet.
		"""
		questions = []
		for w1_ind in xrange(len(self.candidate_words)):
			w1 = self.candidate_words[w1_ind]
			all_words = self.get_all_words_with_words( w1 )
#			print "W: %s, close :%s" % (w1, str(all_words) )

			for w2 in all_words:
				if not w2 in self.detractor_words:
					continue
				if not self.can_add_question(w1,w2):
					continue

				all_close_words = all_words.union( self.get_all_words_with_words(w2) )
				detractors = self.generate_detractors(w1,w2, all_close_words)
				if len(detractors) == self.no_of_detractors:
					questions.append( WBSTQuestion(w1, w2, detractors) )
				else: print "not enough detractors for: (%s,%s)" % (w1,w2)

			if w1_ind % 100 == 0:
				wbst = "wbst"
				if self.hwbst == True:
					wbst = "hwbst"
				print "Generating %s test for word %s (%s / %s). Generated %s questions so far..." % \
						(wbst,w1, w1_ind, len(self.candidate_words), len(questions))

		print "Generated %s questions for %s candidate words" % (len(questions), len(self.candidate_words) )
		return questions

	def generate_detractors(self, w1, w2, close_words):
		"""
		Generates detractors for qa pairs (w1 and w2 params), words is a collection 
		"""
		detractors = []
		select = range ( len(self.detractor_words) )
		random.shuffle(select )

		while len(detractors) < self.no_of_detractors and len(select) > 0:
			w = self.detractor_words[ select.pop() ]
			if w == w1 or w == w2 or w in close_words:
				continue
			if not w in self.detractor_words or not w in self.lemmas:
				continue
			detractors.append(w)
		return detractors

	def get_all_words_with_words(self, w):
		"""
		Retrives all words (lemmas) for given word w, that:
		1) are in the same synset as w (any unit with lemma = w) OR
		2) if len(1) == 1, then are direct hypernym of w (all units in synsets that are direct hypernym 
			of synset containing any unit with lemma = w)
		Second condition only applies for generation of hwbst tests
		"""
		# get all synsets for this lemma
		units = self.db.LexicalUnits.get_units_by_lemma( w )
		synsetsid = [];
		for u in units:
			synsetsid.extend(self.db.Synsets.get_synsets_id_for_unit( u ) )
		if len(units) == 1 and self.hwbst:
			#extends those synsets with direct hypernyms
			hypernyms = []
			for s in synsetsid:
				if not s in self.hyperonyms_directed_graph.graph.nodes():
					continue
				for direct_hypernyms in self.hyperonyms_directed_graph.graph.neighbors(s):
					hypernyms.append ( direct_hypernyms )
			synsetsid.extend(hypernyms)

		synsets = []
		# get full synsets for ids
		for id in synsetsid:
			synsets.append( self.db.Synsets.get_synset_by_id( id ) )

		all_words = set([])
		for s in synsets:
			for unit in s.units:
				if unit.lemma == w: continue
				all_words.add(unit.lemma)
		return all_words


