import os
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.frontend import Values
import sphinx
from sphinx.locale import _
try:
    from sphinx.errors import NoUri
except ImportError:
    from sphinx.environment import NoUri
from docutils.parsers.rst import Directive
from docutils.parsers.rst.directives.admonitions import BaseAdmonition
from docutils.statemachine import StringList
from sphinx.util.nodes import set_source_info, process_index_entry
from sphinx import addnodes
from ..language import TITLES
[docs]
class mathdef_node(nodes.admonition):
    """
    Defines ``mathdef`` node.
    """
    pass 
class mathdeflist(nodes.General, nodes.Element):
    """
    Defines ``mathdeflist`` node.
    """
    pass
[docs]
class MathDef(BaseAdmonition):
    """
    A ``mathdef`` entry, displayed in the form of an admonition.
    It takes the following options:
    * *title*: a title for the math
    * *tag*: a tag to have several categories of math
    * *lid* or *label*: a label to refer to
    * *index*: to add an entry to the index (comma separated)
    Example::
        .. mathdef::
            :title: title
            :tag: definition or theorem or ...
            :lid: id (used for further reference)
            Description of the math
    """
    node_class = mathdef_node
    has_content = True
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {
        "class": directives.class_option,
        "title": directives.unchanged,
        "tag": directives.unchanged,
        "lid": directives.unchanged,
        "label": directives.unchanged,
        "index": directives.unchanged,
    }
[docs]
    def run(self):
        """
        Builds the mathdef text.
        """
        # sett = self.state.document.settings
        # language_code = sett.language_code
        lineno = self.lineno
        env = (
            self.state.document.settings.env
            if hasattr(self.state.document.settings, "env")
            else None
        )
        docname = None if env is None else env.docname
        if docname is not None:
            docname = docname.replace("\\", "/").split("/")[-1]
            legend = f"{docname}:{lineno}"
        else:
            legend = ""
        if hasattr(env, "settings") and hasattr(env.settings, "mathdef_link_number"):
            number_format = env.settings.mathdef_link_number
        elif hasattr(self.state.document.settings, "mathdef_link_number"):
            number_format = self.state.document.settings.mathdef_link_number
        elif hasattr(env, "config") and hasattr(env.config, "mathdef_link_number"):
            number_format = env.config.mathdef_link_number
        else:
            raise ValueError("mathdef_link_number is not defined in the configuration")
        if not self.options.get("class"):
            self.options["class"] = ["admonition-mathdef"]
        # body
        (mathdef,) = super(MathDef, self).run()  # noqa: UP008
        if isinstance(mathdef, nodes.system_message):
            return [mathdef]
        # add a label
        lid = self.options.get("lid", self.options.get("label", None))
        if lid:
            container = nodes.container()
            tnl = [f".. _{lid}:", ""]
            content = StringList(tnl)
            self.state.nested_parse(content, self.content_offset, container)
        else:
            container = None
        # mid
        mathtag = self.options.get("tag", "").strip()
        if len(mathtag) == 0:
            raise ValueError("tag is empty")
        if env is not None:
            mid = int(env.new_serialno(f"indexmathe-u-{mathtag}")) + 1
        else:
            mid = -1
        # id of the section
        first_letter = mathtag[0].upper()
        number = mid
        try:
            label_number = number_format.format(
                number=number, first_letter=first_letter
            )
        except ValueError as e:
            raise RuntimeError(f"Unable to interpret format '{number_format}'.") from e
        # title
        title = self.options.get("title", "").strip()
        if len(title) > 0:
            title = f"{mathtag} {label_number} : {title}"
        else:
            raise ValueError("title is empty")
        # main node
        ttitle = title
        title = nodes.title(text=_(title))
        if container is not None:
            mathdef.insert(0, title)
            mathdef.insert(0, container)
        else:
            mathdef.insert(0, title)
        mathdef["mathtag"] = mathtag
        mathdef["mathmid"] = mid
        mathdef["mathtitle"] = ttitle
        set_source_info(self, mathdef)
        if env is not None:
            targetid = "indexmathe-%s%s" % (
                mathtag,
                env.new_serialno("indexmathe%s" % mathtag),
            )
            targetnode = nodes.target(legend, "", ids=[targetid])
            set_source_info(self, targetnode)
            try:
                self.state.add_target(targetid, "", targetnode, lineno)
            except Exception as e:
                raise RuntimeError(
                    "Issue in\n  File '{0}', line {1}\ntid={2}\ntnode={3}".format(  # noqa: UP030
                        None if env is None else env.docname,
                        lineno,
                        targetid,
                        targetnode,
                    )
                ) from e
            # index node
            index = self.options.get("index", None)
            imposed = ",".join(a for a in [mathtag, ttitle] if a)
            if index is None or len(index.strip()) == 0:
                index = imposed
            else:
                index += "," + imposed
            if index is not None:
                indexnode = addnodes.index()
                indexnode["entries"] = ne = []
                indexnode["inline"] = False
                set_source_info(self, indexnode)
                for entry in index.split(","):
                    ne.extend(process_index_entry(entry, targetid))
            else:
                indexnode = None
        else:
            targetnode = None
            indexnode = None
        return [a for a in [indexnode, targetnode, mathdef] if a is not None] 
 
def process_mathdefs(app, doctree):
    """
    Collects all mathdefs in the environment
    this is not done in the directive itself because it some transformations
    must have already been run, e.g. substitutions.
    """
    env = app.builder.env
    if not hasattr(env, "mathdef_all_mathsext"):
        env.mathdef_all_mathsext = []
    for node in doctree.traverse(mathdef_node):
        try:
            targetnode = node.parent[node.parent.index(node) - 1]
            if not isinstance(targetnode, nodes.target):
                raise IndexError
        except IndexError:
            targetnode = None
        newnode = node.deepcopy()
        mathtag = newnode["mathtag"]
        mathtitle = newnode["mathtitle"]
        mathmid = newnode["mathmid"]
        del newnode["ids"]
        del newnode["mathtag"]
        env.mathdef_all_mathsext.append(
            {
                "docname": env.docname,
                "source": node.source or env.doc2path(env.docname),
                "lineno": node.line,
                "mathdef": newnode,
                "target": targetnode,
                "mathtag": mathtag,
                "mathtitle": mathtitle,
                "mathmid": mathmid,
            }
        )
class MathDefList(Directive):
    """
    A list of all mathdef entries, for a specific tag.
    * tag: a tag to have several categories of mathdef
    * contents: add a bullet list with links to added blocks
    Example::
        .. mathdeflist::
            :tag: issue
            :contents:
    """
    has_content = False
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {
        "tag": directives.unchanged,
        "contents": directives.unchanged,
    }
    def run(self):
        """
        Simply insert an empty mathdeflist node which will be replaced later
        when process_mathdef_nodes is called
        """
        env = (
            self.state.document.settings.env
            if hasattr(self.state.document.settings, "env")
            else None
        )
        tag = self.options.get("tag", "").strip()
        contents = self.options.get("contents", False) in (
            True,
            "True",
            "true",
            1,
            "1",
            "",
            None,
            "None",
        )
        if env is not None:
            targetid = f"indexmathelist-{env.new_serialno('indexmathelist')}"
            targetnode = nodes.target("", "", ids=[targetid])
            n = mathdeflist("")
            n["mathtag"] = tag
            n["mathcontents"] = contents
            n["docname"] = env.docname if env else "none"
            return [targetnode, n]
        n = mathdeflist("")
        n["mathtag"] = tag
        n["mathcontents"] = contents
        n["docname"] = env.docname if env else "none"
        return [n]
def process_mathdef_nodes(app, doctree, fromdocname):
    """
    process_mathdef_nodes
    """
    if not app.config["mathdef_include_mathsext"]:
        for node in doctree.traverse(mathdef_node):
            node.parent.remove(node)
    # Replace all mathdeflist nodes with a list of the collected mathsext.
    # Augment each mathdef with a backlink to the original location.
    env = app.builder.env
    if hasattr(env, "settings") and hasattr(env.settings, "language_code"):
        lang = env.settings.language_code
    else:
        lang = "en"
    orig_entry = TITLES[lang]["original entry"]
    mathmes = TITLES[lang]["mathmes"]
    if not hasattr(env, "mathdef_all_mathsext"):
        env.mathdef_all_mathsext = []
    for ilist, node in enumerate(doctree.traverse(mathdeflist)):
        if "ids" in node:
            node["ids"] = []
        if not app.config["mathdef_include_mathsext"]:
            node.replace_self([])
            continue
        nbmath = 0
        content = []
        mathtag = node["mathtag"]
        add_contents = node["mathcontents"]
        mathdocname = node["docname"]
        if add_contents:
            bullets = nodes.enumerated_list()
            content.append(bullets)
        double_list = [
            (info.get("mathtitle", ""), info) for info in env.mathdef_all_mathsext
        ]
        double_list.sort(key=lambda x: x[:1])
        for n, mathdef_info_ in enumerate(double_list):
            mathdef_info = mathdef_info_[1]
            if mathdef_info["mathtag"] != mathtag:
                continue
            nbmath += 1
            para = nodes.paragraph(classes=["mathdef-source"])
            if app.config["mathdef_link_only"]:
                description = _(f"<<{orig_entry}>>")
            else:
                description = _(mathmes) % (
                    orig_entry,
                    os.path.split(mathdef_info["source"])[-1],
                    mathdef_info["lineno"],
                )
            desc1 = description[: description.find("<<")]
            desc2 = description[description.find(">>") + 2 :]
            para += nodes.Text(desc1, desc1)
            # Create a reference
            newnode = nodes.reference("", "", internal=True)
            innernode = nodes.emphasis("", _(orig_entry))
            try:
                newnode["refuri"] = app.builder.get_relative_uri(
                    fromdocname, mathdef_info["docname"]
                )
                try:
                    newnode["refuri"] += "#" + mathdef_info["target"]["refid"]
                except Exception as e:
                    raise KeyError(
                        f"refid in not present in {mathdef_info['target']!r}"
                    ) from e
            except NoUri:
                # ignore if no URI can be determined, e.g. for LaTeX output
                pass
            newnode.append(innernode)
            para += newnode
            para += nodes.Text(desc2, desc2)
            # (Recursively) resolve references in the mathdef content
            mathdef_entry = mathdef_info["mathdef"]
            idss = ["index-mathdef-%d-%d" % (ilist, n)]
            # Insert into the mathreflist
            if add_contents:
                title = mathdef_info["mathtitle"]
                item = nodes.list_item()
                p = nodes.paragraph()
                item += p
                newnode = nodes.reference("", "", internal=True)
                innernode = nodes.paragraph(text=title)
                try:
                    newnode["refuri"] = app.builder.get_relative_uri(
                        fromdocname, mathdocname
                    )
                    newnode["refuri"] += "#" + idss[0]
                except NoUri:
                    # ignore if no URI can be determined, e.g. for LaTeX output
                    pass
                newnode.append(innernode)
                p += newnode
                bullets += item
            mathdef_entry["ids"] = idss
            if not hasattr(mathdef_entry, "settings"):
                mathdef_entry.settings = Values()
                mathdef_entry.settings.env = env
            # If an exception happens here, see blog 2017-05-21 from the
            # documentation.
            env.resolve_references(mathdef_entry, mathdef_info["docname"], app.builder)
            # Insert into the mathdeflist
            content.append(mathdef_entry)
            content.append(para)
        node.replace_self(content)
def purge_mathsext(app, env, docname):
    """
    purge_mathsext
    """
    if not hasattr(env, "mathdef_all_mathsext"):
        return
    env.mathdef_all_mathsext = [
        mathdef for mathdef in env.mathdef_all_mathsext if mathdef["docname"] != docname
    ]
def merge_mathdef(app, env, docnames, other):
    """
    merge_mathdef
    """
    if not hasattr(other, "mathdef_all_mathsext"):
        return
    if not hasattr(env, "mathdef_all_mathsext"):
        env.mathdef_all_mathsext = []
    env.mathdef_all_mathsext.extend(other.mathdef_all_mathsext)
def visit_mathdef_node(self, node):
    """
    visit_mathdef_node
    """
    self.visit_admonition(node)
def depart_mathdef_node(self, node):
    """
    depart_mathdef_node,
    see https://github.com/sphinx-doc/sphinx/blob/master/sphinx/writers/html.py
    """
    self.depart_admonition(node)
def visit_mathdeflist_node(self, node):
    """
    visit_mathdeflist_node
    see https://github.com/sphinx-doc/sphinx/blob/master/sphinx/writers/html.py
    """
    self.visit_admonition(node)
def depart_mathdeflist_node(self, node):
    """
    depart_mathdef_node
    """
    self.depart_admonition(node)
def setup(app):
    """
    setup for ``mathdef`` (sphinx)
    """
    if hasattr(app, "add_mapping"):
        app.add_mapping("mathdef", mathdef_node)
        app.add_mapping("mathdeflist", mathdeflist)
    app.add_config_value("mathdef_include_mathsext", True, "html")
    app.add_config_value("mathdef_link_only", True, "html")
    app.add_config_value("mathdef_link_number", "{first_letter}{number}", "html")
    app.add_node(
        mathdeflist,
        html=(visit_mathdeflist_node, depart_mathdeflist_node),
        epub=(visit_mathdeflist_node, depart_mathdeflist_node),
        elatex=(visit_mathdeflist_node, depart_mathdeflist_node),
        latex=(visit_mathdeflist_node, depart_mathdeflist_node),
        text=(visit_mathdeflist_node, depart_mathdeflist_node),
        md=(visit_mathdeflist_node, depart_mathdeflist_node),
        rst=(visit_mathdeflist_node, depart_mathdeflist_node),
    )
    app.add_node(
        mathdef_node,
        html=(visit_mathdef_node, depart_mathdef_node),
        epub=(visit_mathdef_node, depart_mathdef_node),
        elatex=(visit_mathdef_node, depart_mathdef_node),
        latex=(visit_mathdef_node, depart_mathdef_node),
        text=(visit_mathdef_node, depart_mathdef_node),
        md=(visit_mathdef_node, depart_mathdef_node),
        rst=(visit_mathdef_node, depart_mathdef_node),
    )
    app.add_directive("mathdef", MathDef)
    app.add_directive("mathdeflist", MathDefList)
    app.connect("doctree-read", process_mathdefs)
    app.connect("doctree-resolved", process_mathdef_nodes)
    app.connect("env-purge-doc", purge_mathsext)
    app.connect("env-merge-info", merge_mathdef)
    return {"version": sphinx.__display_version__, "parallel_read_safe": True}