# pylint: disable=too-many-lines
#
# File:   courseware/capa/inputtypes.py
#

"""
Module containing the problem elements which render into input objects

- textline
- textbox (aka codeinput)
- schematic
- choicegroup (aka radiogroup, checkboxgroup)
- imageinput  (for clickable image)
- optioninput (for option list)
- filesubmission (upload a file)
- crystallography
- vsepr_input
- drag_and_drop
- formulaequationinput
- chemicalequationinput

These are matched by *.html files templates/*.html which are django templates with the
actual html.

Each input type takes the xml tree as 'element', the previous answer as 'value', and the
graded status as'status'
"""

# TODO: make hints do something

# TODO: make all inputtypes actually render msg

# TODO: remove unused fields (e.g. 'hidden' in a few places)

# TODO: add validators so that content folks get better error messages.


# Possible todo: make inline the default for textlines and other "one-line" inputs.  It probably
# makes sense, but a bunch of problems have markup that assumes block.  Bigger TODO: figure out a
# general css and layout strategy for capa, document it, then implement it.


import json
import logging
import re
import shlex  # for splitting quoted strings
import sys
import time
from datetime import datetime

import html5lib
import nh3
import pyparsing
import six
from calc.preview import latex_preview
from chem import chemcalc
from lxml import etree

from xblocks_contrib.problem.capa.xqueue_interface import XQUEUE_TIMEOUT
from xblocks_contrib.problem.markup import HTML, Text
from xblocks_contrib.problem.stringify import stringify_children

from . import xqueue_interface
from .registry import TagRegistry
from .util import sanitize_html

log = logging.getLogger(__name__)

registry = TagRegistry()


class Status:
    """
    Problem status
    attributes: classname, display_name, display_tooltip
    """

    css_classes = {
        # status: css class
        "unsubmitted": "unanswered",
        "incomplete": "incorrect",
        "queued": "processing",
    }
    __slots__ = ("classname", "_status", "display_name", "display_tooltip")

    def __init__(self, status, gettext_func=str):
        self.classname = self.css_classes.get(status, status)
        _ = gettext_func
        names = {
            "correct": _("correct"),
            "incorrect": _("incorrect"),
            "partially-correct": _("partially correct"),
            "incomplete": _("incomplete"),
            "unanswered": _("unanswered"),
            "unsubmitted": _("unanswered"),
            "submitted": _("submitted"),
            "queued": _("processing"),
        }
        tooltips = {
            # Translators: these are tooltips that indicate the state of an assessment question
            "correct": _("This answer is correct."),
            "incorrect": _("This answer is incorrect."),
            "partially-correct": _("This answer is partially correct."),
            "queued": _("This answer is being processed."),
        }
        tooltips.update(dict.fromkeys(["incomplete", "unanswered", "unsubmitted"], _("Not yet answered.")))
        self.display_name = names.get(status, str(status))
        self.display_tooltip = tooltips.get(status, "")
        self._status = status or ""

    def __str__(self):
        return self._status

    def __repr__(self):
        return f"Status({self._status!r})"

    def __eq__(self, other):
        return self._status == str(other)

    def __hash__(self):
        return hash(str(self))


class Attribute:
    """
    Allows specifying required and optional attributes for input types.
    """

    # want to allow default to be None, but also allow required objects
    _sentinel = object()

    def __init__(  # pylint: disable=too-many-positional-arguments,too-many-arguments
        self, name, default=_sentinel, transform=None, validate=None, render=True
    ):
        """
        Define an attribute

        name (str): then name of the attribute--should be alphanumeric (valid for an XML attribute)

        default (any type): If not specified, this attribute is required.  If specified, use this as the default value
                        if the attribute is not specified.  Note that this value will not be transformed or validated.

        transform (function str -> any type): If not None, will be called to transform the parsed value into an internal
                        representation.

        validate (function str-or-return-type-of-tranform -> unit or exception): If not None, called to validate the
                       (possibly transformed) value of the attribute.  Should raise ValueError with a helpful message if
                       the value is invalid.

        render (bool): if False, don't include this attribute in the template context.
        """
        self.name = name
        self.default = default
        self.validate = validate
        self.transform = transform
        self.render = render

    def parse_from_xml(self, element):
        """
        Given an etree xml element that should have this attribute, do the obvious thing:
          - look for it.  raise ValueError if not found and required.
          - transform and validate.  pass through any exceptions from transform or validate.
        """
        val = element.get(self.name)
        if self.default == self._sentinel and val is None:
            raise ValueError(f"Missing required attribute {self.name}.")

        if val is None:
            # not required, so return default
            return self.default

        if self.transform is not None:
            val = self.transform(val)

        if self.validate is not None:
            self.validate(val)

        return val


class InputTypeBase:
    """
    Abstract base class for input types.
    """

    template = None

    def __init__(self, system, xml, state):
        """
        Instantiate an InputType class.  Arguments:

        - system    : LoncapaModule instance which provides OS, rendering, and user context.
                      Specifically, must have a render_template function.
        - xml       : Element tree of this Input element
        - state     : a dictionary with optional keys:
                      * 'value'  -- the current value of this input
                                    (what the student entered last time)
                      * 'id' -- the id of this input, typically
                                "{problem-location}_{response-num}_{input-num}"
                      * 'status' (submitted, unanswered, unsubmitted)
                      * 'input_state' -- dictionary containing any inputtype-specific state
                                        that has been preserved
                      * 'feedback' (dictionary containing keys for hints, errors, or other
                         feedback from previous attempt.  Specifically 'message', 'hint',
                         'hintmode'.  If 'hintmode' is 'always', the hint is always displayed.)
        """

        self.xml = xml
        self.tag = xml.tag
        self.capa_system = system

        # NOTE: ID should only come from one place.  If it comes from multiple,
        # we use state first, XML second (in case the xml changed, but we have
        # existing state with an old id). Since we don't make this guarantee,
        # we can swap this around in the future if there's a more logical
        # order.

        self.input_id = state.get("id", xml.get("id"))
        if self.input_id is None:
            raise ValueError(f"input id state is None. xml is {etree.tostring(xml)}")

        self.value = state.get("value", "")

        feedback = state.get("feedback", {})
        self.msg = feedback.get("message", "")
        self.hint = feedback.get("hint", "")
        self.hintmode = feedback.get("hintmode", None)
        self.input_state = state.get("input_state", {})
        self.answervariable = state.get("answervariable", None)
        self.response_data = state.get("response_data")

        # put hint above msg if it should be displayed
        if self.hintmode == "always":
            self.msg = HTML("{hint}<br/>{msg}" if self.msg else "{hint}").format(
                hint=HTML(self.hint), msg=HTML(self.msg)
            )

        self.status = state.get("status", "unanswered")

        try:
            # Pre-parse and process all the declared requirements.
            self.process_requirements()

            # Call subclass "constructor" -- means they don't have to worry about calling
            # super().__init__, and are isolated from changes to the input
            # constructor interface.
            self.setup()
        except Exception as err:  # pylint: disable=broad-exception-caught
            # Something went wrong: add xml to message, but keep the traceback
            msg = f"Error in xml '{etree.tostring(xml)}': {err} "
            six.reraise(Exception, Exception(msg), sys.exc_info()[2])

    @classmethod
    def get_attributes(cls):
        """
        Should return a list of Attribute objects (see docstring there for details). Subclasses should override.  e.g.

        return [Attribute('unicorn', True), Attribute('num_dragons', 12, transform=int), ...]
        """
        return []

    def process_requirements(self):
        """
        Subclasses can declare lists of required and optional attributes.  This
        function parses the input xml and pulls out those attributes.  This
        isolates most simple input types from needing to deal with xml parsing at all.

        Processes attributes, putting the results in the self.loaded_attributes dictionary.  Also creates a set
        self.to_render, containing the names of attributes that should be included in the context by default.
        """
        # Use local dicts and sets so that if there are exceptions, we don't
        # end up in a partially-initialized state.
        loaded = {}
        to_render = set()
        for attribute in self.get_attributes():
            loaded[attribute.name] = attribute.parse_from_xml(self.xml)
            if attribute.render:
                to_render.add(attribute.name)

        self.loaded_attributes = loaded
        self.to_render = to_render

    def setup(self):
        """
        InputTypes should override this to do any needed initialization.  It is called after the
        constructor, so all base attributes will be set.

        If this method raises an exception, it will be wrapped with a message that includes the
        problem xml.
        """

    def handle_ajax(self, dispatch, data):
        """
        InputTypes that need to handle specialized AJAX should override this.

        Input:
            dispatch: a string that can be used to determine how to handle the data passed in
            data: a dictionary containing the data that was sent with the ajax call

        Output:
            a dictionary object that can be serialized into JSON. This will be sent back to the Javascript.
        """

    def _get_render_context(self):
        """
        Should return a dictionary of keys needed to render the template for the input type.

        (Separate from get_html to faciliate testing of logic separately from the rendering)

        The default implementation gets the following rendering context: basic things like value, id, status, and msg,
        as well as everything in self.loaded_attributes, and everything returned by self._extra_context().

        This means that input types that only parse attributes and pass them to the template get everything they need,
        and don't need to override this method.
        """
        context = {
            "id": self.input_id,
            "value": self.value,
            "status": Status(self.status, self.capa_system.i18n.gettext),
            "msg": self.msg,
            "response_data": self.response_data,
            "STATIC_URL": self.capa_system.STATIC_URL,
            "describedby_html": HTML(""),
        }

        # Generate the list of ids to be used with the aria-describedby field.
        descriptions = []

        # If there is trailing text, add the id as the first element to the list before adding the status id
        if "trailing_text" in self.loaded_attributes and self.loaded_attributes["trailing_text"]:
            trailing_text_id = "trailing_text_" + self.input_id
            descriptions.append(trailing_text_id)

        # Every list should contain the status id
        status_id = "status_" + self.input_id
        descriptions.append(status_id)
        descriptions.extend(list(self.response_data.get("descriptions", {}).keys()))
        description_ids = " ".join(descriptions)
        context.update({"describedby_html": HTML('aria-describedby="{}"').format(description_ids)})

        context.update((a, v) for (a, v) in six.iteritems(self.loaded_attributes) if a in self.to_render)
        context.update(self._extra_context())
        if self.answervariable:
            context.update({"answervariable": self.answervariable})
        return context

    def _extra_context(self):
        """
        Subclasses can override this to return extra context that should be passed to their templates for rendering.

        This is useful when the input type requires computing new template variables from the parsed attributes.
        """
        return {}

    def get_html(self):
        """
        Return the html for this input, as an etree element.
        """
        if self.template is None:
            raise NotImplementedError(f"no rendering template specified for class {self.__class__}")

        context = self._get_render_context()

        html = self.capa_system.render_template(self.template, context).strip()

        try:
            output = etree.XML(html)
        except etree.XMLSyntaxError as ex:
            # If `html` contains attrs with no values, like `controls` in <audio controls src='smth'/>,
            # XML parser will raise exception, so wee fallback to html5parser,
            #  which will set empty "" values for such attrs.
            try:
                output = html5lib.parseFragment(html, treebuilder="lxml", namespaceHTMLElements=False)[0]
            except IndexError as exc:
                raise ex from exc

        return output

    def get_user_visible_answer(self, internal_answer):
        """
        Given the internal representation of the answer provided by the user, return the representation of the answer
        as the user saw it.  Subclasses should override this method if and only if the internal represenation of the
        answer is different from the answer that is displayed to the user.
        """
        return internal_answer


# -----------------------------------------------------------------------------


@registry.register
class OptionInput(InputTypeBase):
    """
    Input type for selecting and Select option input type.

    Example:

    <optioninput options="('Up', 'Down')" correct="Up"/><text>The location of the sky</text>

    # TODO: allow ordering to be randomized
    """

    template = "optioninput.html"
    tags = ["optioninput"]

    @staticmethod
    def parse_options(options):
        """
        Given options string, convert it into an ordered list of (option_id, option_description) tuples, where
        id==description for now.  TODO: make it possible to specify different id and descriptions.
        """
        # convert single quotes inside option values to html encoded string
        options = re.sub(r"([a-zA-Z])('|\\')([a-zA-Z])", r"\1&#39;\3", options)
        options = re.sub(r"\\'", r"&#39;", options)  # replace already escaped single quotes
        # parse the set of possible options
        lexer = shlex.shlex(options[1:-1])

        lexer.quotes = "'"
        # Allow options to be separated by whitespace as well as commas
        lexer.whitespace = ", "

        # remove quotes
        # convert escaped single quotes (html encoded string) back to single quotes
        tokens = [x[1:-1].replace("&#39;", "'") for x in lexer]

        # make list of (option_id, option_description), with description=id
        return [(t, t) for t in tokens]

    @classmethod
    def get_attributes(cls):
        """
        Convert options to a convenient format.
        """
        return [Attribute("options", transform=cls.parse_options), Attribute("inline", False)]

    def _extra_context(self):
        """
        Return extra context.
        """
        _ = self.capa_system.i18n.gettext
        return {"default_option_text": _("Select an option")}


# -----------------------------------------------------------------------------


# TODO: consolidate choicegroup, radiogroup, checkboxgroup after discussion of
# desired semantics.


@registry.register
class ChoiceGroup(InputTypeBase):
    """
    Radio button or checkbox inputs: multiple choice or true/false.

    TODO: allow order of choices to be randomized, following lon-capa spec.
    Use the "location" attribute, e.g., random, top, bottom.

    Example::

        <choicegroup>
        <choice correct="false" name="foil1">
            <text>This is foil One.</text>
        </choice>
        <choice correct="false" name="foil2">
            <text>This is foil Two.</text>
        </choice>
        <choice correct="true" name="foil3">
            <text>This is foil Three.</text>
        </choice>
        </choicegroup>
    """

    template = "choicegroup.html"
    tags = ["choicegroup", "radiogroup", "checkboxgroup"]

    def setup(self):
        i18n = self.capa_system.i18n
        # suffix is '' or [] to change the way the input is handled in --as a scalar or vector
        # value.  (VS: would be nice to make this less hackish).
        if self.tag == "choicegroup":
            self.suffix = ""
            self.html_input_type = "radio"
        elif self.tag == "radiogroup":
            self.html_input_type = "radio"
            self.suffix = "[]"
        elif self.tag == "checkboxgroup":
            self.html_input_type = "checkbox"
            self.suffix = "[]"
        else:
            _ = i18n.gettext
            # Translators: 'ChoiceGroup' is an input type and should not be translated.
            msg = _("ChoiceGroup: unexpected tag {tag_name}").format(tag_name=self.tag)
            raise Exception(msg)  # pylint: disable=broad-exception-raised

        self.choices = self.extract_choices(self.xml, i18n)
        self._choices_map = dict(
            self.choices,
        )

    @classmethod
    def get_attributes(cls):
        # Make '_' a no-op so we can scrape strings. Using function instead of
        #  `django.utils.translation.ugettext_noop` because Django cannot be imported in this file
        def _(text):
            return text

        return [Attribute("show_correctness", "always"), Attribute("submitted_message", _("Answer received."))]

    def _extra_context(self):
        return {"input_type": self.html_input_type, "choices": self.choices, "name_array_suffix": self.suffix}

    @staticmethod
    def extract_choices(element, i18n, text_only=False):
        """
        Extracts choices for a few input types, such as ChoiceGroup, RadioGroup and
        CheckboxGroup.

        returns list of (choice_name, choice_text) tuples

        By default it will return any XML tag in the choice (e.g. <choicehint>) unless text_only=True is passed.

        TODO: allow order of choices to be randomized, following lon-capa spec.  Use
        "location" attribute, ie random, top, bottom.
        """

        choices = []
        _ = i18n.gettext

        for choice in element:
            if choice.tag == "choice":
                if not text_only:
                    text = stringify_children(choice)
                else:
                    text = choice.text
                choices.append((choice.get("name"), text))
            else:
                if choice.tag != "compoundhint":
                    msg = Text("[capa.inputtypes.extract_choices] {error_message}").format(
                        error_message=Text(
                            # Translators: '<choice>' and '<compoundhint>' are tag names and should not be translated.
                            _("Expected a <choice> or <compoundhint> tag; got {given_tag} instead")
                        ).format(given_tag=choice.tag)
                    )
                    raise Exception(msg)  # pylint: disable=broad-exception-raised
        return choices

    def get_user_visible_answer(self, internal_answer):
        if isinstance(internal_answer, str):
            return self._choices_map[internal_answer]

        return [self._choices_map[i] for i in internal_answer]


# -----------------------------------------------------------------------------


@registry.register
class JSInput(InputTypeBase):
    """
    Inputtype for general javascript inputs. Intended to be used with
    customresponse.
    Loads in a sandboxed iframe to help prevent css and js conflicts between
    frame and top-level window.

    iframe sandbox whitelist:
        - allow-scripts
        - allow-popups
        - allow-forms
        - allow-pointer-lock
        - allow-downloads

    This in turn means that the iframe cannot directly access the top-level
    window elements.
    Example:

        <jsinput html_file="/static/test.html"
                 gradefn="grade"
                 height="500"
                 width="400"/>

    See the documentation in docs/data/source/course_data_formats/jsinput.rst
    for more information.
    """

    template = "jsinput.html"
    tags = ["jsinput"]

    @classmethod
    def get_attributes(cls):
        """
        Register the attributes.
        """
        return [
            Attribute("params", None),  # extra iframe params
            Attribute("html_file", None),
            Attribute("gradefn", "gradefn"),
            Attribute("get_statefn", None),  # Function to call in iframe
            #   to get current state.
            Attribute("initial_state", None),  # JSON string to be used as initial state
            Attribute("set_statefn", None),  # Function to call iframe to
            #   set state
            Attribute("width", "400"),  # iframe width
            Attribute("height", "300"),  # iframe height
            # Title for the iframe, which should be supplied by the author of the problem. Not translated
            # because we are in a class method and therefore do not have access to capa_system.i18n.
            # Note that the default "display name" for the problem is also not translated.
            Attribute("title", "Problem Remote Content"),
            # SOP will be relaxed only if this attribute is set to false.
            Attribute("sop", None),
        ]

    def _extra_context(self):
        context = {
            "jschannel_loader": f"{self.capa_system.STATIC_URL}js/capa/src/jschannel.js",
            "jsinput_loader": f"{self.capa_system.STATIC_URL}js/capa/src/jsinput.js",
            "saved_state": self.value,
        }

        return context


# -----------------------------------------------------------------------------


@registry.register
class TextLine(InputTypeBase):
    """
    A text line input.  Can do math preview if "math"="1" is specified.

    If "trailing_text" is set to a value, then the textline will be shown with
    the value after the text input, and before the checkmark or any input-specific
    feedback. HTML will not work, but properly escaped HTML characters will. This
    feature is useful if you would like to specify a specific type of units for the
    text input.

    If the hidden attribute is specified, the textline is hidden and the input id
    is stored in a div with name equal to the value of the hidden attribute.  This
    is used e.g. for embedding simulations turned into questions.

    Example:
        <textline math="1" trailing_text="m/s"/>

    This example will render out a text line with a math preview and the text 'm/s'
    after the end of the text line.
    """

    template = "textline.html"
    tags = ["textline"]

    @classmethod
    def get_attributes(cls):
        """
        Register the attributes.
        """
        return [
            Attribute("size", None),
            Attribute("hidden", False),
            Attribute("inline", False),
            # Attributes below used in setup(), not rendered directly.
            Attribute("math", None, render=False),
            # TODO: 'dojs' flag is temporary, for backwards compatibility with
            # 8.02x
            Attribute("dojs", None, render=False),
            Attribute("preprocessorClassName", None, render=False),
            Attribute("preprocessorSrc", None, render=False),
            Attribute("trailing_text", ""),
        ]

    def setup(self):
        self.do_math = bool(self.loaded_attributes["math"] or self.loaded_attributes["dojs"])

        # TODO: do math checking using ajax instead of using js, so
        # that we only have one math parser.
        self.preprocessor = None
        if self.do_math:
            # Preprocessor to insert between raw input and Mathjax
            self.preprocessor = {
                "class_name": self.loaded_attributes["preprocessorClassName"],
                "script_src": self.loaded_attributes["preprocessorSrc"],
            }
            if None in list(self.preprocessor.values()):
                self.preprocessor = None

    def _extra_context(self):
        return {
            "do_math": self.do_math,
            "preprocessor": self.preprocessor,
        }


# -----------------------------------------------------------------------------


@registry.register
class FileSubmission(InputTypeBase):
    """
    Upload some files (e.g. for programming assignments)
    """

    template = "filesubmission.html"
    tags = ["filesubmission"]

    @staticmethod
    def parse_files(files):
        """
        Given a string like 'a.py b.py c.out', split on whitespace and return as a json list.
        """
        return json.dumps(files.split())

    @classmethod
    def get_attributes(cls):
        """
        Convert the list of allowed files to a convenient format.
        """
        return [
            Attribute("allowed_files", "[]", transform=cls.parse_files),
            Attribute("required_files", "[]", transform=cls.parse_files),
        ]

    def setup(self):
        """
        Do some magic to handle queueing status (render as "queued" instead of "incomplete"),
        pull queue_len from the msg field.  (TODO: get rid of the queue_len hack).
        """
        _ = self.capa_system.i18n.gettext
        submitted_msg = _(
            "Your files have been submitted. As soon as your submission is"
            " graded, this message will be replaced with the grader's feedback."
        )
        self.submitted_msg = submitted_msg

        # Check if problem has been queued
        self.queue_len = 0
        # Flag indicating that the problem has been queued, 'msg' is length of
        # queue
        if self.status == "incomplete":
            self.status = "queued"
            self.queue_len = self.msg
            self.msg = self.submitted_msg

    def _extra_context(self):
        return {
            "queue_len": self.queue_len,
        }


# -----------------------------------------------------------------------------


@registry.register
class CodeInput(InputTypeBase):
    """
    A text area input for code--uses codemirror, does syntax highlighting, special tab handling,
    etc.
    """

    template = "codeinput.html"
    tags = [
        "codeinput",
        "textbox",
        # Another (older) name--at some point we may want to make it use a
        # non-codemirror editor.
    ]

    @classmethod
    def get_attributes(cls):
        """
        Convert options to a convenient format.
        """
        return [
            Attribute("rows", "30"),
            Attribute("cols", "80"),
            Attribute("hidden", ""),
            # For CodeMirror
            Attribute("mode", "python"),
            Attribute("linenumbers", "true"),
            # Template expects tabsize to be an int it can do math with
            Attribute("tabsize", 4, transform=int),
        ]

    def setup_code_response_rendering(self):
        """
        Implement special logic: handle queueing state, and default input.
        """
        # if no student input yet, then use the default input given by the
        # problem
        if not self.value and self.xml.text:
            self.value = self.xml.text.strip()

        # Check if problem has been queued
        self.queue_len = 0  # pylint: disable=attribute-defined-outside-init
        # Flag indicating that the problem has been queued, 'msg' is length of
        # queue
        if self.status == "incomplete":
            self.status = "queued"
            self.queue_len = self.msg  # pylint: disable=attribute-defined-outside-init
            self.msg = nh3.clean(self.submitted_msg)

    def setup(self):
        """setup this input type"""
        _ = self.capa_system.i18n.gettext
        submitted_msg = _(
            "Your answer has been submitted. As soon as your submission is"
            " graded, this message will be replaced with the grader's feedback."
        )
        self.submitted_msg = submitted_msg

        self.setup_code_response_rendering()

    def _extra_context(self):
        """
        Define queue_len, arial_label and code mirror exit message context variables
        """
        _ = self.capa_system.i18n.gettext
        return {
            "queue_len": self.queue_len,
            "aria_label": _("{programming_language} editor").format(
                programming_language=self.loaded_attributes.get("mode")
            ),
            "code_mirror_exit_message": _("Press ESC then TAB or click outside of the code editor to exit"),
        }


# -----------------------------------------------------------------------------


@registry.register
class MatlabInput(CodeInput):
    """
    InputType for handling Matlab code input

    Example::

        <matlabinput rows="10" cols="80" tabsize="4">
            Initial Text
        </matlabinput>

    """

    template = "matlabinput.html"
    tags = ["matlabinput"]

    def setup(self):
        """
        Handle matlab-specific parsing
        """
        _ = self.capa_system.i18n.gettext

        submitted_msg = _(
            "Submitted. As soon as a response is returned, this message will be replaced by that feedback."
        )
        self.submitted_msg = submitted_msg

        self.setup_code_response_rendering()

        xml = self.xml

        self.plot_payload = xml.findtext("./plot_payload")
        # Check if problem has been queued
        self.queuename = "matlab"
        self.queue_msg = ""
        # this is only set if we don't have a graded response
        # the graded response takes precedence
        if "queue_msg" in self.input_state and self.status in ["queued", "incomplete", "unsubmitted"]:
            self.queue_msg = sanitize_html(self.input_state["queue_msg"])

        if "queuestate" in self.input_state and self.input_state["queuestate"] == "queued":
            self.status = "queued"
            self.queue_len = 1
            self.msg = self.submitted_msg
            # Handle situation if no response from xqueue arrived during specified time.
            if "queuetime" not in self.input_state or time.time() - self.input_state["queuetime"] > XQUEUE_TIMEOUT:
                self.queue_len = 0
                self.status = "unsubmitted"
                self.msg = _("No response from Xqueue within {xqueue_timeout} seconds. Aborted.").format(
                    xqueue_timeout=XQUEUE_TIMEOUT
                )

    def handle_ajax(self, dispatch, data):
        """
        Handle AJAX calls directed to this input

        Args:
            - dispatch (str) - indicates how we want this ajax call to be handled
            - data (dict) - dictionary of key-value pairs that contain useful data
        Returns:
            dict - 'success' - whether or not we successfully queued this submission
                 - 'message' - message to be rendered in case of error
        """

        if dispatch == "plot":
            return self._plot_data(data)
        return {}

    def ungraded_response(self, queue_msg, queuekey):
        """
        Handle the response from the XQueue
        Stores the response in the input_state so it can be rendered later

        Args:
            - queue_msg (str) - message returned from the queue. The message to be rendered
            - queuekey (str) - a key passed to the queue. Will be matched up to verify that
                            this is the response we're waiting for

        Returns:
            nothing
        """
        # check the queuekey against the saved queuekey
        if (
            "queuestate" in self.input_state
            and self.input_state["queuestate"] == "queued"
            and self.input_state["queuekey"] == queuekey
        ):
            msg = self._parse_data(queue_msg)
            # save the queue message so that it can be rendered later
            self.input_state["queue_msg"] = msg
            self.input_state["queuestate"] = None
            self.input_state["queuekey"] = None

    def button_enabled(self):
        """Return whether or not we want the 'Test Code' button visible

        Right now, we only want this button to show up when a problem has not been
        checked.
        """
        if self.status in ["correct", "incorrect", "partially-correct"]:
            return False

        return True

    def _extra_context(self):
        """Set up additional context variables"""

        _ = self.capa_system.i18n.gettext

        queue_msg = self.queue_msg
        if len(self.queue_msg) > 0:  # An empty string cannot be parsed as XML but is okay to include in the template.
            try:
                etree.XML(HTML("<div>{0}</div>").format(HTML(self.queue_msg)))
            except etree.XMLSyntaxError:
                try:
                    html5lib.parseFragment(self.queue_msg, treebuilder="lxml", namespaceHTMLElements=False)[0]
                except (IndexError, ValueError):
                    # If neither can parse queue_msg, it contains invalid xml.
                    queue_msg = HTML("<span>{0}</span>").format(_("Error running code."))

        extra_context = {
            "queue_len": str(self.queue_len),
            "queue_msg": queue_msg,
            "button_enabled": self.button_enabled(),
            "matlab_editor_js": f"{self.capa_system.STATIC_URL}js/vendor/CodeMirror/octave.js",
            "msg": sanitize_html(self.msg),  # sanitize msg before rendering into template
        }
        return extra_context

    def _parse_data(self, queue_msg):
        """
        Parses the message out of the queue message
        Args:
            queue_msg (str) - a JSON encoded string
        Returns:
            returns the value for the the key 'msg' in queue_msg
        """
        try:
            result = json.loads(queue_msg)
        except (TypeError, ValueError):
            log.error("External message should be a JSON serialized dict. Received queue_msg = %s", queue_msg)
            raise
        msg = result["msg"]
        return msg

    def _plot_data(self, data):
        """
        AJAX handler for the plot button
        Args:
            get (dict) - should have key 'submission' which contains the student submission
        Returns:
            dict - 'success' - whether or not we successfully queued this submission
                 - 'message' - message to be rendered in case of error
        """
        _ = self.capa_system.i18n.gettext
        # only send data if xqueue exists
        if self.capa_system.xqueue is None:
            return {"success": False, "message": _("Cannot connect to the queue")}

        # pull relevant info out of get
        response = data["submission"]

        # construct xqueue headers
        qinterface = self.capa_system.xqueue.interface
        qtime = datetime.utcnow().strftime(xqueue_interface.DATEFORMAT)
        callback_url = self.capa_system.xqueue.construct_callback("ungraded_response")
        anonymous_student_id = self.capa_system.anonymous_student_id
        # TODO: Why is this using self.capa_system.seed when we have self.seed???
        queuekey = xqueue_interface.make_hashkey(
            str(self.capa_system.seed) + qtime + anonymous_student_id + self.input_id
        )
        xheader = xqueue_interface.make_xheader(
            lms_callback_url=callback_url, lms_key=queuekey, queue_name=self.queuename
        )

        # construct xqueue body
        student_info = {"anonymous_student_id": anonymous_student_id, "submission_time": qtime}
        contents = {
            "grader_payload": self.plot_payload,
            "student_info": json.dumps(student_info),
            "student_response": response,
            "token": getattr(self.capa_system, "matlab_api_key", None),
            "endpoint_version": "2",
            "requestor_id": anonymous_student_id,
        }

        error, msg = qinterface.send_to_queue(header=xheader, body=json.dumps(contents))
        # save the input state if successful
        if error == 0:
            self.input_state["queuekey"] = queuekey
            self.input_state["queuestate"] = "queued"
            self.input_state["queuetime"] = time.time()

        return {"success": error == 0, "message": msg}


# -----------------------------------------------------------------------------


@registry.register
class Schematic(InputTypeBase):
    """
    InputType for the schematic editor
    """

    template = "schematicinput.html"
    tags = ["schematic"]

    @classmethod
    def get_attributes(cls):
        """
        Convert options to a convenient format.
        """
        return [
            Attribute("height", None),
            Attribute("width", None),
            Attribute("parts", None),
            Attribute("analyses", None),
            Attribute("initial_value", None),
            Attribute("submit_analyses", None),
        ]

    def _extra_context(self):
        context = {
            "setup_script": f"{self.capa_system.STATIC_URL}js/capa/schematicinput.js",
        }

        return context


# -----------------------------------------------------------------------------


@registry.register
class ImageInput(InputTypeBase):
    """
    Clickable image as an input field.  Element should specify the image source, height,
    and width, e.g.

    <imageinput src="/static/Figures/Skier-conservation-of-energy.jpg" width="388" height="560" />

    TODO: showanswer for imageimput does not work yet - need javascript to put rectangle
    over acceptable area of image.
    """

    template = "imageinput.html"
    tags = ["imageinput"]

    @classmethod
    def get_attributes(cls):
        """
        Note: src, height, and width are all required.
        """
        return [
            Attribute("src"),
            Attribute("height"),
            Attribute("width"),
        ]

    def setup(self):
        """
        if value is of the form [x,y] then parse it and send along coordinates of previous answer
        """
        m = re.match(r"\[([0-9]+),([0-9]+)]", self.value.strip().replace(" ", ""))
        if m:
            # Note: we subtract 15 to compensate for the size of the dot on the screen.
            # (is a 30x30 image--lms/static/images/green-pointer.png).
            self.gx, self.gy = [int(x) - 15 for x in m.groups()]
        else:
            self.gx, self.gy = (0, 0)

    def _extra_context(self):

        return {"gx": self.gx, "gy": self.gy}


# -----------------------------------------------------------------------------


@registry.register
class Crystallography(InputTypeBase):
    """
    An input for crystallography -- user selects 3 points on the axes, and we get a plane.

    TODO: what's the actual value format?
    """

    template = "crystallography.html"
    tags = ["crystallography"]

    @classmethod
    def get_attributes(cls):
        """
        Note: height, width are required.
        """
        return [
            Attribute("height"),
            Attribute("width"),
        ]


# -------------------------------------------------------------------------


@registry.register
class VseprInput(InputTypeBase):
    """
    Input for molecular geometry--show possible structures, let student
    pick structure and label positions with atoms or electron pairs.
    """

    template = "vsepr_input.html"
    tags = ["vsepr_input"]

    @classmethod
    def get_attributes(cls):
        """
        Note: height, width, molecules and geometries are required.
        """
        return [
            Attribute("height"),
            Attribute("width"),
            Attribute("molecules"),
            Attribute("geometries"),
        ]


# -------------------------------------------------------------------------


@registry.register
class ChemicalEquationInput(InputTypeBase):
    """
    An input type for entering chemical equations.  Supports live preview.

    Example:

    <chemicalequationinput size="50"/>

    options: size -- width of the textbox.
    """

    template = "chemicalequationinput.html"
    tags = ["chemicalequationinput"]

    @classmethod
    def get_attributes(cls):
        """
        Can set size of text field.
        """
        return [
            Attribute("size", "20"),
        ]

    def _extra_context(self):
        """
        TODO (vshnayder): Get rid of this once we have a standard way of requiring js to be loaded.
        """
        return {
            "previewer": f"{self.capa_system.STATIC_URL}js/capa/chemical_equation_preview.js",
        }

    def handle_ajax(self, dispatch, data):
        """
        Since we only have chemcalc preview this input, check to see if it
        matches the corresponding dispatch and send it through if it does
        """
        if dispatch == "preview_chemcalc":
            return self.preview_chemcalc(data)
        return {}

    def preview_chemcalc(self, data):
        """
        Render an html preview of a chemical formula or equation.  get should
        contain a key 'formula' and value 'some formula string'.

        Returns a json dictionary::

            {
                'preview' : 'the-preview-html' or ''
                'error' : 'the-error' or ''
            }

        """

        _ = self.capa_system.i18n.gettext
        result = {"preview": "", "error": ""}
        try:
            formula = data["formula"]
        except KeyError:
            result["error"] = _("No formula specified.")
            return result

        try:
            result["preview"] = chemcalc.render_to_html(formula)
        except pyparsing.ParseException as err:
            result["error"] = _("Couldn't parse formula: {error_msg}").format(error_msg=err.msg)
        except Exception:  # pylint: disable=broad-exception-caught
            # this is unexpected, so log
            log.warning("Error while previewing chemical formula", exc_info=True)
            result["error"] = _("Error while rendering preview")

        return result


# -------------------------------------------------------------------------


@registry.register
class FormulaEquationInput(InputTypeBase):
    """
    An input type for entering formula equations.  Supports live preview.

    Example:

    <formulaequationinput size="50"/>

    options: size -- width of the textbox.
             trailing_text -- text to show after the input textbox when
                              rendered, same as textline (useful for units)
    """

    template = "formulaequationinput.html"
    tags = ["formulaequationinput"]

    @classmethod
    def get_attributes(cls):
        """
        Can set size of text field.
        """
        return [
            Attribute("size", "20"),
            Attribute("inline", False),
            Attribute("trailing_text", ""),
        ]

    def _extra_context(self):
        """
        TODO (vshnayder): Get rid of 'previewer' once we have a standard way of requiring js to be loaded.
        """
        # `reported_status` is basically `status`, except we say 'unanswered'

        return {
            "previewer": f"{self.capa_system.STATIC_URL}js/capa/src/formula_equation_preview.js",
        }

    def handle_ajax(self, dispatch, data):
        """
        Since we only have formcalc preview this input, check to see if it
        matches the corresponding dispatch and send it through if it does
        """
        if dispatch == "preview_formcalc":
            return self.preview_formcalc(data)
        return {}

    def preview_formcalc(self, get):
        """
        Render a preview of a formula or equation. `get` should
        contain a key 'formula' with a math expression.

        Returns a JSON dictionary::

            {
               'preview' : '<some latex>' or '',
               'error' : 'the-error' or '',
               'request_start' : <time sent with request>
            }
        """
        _ = self.capa_system.i18n.gettext
        result = {"preview": "", "error": ""}

        try:
            formula = get["formula"]
        except KeyError:
            result["error"] = _("No formula specified.")
            return result

        result["request_start"] = int(get.get("request_start", 0))

        # TODO add references to valid variables and functions
        # At some point, we might want to mark invalid variables as red
        # or something, and this is where we would need to pass those in.
        try:
            numeric_result = preview_numeric_input(formula)
            # Map results into the correct format
            result["preview"] = numeric_result["preview"]
            if numeric_result["error"]:
                result["error"] = numeric_result["error"]
            # if formula is invalid return formula
            if not numeric_result["is_valid"]:
                result["formula"] = formula
        except pyparsing.ParseException:
            result["error"] = _("Sorry, couldn't parse formula")
            result["formula"] = formula
        except Exception:  # pylint: disable=broad-exception-caught
            log.warning("Error while previewing formula", exc_info=True)
            result["error"] = _("Error while rendering preview")
            return result

        return result


def preview_numeric_input(formula):
    """
    Handles numeric validations, validates that the formula provided is a valid formula.
    """
    result = {"preview": "", "is_valid": True, "error": ""}
    try:
        result["preview"] = latex_preview(formula)
    except pyparsing.ParseException:
        result["error"] = "Sorry, couldn't parse formula"
        result["is_valid"] = False
        return result
    except Exception:  # pylint: disable=broad-exception-caught
        log.warning("Error while previewing formula", exc_info=True)
        result["error"] = "Error while rendering preview"
        result["is_valid"] = False
    return result


# -----------------------------------------------------------------------------


@registry.register
class DragAndDropInput(InputTypeBase):
    """
    Input for drag and drop problems. Allows student to drag and drop images and
    labels to base image.
    """

    template = "drag_and_drop_input.html"
    tags = ["drag_and_drop_input"]

    def setup(self):

        def parse(tag, tag_type):
            """Parses <tag ... /> xml element to dictionary. Stores
            'draggable' and 'target' tags with attributes to dictionary and
            returns last.

            Args:
                tag: xml etree element <tag...> with attributes

                tag_type: 'draggable' or 'target'.

                If tag_type is 'draggable' : all attributes except id
                (name or label or icon or can_reuse) are optional

                If tag_type is 'target' all attributes (name, x, y, w, h)
                are required. (x, y) - coordinates of center of target,
                w, h - weight and height of target.

            Returns:
                Dictionary of vaues of attributes:
                dict{'name': smth, 'label': smth, 'icon': smth,
                'can_reuse': smth}.
            """
            tag_attrs = {}
            tag_attrs["draggable"] = {
                "id": Attribute._sentinel,  # pylint: disable=protected-access
                "label": "",
                "icon": "",
                "can_reuse": "",
            }

            tag_attrs["target"] = {
                "id": Attribute._sentinel,  # pylint: disable=protected-access
                "x": Attribute._sentinel,  # pylint: disable=protected-access
                "y": Attribute._sentinel,  # pylint: disable=protected-access
                "w": Attribute._sentinel,  # pylint: disable=protected-access
                "h": Attribute._sentinel,  # pylint: disable=protected-access
            }

            dic = {}

            for attr_name in tag_attrs[tag_type].keys():
                dic[attr_name] = Attribute(attr_name, default=tag_attrs[tag_type][attr_name]).parse_from_xml(tag)

            if tag_type == "draggable" and not self.no_labels:
                dic["label"] = dic["label"] or dic["id"]

            if tag_type == "draggable":
                dic["target_fields"] = [parse(target, "target") for target in tag.iterchildren("target")]

            return dic

        # add labels to images?:
        self.no_labels = Attribute("no_labels", default="False").parse_from_xml(self.xml)

        to_js = {}

        # image drag and drop onto
        to_js["base_image"] = Attribute("img").parse_from_xml(self.xml)

        # outline places on image where to drag adn drop
        to_js["target_outline"] = Attribute("target_outline", default="False").parse_from_xml(self.xml)
        # one draggable per target?
        to_js["one_per_target"] = Attribute("one_per_target", default="True").parse_from_xml(self.xml)
        # list of draggables
        to_js["draggables"] = [parse(draggable, "draggable") for draggable in self.xml.iterchildren("draggable")]
        # list of targets
        to_js["targets"] = [parse(target, "target") for target in self.xml.iterchildren("target")]

        # custom background color for labels:
        label_bg_color = Attribute("label_bg_color", default=None).parse_from_xml(self.xml)
        if label_bg_color:
            to_js["label_bg_color"] = label_bg_color

        self.loaded_attributes["drag_and_drop_json"] = json.dumps(to_js)
        self.to_render.add("drag_and_drop_json")


# -------------------------------------------------------------------------


@registry.register
class DesignProtein2dInput(InputTypeBase):
    """
    An input type for design of a protein in 2D. Integrates with the Protex java applet.

    Example:

    <designprotein2d width="800" hight="500" target_shape="E;NE;NW;W;SW;E;none" />
    """

    template = "designprotein2dinput.html"
    tags = ["designprotein2dinput"]

    @classmethod
    def get_attributes(cls):
        """
        Note: width, hight, and target_shape are required.
        """
        return [Attribute("width"), Attribute("height"), Attribute("target_shape")]

    def _extra_context(self):
        context = {
            "applet_loader": f"{self.capa_system.STATIC_URL}js/capa/design-protein-2d.js",
        }

        return context


# -----------------------------------------------------------------------------


@registry.register
class EditAGeneInput(InputTypeBase):
    """
    An input type for editing a gene.
    Integrates with the genex GWT application.

    Example:

    <editagene genex_dna_sequence="CGAT" genex_problem_number="1"/>
    """

    template = "editageneinput.html"
    tags = ["editageneinput"]

    @classmethod
    def get_attributes(cls):
        """
        Note: width, height, and dna_sequencee are required.
        """
        return [Attribute("genex_dna_sequence"), Attribute("genex_problem_number")]

    def _extra_context(self):
        context = {
            "applet_loader": f"{self.capa_system.STATIC_URL}js/capa/edit-a-gene.js",
        }

        return context


# ---------------------------------------------------------------------


@registry.register
class AnnotationInput(InputTypeBase):
    """Input type for annotations.

    Students can enter some notes or other text (currently ungraded), and
    then choose from a set of tags/options, which are graded.

    Example::

        <annotationinput>
            <title>Annotation Exercise</title>
            <text>
                They are the ones who, at the public assembly, had put savage derangement [ate] into my thinking
                [phrenes] |89 on that day when I myself deprived Achilles of his honorific portion [geras]
            </text>
            <comment>Agamemnon says that ate or 'derangement' was the cause of his actions:
            why could Zeus say the same thing?</comment>
            <comment_prompt>Type a commentary below:</comment_prompt>
            <tag_prompt>Select one tag:</tag_prompt>
            <options>
                <option choice="correct">ate - both a cause and an effect</option>
                <option choice="incorrect">ate - a cause</option>
                <option choice="partially-correct">ate - an effect</option>
            </options>
        </annotationinput>

    # TODO: allow ordering to be randomized
    """

    template = "annotationinput.html"
    tags = ["annotationinput"]

    def setup(self):
        xml = self.xml

        self.debug = False  # set to True to display extra debug info with input
        self.return_to_annotation = True  # return only works in conjunction with annotatable xmodule

        self.title = xml.findtext("./title", "Annotation Exercise")
        self.text = xml.findtext("./text")
        self.comment = xml.findtext("./comment")
        self.comment_prompt = xml.findtext("./comment_prompt", "Type a commentary below:")
        self.tag_prompt = xml.findtext("./tag_prompt", "Select one tag:")
        self.options = self._find_options()

        # Need to provide a value that JSON can parse if there is no
        # student-supplied value yet.
        if self.value == "":
            self.value = "null"

        self._validate_options()

    def _find_options(self):
        """Returns an array of dicts where each dict represents an option."""
        elements = self.xml.findall("./options/option")
        return [
            {"id": index, "description": option.text, "choice": option.get("choice")}
            for (index, option) in enumerate(elements)
        ]

    def _validate_options(self):
        """Raises a ValueError if the choice attribute is missing or invalid."""
        valid_choices = ("correct", "partially-correct", "incorrect")
        for option in self.options:
            choice = option["choice"]
            if choice is None:
                raise ValueError("Missing required choice attribute.")
            if choice not in valid_choices:
                raise ValueError(f"Invalid choice attribute: {choice}. Must be one of: {', '.join(valid_choices)}")

    def _unpack(self, json_value):
        """Unpacks the json input state into a dict."""
        d = json.loads(json_value)
        if not isinstance(d, dict):
            d = {}

        comment_value = d.get("comment", "")
        if not isinstance(comment_value, str):
            comment_value = ""

        options_value = d.get("options", [])
        if not isinstance(options_value, list):
            options_value = []

        return {
            "options_value": options_value,
            "has_options_value": len(options_value) > 0,  # for convenience
            "comment_value": comment_value,
        }

    def _extra_context(self):
        extra_context = {
            "title": self.title,
            "text": self.text,
            "comment": self.comment,
            "comment_prompt": self.comment_prompt,
            "tag_prompt": self.tag_prompt,
            "options": self.options,
            "return_to_annotation": self.return_to_annotation,
            "debug": self.debug,
        }

        extra_context.update(self._unpack(self.value))

        return extra_context


@registry.register
class ChoiceTextGroup(InputTypeBase):
    r"""Groups of radiobutton/checkboxes with text inputs.

    Examples:
    **RadioButton Problem**::

        <problem>
            <startouttext/>
            A person rolls a standard die 100 times and records the results.
            On the first roll they received a "1". Given this information
            select the correct choice and fill in numbers to make it accurate.
            <endouttext/>
            <choicetextresponse>
                <radiotextgroup>
                    <choice correct="false">The lowest number rolled was:
                        <decoy_input/> and the highest number rolled was:
                        <decoy_input/> .
                    </choice>
                    <choice correct="true">The lowest number rolled was <numtolerance_input answer="1"/>
                        and there is not enough information to determine the highest number rolled.
                    </choice>
                    <choice correct="false">There is not enough information to determine the lowest
                        number rolled, and the highest number rolled was:
                        <decoy_input/> .
                    </choice>
                </radiotextgroup>
            </choicetextresponse>
        </problem>

    **Checkbox Problem**::

        <problem>
            <startouttext/>
            A person randomly selects 100 times, with replacement, from the list of numbers \(\sqrt{2}\), 2, 3, 4, 5, 6
            and records the results. The first number they pick is \(\sqrt{2}\). Given this information
            select the correct choices and fill in numbers to make them accurate.
            <endouttext/>
            <choicetextresponse>
                <checkboxtextgroup>
                    <choice correct="true">
                        The lowest number selected was <numtolerance_input answer="1.4142" tolerance="0.01"/>
                    </choice>
                    <choice correct="false">
                        The highest number selected was <decoy_input/> .
                    </choice>
                    <choice correct="true">There is not enough information given to determine the highest number
                        which was selected.
                    </choice>
                    <choice correct="false">There is not enough information given to determine the lowest number
                        selected.
                    </choice>
                </checkboxtextgroup>
            </choicetextresponse>
        </problem>

    .. note::
        In the preceding examples the <decoy_input/> is used to generate a textinput html element
        in the problem's display. Since it is inside of an incorrect choice, no answer given
        for it will be correct, and thus specifying an answer for it is not needed.
    """

    template = "choicetext.html"
    tags = ["radiotextgroup", "checkboxtextgroup"]

    def setup(self):
        """
        Performs setup for the initial rendering of the problem.
        `self.html_input_type` determines whether this problem is displayed
        with radiobuttons or checkboxes

        If the initial value of `self.value` is '' change it to {} so that
        the template has an empty dictionary to work with.

        sets the value of self.choices to be equal to the return value of
        `self.extract_choices`
        """
        self.text_input_values = {}
        if self.tag == "radiotextgroup":
            self.html_input_type = "radio"
        elif self.tag == "checkboxtextgroup":
            self.html_input_type = "checkbox"
        else:
            _ = self.capa_system.i18n.gettext
            msg = _("{input_type}: unexpected tag {tag_name}").format(input_type="ChoiceTextGroup", tag_name=self.tag)
            raise Exception(msg)  # pylint: disable=broad-exception-raised

        if self.value == "":
            # Make `value` an empty dictionary, if it currently has an empty
            # value. This is necessary because the template expects a
            # dictionary.
            self.value = {}
        self.choices = self.extract_choices(self.xml, self.capa_system.i18n)

    @classmethod
    def get_attributes(cls):
        """
        Returns a list of `Attribute` for this problem type
        """

        # Make '_' a no-op so we can scrape strings. Using function instead of
        #  `django.utils.translation.ugettext_noop` because Django cannot be imported in this file
        def _(text):
            return text

        return [
            Attribute("show_correctness", "always"),
            Attribute("submitted_message", _("Answer received.")),
        ]

    def _extra_context(self):
        """
        Returns a dictionary of extra content necessary for rendering this InputType.

        `input_type` is either 'radio' or 'checkbox' indicating whether the choices for
        this problem will have radiobuttons or checkboxes.
        """
        return {"input_type": self.html_input_type, "choices": self.choices}

    @staticmethod
    def extract_choices(element, i18n):
        """Extracts choices from the xml for this problem type.

        If we have xml that is as follows(choice names will have been assigned
        by now)::

            <radiotextgroup>
                <choice correct="true" name="1_2_1_choiceinput_0bc">
                    The number
                    <numtolerance_input name="1_2_1_choiceinput0_numtolerance_input_0" answer="5"/>
                    Is the mean of the list.
                </choice>
                <choice correct="false" name="1_2_1_choiceinput_1bc">
                    False demonstration choice
                </choice>
            </radiotextgroup>

        Choices are used for rendering the problem properly. The function
        will set up choices as follows::

            choices = [
                ("1_2_1_choiceinput_0bc",
                    [{'type': 'text', 'contents': "The number", 'tail_text': '',
                      'value': ''
                     },
                     {'type': 'textinput',
                      'contents': "1_2_1_choiceinput0_numtolerance_input_0",
                      'tail_text': 'Is the mean of the list',
                      'value': ''
                     }
                    ]
                ),
                ("1_2_1_choiceinput_1bc",
                    [{'type': 'text', 'contents': "False demonstration choice",
                      'tail_text': '',
                      'value': ''
                     }
                    ]
                )
            ]
        """

        _ = i18n.gettext
        choices = []

        for choice in element:
            if choice.tag != "choice":
                msg = Text("[capa.inputtypes.extract_choices] {0}").format(
                    # Translators: a "tag" is an XML element, such as "<b>" in HTML
                    Text(_("Expected a {expected_tag} tag; got {given_tag} instead")).format(
                        # xss-lint: disable=python-wrap-html
                        expected_tag="<choice>",
                        given_tag=choice.tag,
                    )
                )
                raise Exception(msg)  # pylint: disable=broad-exception-raised

            components = []
            choice_text = ""
            if choice.text is not None:
                choice_text += choice.text
            # Initialize our dict for the next content
            adder = {"type": "text", "contents": choice_text, "tail_text": "", "value": ""}
            components.append(adder)

            for elt in choice:
                # for elements in the choice e.g. <text> <numtolerance_input>
                adder = {"type": "text", "contents": "", "tail_text": "", "value": ""}
                tag_type = elt.tag
                # If the current `elt` is a <numtolerance_input> set the
                # `adder`type to 'numtolerance_input', and 'contents' to
                # the `elt`'s name.
                # Treat decoy_inputs and numtolerance_inputs the same in order
                # to prevent students from reading the Html and figuring out
                # which inputs are valid
                if tag_type in ("numtolerance_input", "decoy_input"):
                    # We set this to textinput, so that we get a textinput html
                    # element.
                    adder["type"] = "textinput"
                    adder["contents"] = elt.get("name")
                else:
                    adder["contents"] = elt.text

                # Add any tail text("is the mean" in the example)
                adder["tail_text"] = elt.tail if elt.tail else ""
                components.append(adder)

            # Add the tuple for the current choice to the list of choices
            choices.append((choice.get("name"), components))
        return choices
