#!/usr/local/cpython-3.6/bin/python3

"""Ask questions in a way that facilitates memorization."""

import re
import sys
import time
import random
import typing
import readline
# port importlib
import subprocess

REPS = 2
MULTILINE = False
PRODUCE_SPEECH = False
AUTOREPEAT = False


def make_used(variable: typing.Any) -> None:
    """Convince pyflakes that variable is used."""
    assert True or variable


def clear_history() -> None:
    """Erase input history, so the user can't just uparrow to get back old answers."""
    readline.clear_history()


def speak(string: str) -> None:
    """Say something via flite."""
    command = ["flite -voice rms -t '{}' > /dev/null 2>&1".format(string)]
    subprocess.call(command, shell=True)
    if AUTOREPEAT:
        time.sleep(0.5)
        subprocess.call(command, shell=True)


def positive_reinforcement() -> None:
    """Output a nice message when the user gets a question right."""
    list_ = [
        'That is correct!',
        'Good job!',
        'Well done!',
        'Yay!',
        'Terrific!',
        'Very nice!',
        'You got it!',
        'Very cool!',
        'Great.  Keep up the good work!',
    ]

    selection = random.choice(list_)
    print(selection)


class QuestionAnswer:
    # pylint: disable=too-few-public-methods
    """
    Hold a question and answer.

    Enable the ability to pose a question and get a response.
    """

    def __init__(self, question_string: str, answer_string: str) -> None:
        """Initialize."""
        self.question_string = question_string.strip()
        self.answer_string = answer_string.strip()
        self.correct_in_a_row = 0

    def question_done(self) -> bool:
        """Ask the question, get the response, return True iff the question was answered correctly enough times in a row."""
        if PRODUCE_SPEECH:
            speak(self.question_string)
        else:
            print('Question:')
            print(self.question_string)
        print('Type answer - from longer-term memory:')
        if MULTILINE:
            lines = []
            while True:
                clear_history()
                line = input()
                if not line:
                    break
                lines.append(line)
            user_answer = '\n'.join(lines)
        else:
            clear_history()
            user_answer = input().strip()
        if user_answer == self.answer_string:
            positive_reinforcement()
            self.correct_in_a_row += 1
            print('Hit enter to continue.')
            make_used(input())
            return self.correct_in_a_row == REPS
        else:
            self.correct_in_a_row = 0
            while True:
                print('Sorry, that is incorrect')
                print()
                print('The question was:')
                print(self.question_string)
                print()
                print('The correct answer is:')
                print(self.answer_string)
                print()
                print('Hit enter to immediately type in the correct answer from short-term memory')
                make_used(input())

                clear_screen()
                if PRODUCE_SPEECH:
                    speak('The question was')
                    speak(self.question_string)
                else:
                    print('The question was:')
                    print(self.question_string)
                    print()
                print('Type the answer - from short-term memory:')
                re_answer = input()
                if re_answer == self.answer_string:
                    print('Good.  Hit enter to continue')
                    make_used(input())
                    break

            return False


def usage(retval: int) -> None:
    """Output a usage message."""
    if retval == 0:
        writer = sys.stdout.write
    elif retval == 1:
        writer = sys.stderr.write
    else:
        sys.stderr.write('{}: Internal error: retval not 0 or 1\n'.format(sys.argv[0]))
        sys.exit(1)

    writer('Usage: {} --file filename --reps 2\n'.format(sys.argv[0]))
    writer('\n')
    writer('filename must contain an options line, followed by question-answer pairs separated by\n')
    writer('a %% token.  Within this, the question is separated from the answer by a - token.\n')

    sys.exit(retval)


def get_question_answers(filename: str) -> typing.Dict[str, QuestionAnswer]:
    # pylint: disable=global-statement
    """Read the options, questions and answers from filename."""
    global MULTILINE
    global PRODUCE_SPEECH

    with open(filename, 'r', encoding='utf-8') as file_:
        options_line = file_.readline().strip()
        options = options_line.split(',')
        if len(options) == 1 and options[0] == '':
            # This means the options line was empty
            pass
        else:
            if 'multiline' in options:
                MULTILINE = True
                options.remove('multiline')
            if 'produce_speech' in options:
                PRODUCE_SPEECH = True
                options.remove('produce_speech')
            else:
                MULTILINE = False
            if options:
                sys.stderr.write('{}: One or more unrecognized options: {}\n'.format(sys.argv[0], options))
                usage(1)

        data = file_.read()

    question_answer_strings = re.split('^%%$', data, flags=re.MULTILINE)
    question_answers = dict()
    for question_answer_string in question_answer_strings:
        sublist = re.split('^-$', question_answer_string, flags=re.MULTILINE)
        if len(sublist) != 2:
            print(sublist)
            raise ValueError('question has multi -')
        question_string = sublist[0]
        answer_string = sublist[1]
        question_answer = QuestionAnswer(question_string, answer_string)
        question_answers[question_answer.question_string] = question_answer

    return question_answers


def maybe_plural(number: int) -> str:
    """Return an s if number is not 1."""
    if number == 1:
        return ''
    else:
        return 's'


def clear_screen() -> None:
    """Clear the screen using an ugly escape sequence, to avoid the complexity of curses."""
    # We assume a vt100-like terminal emulator.
    # This clears the screen:
    sys.stdout.write(chr(27) + "[2J" + chr(27) + '[H')
    # This erases the scrollback buffer:
    sys.stdout.write(chr(27) + 'c')
    sys.stdout.flush()


def main() -> None:
    # pylint: disable=global-statement
    """Drill on quiz."""
    global REPS

    while sys.argv[1:]:
        if sys.argv[1] == '--file':
            filename = sys.argv[2]
            del sys.argv[1]
        elif sys.argv[1] == '--reps':
            REPS = int(sys.argv[2])
            del sys.argv[1]
        elif sys.argv[1] in ('-h', '--help'):
            usage(0)
        else:
            sys.stderr.write('{}: Unrecognized command line option: {}\n'.format(sys.argv[0], sys.argv[1]))
            usage(1)
        del sys.argv[1]

    question_answers = get_question_answers(filename)

    original_num_qa = len(question_answers)

    prior_key = None

    time0 = time.time()

    while True:
        keys = list(question_answers)

        len_keys = len(keys)

        if len_keys == 0:
            # Good, we're all done
            break

        clear_screen()

        print('{} question{} left'.format(len_keys, maybe_plural(len_keys)))

        # Get a random question - except disallow asking the same
        # question twice in a row, except one there is only one question
        # remaining.
        if keys[1:]:
            while True:
                key = random.choice(keys)
                if key != prior_key:
                    prior_key = key
                    break
        else:
            key = keys[0]
            prior_key = key

        if question_answers[key].question_done():
            del question_answers[key]

    time1 = time.time()

    print('Congratulations!  You got all {} answers right {} time{} in a row!'.format(original_num_qa, REPS, maybe_plural(REPS)))

    duration_in_seconds = (time1 - time0)
    duration_in_minutes = duration_in_seconds / 60.0

    print('It took you {} minutes to complete the whole test.'.format(round(duration_in_minutes, 1)))
    print("That's an average of {} questions per minute!".format(round(original_num_qa / duration_in_minutes, 2)))


main()