#!/usr/bin/env python

import sys
import random
import os
from typing import List
from dataclasses import dataclass
from argparse import ArgumentParser

import yaml
from markdown import markdown

@dataclass
class Quiz:
    questions: List

    def shuffle(self):
        random.shuffle(self.questions)
        [q.shuffle() for q in self.questions]

    @classmethod
    def from_question_list(cls, question_list):
        questions = [Question(prompt=q['prompt'],
                              options=q.get('options'),
                              additional_resource = q.get('additional_resource'),
                              answer=q.get('answer'),
                              fixed_order = q.get('fixed_order', False))
                     for q in question_list]
        quiz = cls(questions=questions)
        return quiz

    def render_test_html(self, title, css='', version=1):
        q_html = [q.render_test_html() for q in self.questions]
        n_questions = len(q_html)
        css_html = '<style type="text/css">' + css + '</style>'
        head_html = '<head>' + css_html + '</head>'
        title_html = '<h1>' + title + '</h1>'
        desc_html = f'<p><em>Version {version}<br>{n_questions} questions</em></p>'
        body_html = '<body>' + title_html + desc_html + make_ol(q_html) + '</body>'
        return head_html + body_html
    
    def render_answer_key_html(self, title, css='', version=1, verbose=False):
        q_html = [q.render_answer_key_html(verbose=verbose)
                  for q in self.questions]
        css_html = '<style type="text/css">' + css + '</style>'
        head_html = '<head>' + css_html + '</head>'
        title_html = '<h1>' + title + '</h1>'
        desc_html = f'<p><em>Version {version}<br>Answer Key</em></p>'
        body_html = '<body>' + title_html + desc_html + make_ol(q_html) + '</body>'
        return head_html + body_html

class Question:
    def __init__(self, prompt, additional_resource='', options=None,
                 answer=None, fixed_order=False):
        self.prompt = markdown(prompt)
        self.additional_resource = additional_resource
        if options is not None:
            self.options = [markdown(option) for option in options]
        else:
            self.options = None
        # If no correct answer is specified, assume 'a'.
        self.answer = answer or 'a'
        self.fixed_order = fixed_order

    def render_test_html(self) -> str:
        prompt_html = self.prompt
        if self.additional_resource:
            additional_resource_html = self.additional_resource + '<br>'
        else:
            additional_resource_html = ''
        # If the question has options, render them as an <ol>.
        if self.options:
            options_html = make_ol(self.options, ol_type='a')
        # If the question doesn't have options, it's a short answer and needs some space
        else:
            options_html = '<br>' * 4
        result_html = (prompt_html +
                       additional_resource_html +
                       options_html)
        return result_html

    def render_answer_key_html(self, verbose=False) -> str:
        prompt_html = self.prompt
        if self.additional_resource:
            additional_resource_html = self.additional_resource + '<br>'
        else:
            additional_resource_html = ''
        answer_html = markdown(self.answer)
        if verbose:
            result_html = (prompt_html +
                           additional_resource_html +
                           answer_html)
        else:
            result_html = answer_html
        return result_html

    def shuffle(self) -> None:
        if self.options and not self.fixed_order:
            # Generate a new order (eg 2,0,1,3)
            order = list(range(len(self.options)))
            random.shuffle(order)
            self.options = [self.options[i] for i in order]
            # Update the correct answer based on where the old correct answer
            # ended up.
            answer_map = {'a': 0, 'b': 1, 'c': 2, 'd': 3, 'e': 4, 'f': 5}
            rev_answer_map = ['a', 'b', 'c', 'd', 'e', 'f']
            # Get the index of the old right answer from its letter position.
            old_answer_index = answer_map[self.answer]
            new_answer_index = order.index(old_answer_index)
            # Translate back to a letter.
            new_answer_letter = rev_answer_map[new_answer_index]
            self.answer = new_answer_letter
            

def make_ol(strings, ol_type='1'):
    '''Make an ordered list out of some html strings.'''
    html = '</li><li>'.join(strings)
    html = f'<ol type="{ol_type}"><li>{html}</li></ol>'
    return html

def parse_args():
    parser = ArgumentParser()
    parser.add_argument('quiz_filepath',
                        help='path to the yaml file of the quiz')
    parser.add_argument('-n', '--nversions',
                        type=int, default=1,
                        help='number of versions to create')
    parser.add_argument('-u', '--unshuffled',
                        action='store_true',
                        help="don't shuffle questions and options")
    args = parser.parse_args()
    return args

def gen_html(quiz, title='My Quiz', css='', version=1):
    q_html = [q.render_test_html() for q in quiz.questions]
    n_questions = len(q_html)
    css_html = '<style type="text/css">' + css + '</style>'
    head_html = '<head>' + css_html + '</head>'
    title_html = '<h1>' + title + '</h1>'
    desc_html = f'<p><em>Version {version}<br>{n_questions} questions</em></p>'
    body_html = '<body>' + title_html + desc_html + make_ol(q_html) + '</body>'
    return head_html + body_html

def main():
    args = parse_args()
    # The quiz_name should be the quiz_file with its extension removed.
    # TODO If the quiz is outside the current directory (ie this is a path
    # with slashes) this will produce a nonsense quiz_name.
    quiz_name = '.'.join(args.quiz_filepath.split('.')[:-1])
    # Create a folder for output.
    out_folder = quiz_name + '_quizmaker'
    os.makedirs(out_folder, exist_ok=True)
    with open(args.quiz_filepath, 'r') as f:
        quiz_data = yaml.safe_load(f)
    for i in range(args.nversions):
        quiz = Quiz.from_question_list(quiz_data['questions'])
        if not args.unshuffled:
            quiz.shuffle()
        title = quiz_data.get('title', 'My Quiz')
        css = quiz_data.get('css', '')
        quiz_html = quiz.render_test_html(title=title, css=css, version=i+1)
        quiz_key_html = quiz.render_answer_key_html(title=title, css=css,
                                                     version=i+1)
        quiz_file_name = f'{out_folder}/{quiz_name}_ver_{i + 1}.html'
        quiz_key_file_name = f'{out_folder}/{quiz_name}_ver_{i + 1}_answer_key.html'
        with open(quiz_file_name, 'w') as f:
            f.write(quiz_html)
        with open(quiz_key_file_name, 'w') as f:
            f.write(quiz_key_html)

if __name__ == '__main__':
    main()
