r'''
-----------------------------------------------------------------------------
    webquiz_xml | xml reader for reading the xml file generated by tex4ht
-----------------------------------------------------------------------------

    Copyright (C) Andrew Mathas and Donald Taylor, University of Sydney

    Distributed under the terms of the GNU General Public License (GPL)
                  http://www.gnu.org/licenses/

    This file is part of the Math_quiz system.

    <Andrew.Mathas@sydney.edu.au>
    <Donald.Taylor@sydney.edu.au>
-----------------------------------------------------------------------------
'''

# -*- encoding: utf-8 -*-

import xml.sax

# imports of webquiz code
import webquiz_util

# ---------------------------------------------------------------------------------------
def ReadWebQuizXmlFile(quizfile, defaults):
    r'''
    Set up, call and then return the xml parser
    for the quiz web page
    '''
    parser = xml.sax.make_parser()
    quiz = QuizHandler(defaults)
    parser.setContentHandler(quiz)
    parser.setErrorHandler(quiz)
    parser.setDTDHandler(quiz) # as far as I can see this does nothing...
    parser.parse(quizfile)
    parser.close()
    return quiz


# ---------------------------------------------------------------------------------------
class Data(object):
    r'''
    A wrapper object class that holds the data for the different
    components of the quiz.
    '''
    def __init__(self, **args):
        '''
        Accepts key-value pairs, each of which is stored as an attribute
        '''
        self._items = args.items()
        for key, val in args.items():
            setattr(self, key, val)


class QuizHandler(xml.sax.ContentHandler):
    """
        The content handler gives the xml tags to `startElement`, which
        initialises the webquiz tags, and then `endElement` attaches the
        content of each webquiz tag tothe appropriate part of `self`. Any end
        tag that is ot special to webquiz has its contents appended to
        `self.text`. Any tag that contains `DeFaUlT` is set to the system
        default using the `defaults` dictionary.
    """

    def __init__(self, defaults):
        self.defaults = defaults

        # arrays for the different quiz components
        self.discussion_list = []
        self.question_list = []
        self.quiz_index = []

        # these will contain the link and meta elements from the xml file but they
        # are ignored by webquiz.py
        self.link_list = []
        self.meta_list = []

        # to add mathjs when an eval comparison is used
        self.mathjs = False

        # the following tags have defaults set by `defaults`
        self.setting_tags = [
               'department',
               'department_url',
               'institution',
               'institution_url',
               'language',
               'theme',
        ]
        # quiz data
        for tag in self.setting_tags:
            setattr(self, tag, defaults[tag])
        self.breadcrumb = ''
        self.text = ''
        self.after_text = ''
        self.title = ''
        self.unit_code = ''
        self.unit_name = ''

        # keep track of current tags for debugging...
        self.current_tags=[]

    def webquiz_debug(self, msg):
        r'''
            Customised debugging message for the xml module
        '''
        webquiz_util.webquiz_debug(self.defaults.debugging, 'xml: '+msg)

    def webquiz_error(self, msg, err=None):
        r'''
            Customised error message for the xml module
        '''
        webquiz_util.webquiz_error(self.defaults.debugging, 'xml: '+msg, err)

    def set_default_attribute(self, key, value):
        ''' Set the attribute `key` of self, using the default value if
        `value=='DeFaUlT'`.
        '''
        if value.strip() == 'DeFaUlT':
            setattr(self, key, self.defaults[key])
        else:
            setattr(self, key, value)
        self.webquiz_debug('Just set "{}" equal to "{}" from "{}"'.format(key, getattr(self, key), value))

    #---- start of start elements --------------------------------------------
    def startElement(self, tag, attributes):
        '''
            At the start of each webquiz xml tag we need to pull out the
            attributes and place
        '''
        self.webquiz_debug('Starting tag for '+tag)
        self.current_tags.append(tag)

        if hasattr(self, 'start_'+tag):
            getattr(self, 'start_'+tag)(attributes)

        elif tag in ['department', 'institution', 'uni']:
            for key in attributes.keys():
                self.set_default_attribute(tag, attributes.get(key))

    def start_webquiz(self, attributes):
        r'''
        Start element for tag="webquiz". Initialise the quix and extract
        and process the list of attributes.
        '''
        for key in attributes.keys():
            self.set_default_attribute(key, attributes.get(key))

        # convert the following attibutes to booleans
        for key in ['debugging', 'hide_side_menu', 'one_page', 'pst2pdf', 'random_order']:
            setattr(self, key, getattr(self, key)=='true')

        setattr(self, 'language', self.language.lower())
        setattr(self, 'theme', self.theme.lower())

        # set debugging mode from the latex file...from this point on
        self.defaults.debugging = self.defaults.debugging or self.debugging

    def start_link(self, attributes):
        r'''
        Start element for tag="link"
        '''
        self.link_list.append({key: attributes.get(key) for key in attributes.keys()})

    def start_meta(self, attributes):
        r'''
        Start element for tag="meta"
        '''
        self.meta_list.append({key: attributes.get(key) for key in attributes.keys()})

    def start_breadcrumb(self, attributes):
        r'''
        Start element for tag="breadcrumb"
        '''
        self.set_default_attribute('breadcrumbs', attributes.get('breadcrumbs'))

    def start_unit_name(self, attributes):
        r'''
        Start element for tag="unit_name"
        '''
        self.set_default_attribute('unit_url', attributes.get('url'))
        self.quizzes_url = attributes.get('quizzes_url')
        if self.quizzes_url == 'DeFaUlT':
            self.quizzes_url = self.unit_url + '/Quizzes'

    def start_discussion(self, attributes):
        r'''
        Start element for tag="discussion"
        '''
        discussion = Data(heading = '',
                          short_heading = '',
                          text = ''  # The text of the discussion
        )
        self.discussion_list.append(discussion)

    def start_question(self, attributes):
        r'''
        Start element for tag="question"
        '''
        self.question_list.append(
            Data(text = '',      # The text of the question
                type = None,    # input, or single or multiple choice
                after_text = '' # text at end of question
            )
        )

    def start_answer(self, attributes):
        r'''
        Process the different question types, items choice and feedback
        '''
        if self.question_list[-1].type != None:
            self.webquiz_error('question {} has too many question types: {} and input'.format(
                    len(self.question_list)+1, self.question_list[-1].type)
            )
        self.question_list[-1].type = 'input'
        self.question_list[-1].answer = ''
        self.question_list[-1].feedback_right = ''
        self.question_list[-1].feedback_wrong = ''
        self.question_list[-1].text += self.text
        self.text = ''

        self.question_list[-1].comparison = attributes.get('comparison')
        self.question_list[-1].prompt = attributes.get('prompt')=='true'
        if self.question_list[-1].comparison in ['complex', 'number']:
            self.mathjs = True

    def start_choice(self, attributes):
        r'''
        Start element for tag="choice"
        '''
        if self.question_list[-1].type != None:
            self.webquiz_error('question {} has too many question types: {} and choice'.format(
                    len(self.question_list)+1, self.question_list[-1].type)
            )
        self.question_list[-1].type = attributes.get('type')
        self.question_list[-1].columns = int(attributes.get('columns'))
        self.question_list[-1].items = []
        self.question_list[-1].correct = 0
        self.question_list[-1].text += self.text
        self.text = ''

    def start_item(self, attributes):
        r'''
        Start element for tag="item"
        '''
        self.question_list[-1].items.append(
                Data(correct= attributes.get('correct'),
                     symbol=attributes.get('symbol'),
                     feedback='',
                     text=''
                    )
        )
        if attributes.get('correct')=='true':
            self.question_list[-1].correct += 1

    def start_index_item(self, attributes):
        r'''
        Finally look after the index file
        '''
        self.quiz_index.append(Data(
                prompt=attributes.get('prompt')=='true',
                url=attributes.get('url'),
                title=''
            )
        )

    def start_when(self, attributes):
        r'''
        start element for tag="when"
        '''
        if self.text.strip() != '':
            self.question_list[-1].after_text += ' '+self.text.strip()
            self.webquiz_debug('After_text is now {}'.format(self.question_list[-1].after_text))
            self.text = ''
        self.current_tags[-1] = 'feedback_'+attributes.get('type')

    #---- end of start elements ---------------------------------------------

    def endElement(self, tag):
        self.webquiz_debug('ending tag for {} (should be {})'.format(tag, self.current_tags[-1])) 

        reset_text = True
        if hasattr(self, 'end_'+tag):
            getattr(self, 'end_'+tag)()
            self.text = ''

        elif tag in self.setting_tags:
            self.set_default_attribute(tag, self.text)
            self.text = ''

        elif tag in ['heading', 'short_heading']:
            setattr(self.discussion_list[-1], tag, self.text.strip())

        elif tag in ['breadcrumb', 'title', 'unit_code', 'unit_name']:
            setattr(self, tag, self.text.strip())

        else:
            # self.text lives to be used another day
            reset_text = False

        if reset_text:
            self.text = ''

        # remove the last tag from the tag list
        self.current_tags.pop()

    #---- start of the end elements ------------------------------------------

    def end_answer(self):
        r'''
        Process end tag when tag="answer"
        '''
        self.question_list[-1].answer = self.text.strip()

    def end_discussion(self):
        r'''
        Process end tag when tag="discussion"
        '''
        self.discussion_list[-1].text = self.text.strip()

    def end_item(self):
        r'''
        Process end tag when tag="item"
        '''
        self.question_list[-1].items[-1].text = self.text.strip()

    def end_feedback(self):
        r'''
        Process end tag when tag="feedback"
        '''
        self.question_list[-1].items[-1].feedback = self.text.strip()

    def end_question(self):
        r'''
        Process end tag when tag="question"
        '''
        # first some error checking
        if self.question_list[-1].type == None:
                self.webquiz_error('Question {} does not have an \\answer or choice environment'.format(
                              len(self.question_list)+1))

        elif hasattr(self.question_list[-1], 'items'):
            if len(self.question_list[-1].items)==0:
                self.webquiz_error('question {} has no multiple choice items'.format(
                              len(self.question_list)+1))

            if self.question_list[-1].type=='single' and self.question_list[-1].correct!=1:
                self.webquiz_error('question {} is single-choice but has {} correct answers'.format(
                                len(self.question_list)+1,
                                self.question_list[-1].correct
                             )
                )
        elif not hasattr(self.question_list[-1], 'answer') or self.question_list[-1].answer=='':
            self.webquiz_error('question {} does have not an \answer or multiple choice'.format(
                          len(self.question_list)+1))

        if self.text.strip() != '':
            self.question_list[-1].after_text += ' '+self.text.strip()

    def end_index_item(self):
        r'''
        Process end tag when tag="index_item"
        '''
        self.quiz_index[-1].title = self.text.strip().replace('\n',' ').replace('\r',' ')

    def end_when(self):
        r'''
        Process end tag when tag="index_item"
        '''
        self.webquiz_debug('WHEN: Adding text to '+self.current_tags[-1])
        setattr(self.question_list[-1], self.current_tags[-1], self.text.strip())

    #---- end of end elements -----------------------------------------------

    def characters(self, text):  #data,start,length):
        r'''
        Append everything to `self.text`
        '''
        self.text += text

    def error(self, e):
        self.webquiz_error('unknown error', e)

    def fatalError(self, e):
        self.webquiz_error('unknown fatal error', e)