#!/usr/bin/env python3
import xml.etree.ElementTree as ET
from unidecode import unidecode
from utils import strip_tags, mlang_2_multiling, score_2_str
import base64
from PIL import Image
from io import BytesIO
from re import findall,DOTALL

class Quizz:
	def __init__(self, file_name, target_folder, extrafiles_folder, risky):
		self.file_name = file_name
		self.tree = ET.parse(self.file_name)
		self.questions = {}
		self.categories = {}
		self.folder = target_folder
		self.xtra_f = extrafiles_folder
		self.risky = risky
		self.parse()

	def get_tree(self):
		return self.tree

	def create_question(self, q, c):
		if c not in self.questions.keys():
			self.questions[c] = []
			self.categories[c] = f"c{len(self.categories)}"
		if q.attrib['type'] == "multichoice":
			newQ = MCQ(q,self.categories[c],len(self.questions[c]),self.folder,self.xtra_f, self.risky)
		elif q.attrib['type'] == "shortanswer":
			newQ = ShortAnswer(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)
		elif q.attrib['type'] == "truefalse":
			newQ = TF(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)
		elif q.attrib['type'] == "cloze":
			newQ = Cloze(q, self.categories[c], len(self.questions[c]), self.folder, self.xtra_f, self.risky)
		elif q.attrib['type'] == "matching":
			newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)#non traité
		elif q.attrib['type'] == "ddimageortext":
			newQ = Question(q,self.categories[c],len(self.questions[c]),self.folder, self.xtra_f, self.risky)#non traité
		else:
			raise f"{q.attrib['type']} is not expected"
		if newQ.__class__.__name__ != "Question":
			self.questions[c].append(newQ)

	def parse(self):
		questions = self.tree.findall("question")
		for q in questions:
			if q.attrib["type"] == "category":
				c = q.find("category/text").text.replace("$course$/top/","").replace("/",":").replace("::","/")
			else:
				self.create_question(q, c)

	def __str__(self):
		res = ""
		digest ="%%%Exemple d'examen\n\t\t%\\cleargroup{exam}\n"
		for c,c_q in self.questions.items():
			tmp = "\t\t%\\shufflegroup{"+self.categories[c]+"}%("+c+"):"
			count = 0
			for q in c_q:
				res += str(q)
				count += 1
			if count > 0:
				digest += f"{tmp}{count} questions\n\t\t%\\copygroup[{int(count / 2)}]" + "{" + self.categories[c] + "}" + "{exam}\n"
		return digest+"\t\t%\shufflegroup{exam}\n\t\t%\insertgroup{exam}\n\n"+res

	def save(self):
		name = self.file_name[self.file_name.rfind('/')+1:self.file_name.rfind('.')]
		output = open(self.folder+"/"+name+".tex", "w")
		output.write(self.__str__())

class Question:
	def __init__(self,xmlQ,c,n,f,x,r=False):
		self.folder = f
		self.xtra_f = x
		self.risky = r
		self.id = unidecode(c+"."+mlang_2_multiling(xmlQ.find("name/text").text.replace(" ",""),"en")+f".{n}")
		self.q = self.strip_tags(xmlQ.find("questiontext/text").text)
		self.category = c
		self.env = "todo:"+xmlQ.attrib["type"]
		if xmlQ.find("defaultgrade") != None:
			self.max = float(xmlQ.find("defaultgrade").text)
		self.parseImages(xmlQ.findall(".//file"))

	def parseImages(self, file_elements):
		for i in file_elements:
			name = i.attrib["name"]
			im = Image.open(BytesIO(base64.b64decode(i.text)))
			ext = name[name.rfind('.')+1:].upper()
			im.save(self.folder+self.xtra_f+name, ext)

	def strip_tags(self, txt):
		return strip_tags(mlang_2_multiling(txt), self.folder, self.xtra_f, self.risky, self.id)

	def __str__(self):
		#return "\\element{" + self.category + "}{\n\t\\begin{" + self.env + "}{" + self.id + "}\\nbpoints{" + score_2_str(self.max) + "}\n\t\t" + self.q + "\\end{" + self.env + "}\n"
		return ""

class Cloze(Question):
	zones = ['A', 'B', 'C', 'D', 'E', 'F','G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
	PC = "øØ" #character that replaces "%" (cf. url decode pb)
	def __init__(self, xmlQ,c,n,f,x,r):
		super().__init__(xmlQ,c,n,f,x,r)
		self.max = float(xmlQ.find("penalty").text)
		self.choices = []
		self.choices_num = {}
		self.choice_type = MCQ.VERTICAL
		self.env = "questionmult"
		self.q = self.strip_tags(xmlQ.find("questiontext/text").text.replace("%",Cloze.PC))
		if self.__class__.__name__ == "Cloze":
			self.__parseAnswers(self.q)

	def __parseAnswers(self, txt):
		questions = findall(r'\{:MCS?:([^\}]*)\}', txt, DOTALL)
		i = 0
		for c in questions:
			self.create_choices(c, Cloze.zones[i], len(questions))
			txt = txt.replace(r"{:MCS:"+c+"}", "\\underline{~~~~~~~~}("+Cloze.zones[i]+")").replace(r"{:MC:"+c+"}", "\\underline{~~~~~~~~}("+Cloze.zones[i]+")")
			i += 1
		self.q = txt

	def create_choices(self, c_list, l, n):
		self.choices_num[l] = 0
		choices = c_list.split("~")
		for c in choices:
			self.choices_num[l] += 1
			if c[0] == "=":
				self.choices.append(Answer(f"{l}) {c[1:]}", 100/n, self.max))
			elif c[0:2] == Cloze.PC:
				scores = c.split(Cloze.PC)
				self.choices.append(Answer(f"{l}) {scores[2]}", float(scores[1]), self.max))
			else :
				self.choices.append(Answer(f"{l}) {c}", 0, self.max))

	def __str__(self):
		res = "\\element{" + self.category + "}{\n\t\\begin{" + self.env + "}{" + self.id + "}\\nbpoints{" + score_2_str(self.max) + "}\\\\\n" + self.q +"\n\t\t\\begin{multicols}{"+str(max(2,len(self.choices_num)))+"}\n\t\t\t\\begin{choices}[o]\n\\AMCnoCompleteMulti"
		res += "\n"
		l = 0
		i = 0
		for c in self.choices:
			i += 1
			res += str(c)
			if self.choices_num[Cloze.zones[l]] == i:
				i = 0
				l += 1
				if l < len(self.choices_num):
					res += "\\vfill\\null\n\\columnbreak\n"
		res += "\n\t\t\t\\end{choices} \n\t\t\\end{multicols} \n\t\\end{" + self.env + "}\n}\n\n"
		return res

class Answer:
	def __init__(self,t,b,max,fb=None):
		self.status = float(b) > 0
		self.text = t
		self.points = round((float(b)/100)*float(max),3)
		self.feedback = fb

	def __str__(self):
		res = "\t\t\t"
		if self.status:
			res += "\\correctchoice{"
		else:
			res += "\\wrongchoice{"
		res += self.text
		res+="}"
		if self.points >= 0:
			res += "\\bareme{b="+str(self.points)+"}"
		else:
			res += "\\bareme{m="+str(self.points)+"}"
		if self.feedback != None:
			res += "\n\\explain{"+self.feedback+"}"
		return res+"\n"


class MCQ(Question):
	HORIZONTAL = 1
	VERTICAL = 0
	def __init__(self, xmlQ,c,n,f,x,r):
		super().__init__(xmlQ,c,n,f,x,r)
		self.choices = []
		self.choice_type = MCQ.VERTICAL
		self.env = "questionmult"
		if self.__class__.__name__ == "MCQ":
			self.__parseAnswers(xmlQ)

	def __parseAnswers(self, xmlQ):
		self.shuffle = xmlQ.find("shuffleanswers").text == "true"
		for a in xmlQ.findall("answer"):
			fb = a.find("feedback/text").text
			if fb != None:
				fb = self.strip_tags(mlang_2_multiling(fb))
			self.choices.append(Answer(self.strip_tags(a.find("text").text), a.attrib['fraction'], self.max, fb))

	def get_choice_env(self):
		if self.choice_type == MCQ.VERTICAL:
			return "choices"
		else:
			return "choiceshoriz"

	def __str__(self):
		res = """\\element{"""+self.category+"""}{
	\\begin{"""+self.env+"""}{"""+self.id+"""}\\nbpoints{"""+score_2_str(self.max)+"""}\\\\
		"""+self.q+"\n\t\t\\begin{"+self.get_choice_env()+"}"
		if not self.shuffle:
			res += "[o]"
		res += "\n"
		for c in self.choices:
			res += str(c)
		res += "\t\t\\end{"+self.get_choice_env()+"}\n\t\\end{"+self.env+"}\n}\n\n"
		return res

class TF(MCQ):
	def __init__(self, xmlQ,c,n,f,x,r):
		super().__init__(xmlQ,c,n,f,x,r)
		self.choices = []
		self.env = "question"
		self.shuffle = False
		if self.__class__.__name__ == "TF":
			self.__parseAnswers(xmlQ)

	def __parseAnswers(self, xmlQ):
		for a in xmlQ.findall("answer"):
			fb = a.find("feedback/text").text
			if fb != None:
				fb = self.strip_tags(mlang_2_multiling(fb))
			if a.find("text").text == "true":
				self.choices.append(Answer("\\multiling{vrai}{true}", a.attrib['fraction'], self.max, fb))
			else:
				self.choices.append(Answer("\\multiling{faux}{false}", a.attrib['fraction'], self.max, fb))

class ShortAnswer(TF):
	def __init__(self, xmlQ,c,n,f,x,r,l=2):
		super().__init__(xmlQ,c,n,f,x,r)
		self.nb_lines = l
		if self.__class__.__name__ == "ShortAnswer":
			self.__parseAnswers(xmlQ)

	def __parseAnswers(self, xmlQ):
		note = 0
		while note < self.max:
			self.choices.append(note)
			note += 0.25

	def __str__(self):
		res = """\\element{"""+self.category+"""}{
	\\begin{"""+self.env+"""}{"""+self.id+"""}\\nbpoints{"""+score_2_str(self.max)+"""}\\\\
		"""+self.q+"\n\t\t\\AMCOpen{lineheight=0.6cm,lines="+str(self.nb_lines)+"}{"
		for c in self.choices:
			res += "\\wrongchoice{"+score_2_str(c)+"}\\scoring{"+score_2_str(c)+"}"
		res += "\\correctchoice{"+score_2_str(self.max)+"}\\scoring{"+score_2_str(self.max)+"}"
		res += "}\n\t\\end{"+self.env+"}\n}\n\n"
		return res

if __name__ == "__main__":
	quizz = Quizz("data/KNM questions.xml", "data")
	#quizz = Quizz("data/quiz-GI-4.xml" , "data")
	#print(quizz)
	quizz.save()