import os
import logging
import shutil
from docutils import nodes
from docutils.parsers.rst import directives, Directive
import sphinx
from sphinx.ext.graphviz import latex_visit_graphviz, text_visit_graphviz
from ..ext_helper import get_env_state_info
from ..ext_io_helper import download_requirejs, get_url_content_timeout
from ..runpython.sphinx_runpython_extension import run_python_script
class gdot_node(nodes.admonition):
    """
    Defines ``gdot`` node.
    """
    pass
[docs]class GDotDirective(Directive):
    """
    A ``gdot`` node displays a :epkg:`DOT` graph.
    The build choose :epkg:`SVG` for :epkg:`HTML` format and image for
    other format unless it is specified.
    * *format*: SVG or HTML
    * *script*: boolean or a string to indicate than the standard output
        should only be considered after this substring
    * *url*: url to :epkg:`viz.js`, only if format *SVG* is selected
    * *process*: run the script in an another process
    Example::
        .. gdot::
            digraph foo {
                "bar" -> "baz";
            }
    Which gives:
    .. gdot::
        digraph foo {
            "bar" -> "baz";
        }
    The directive also accepts scripts producing
    dot graphs on the standard output. Option *script*
    must be specified. This extension loads
    `sphinx.ext.graphviz <https://www.sphinx-doc.org/
    en/master/usage/extensions/graphviz.html>`_
    if not added to the list of extensions:
    Example::
        .. gdot::
            :format: png
            digraph foo {
                "bar" -> "baz";
            }
    .. gdot::
        :format: png
        digraph foo {
            "bar" -> "baz";
        }
    The output can be produced by a script::
        .. gdot::
            :script:
            print('''
                digraph foo {
                    "bar" -> "baz";
                }
            ''')
    .. gdot::
        :script:
        print('''
            digraph foo {
                "bar" -> "baz";
            }
        ''')
    """
    node_class = gdot_node
    has_content = True
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = False
    option_spec = {
        "format": directives.unchanged,
        "script": directives.unchanged,
        "url": directives.unchanged,
        "process": directives.unchanged,
    }
    _default_url = (
        "https://github.com/sdpython/jyquickhelper/raw/master/src/"
        "jyquickhelper/js/vizjs/viz.js"
    )
    def run(self):
        """
        Builds the collapse text.
        """
        # retrieves the parameters
        if "format" in self.options:
            format = self.options["format"]
        else:
            format = "png"
        url = self.options.get("url", "local")
        bool_set_ = (True, 1, "True", "1", "true", "")
        process = "process" in self.options and self.options["process"] in bool_set_
        if url == "local":
            try:
                import jyquickhelper
                path = os.path.join(
                    os.path.dirname(jyquickhelper.__file__), "js", "vizjs", "viz.js"
                )
                if not os.path.exists(path):
                    raise ImportError("jyquickelper needs to be updated to get viz.js.")
                url = "local"
            except ImportError:
                url = GDotDirective._default_url
                logger = logging.getLogger("gdot")
                logger.warning("[gdot] use %r", url)
        info = get_env_state_info(self)
        docname = info["docname"]
        if url == "local":
            if docname is None or "HERE" not in info:
                url = GDotDirective._default_url
                logger = logging.getLogger("gdot")
                logger.warning("[gdot] docname is none, falling back to %r.", url)
            else:
                spl = docname.split("/")
                sp = [".."] * (len(spl) - 1) + ["_static", "viz.js"]
                url = "/".join(sp)
        if "script" in self.options:
            script = self.options["script"]
            if script in (0, "0", "False", "false"):
                script = None
            elif script in (1, "1", "True", "true", ""):
                script = ""
            elif len(script) == 0:
                raise RuntimeError(
                    "script should be a string to indicate"
                    " the beginning of DOT graph."
                )
        else:
            script = False
        # executes script if any
        content = "\n".join(self.content)
        if script or script == "":
            stdout, stderr, _ = run_python_script(content, process=process)
            if stderr:
                logger.warning("[gdot] a dot graph cannot be draw due to %s", stderr)
            content = stdout
            if script:
                spl = content.split(script)
                if len(spl) > 2:
                    logger.warning("[gdot] too many output lines %s", content)
                content = spl[-1]
        node = gdot_node(
            format=format, code=content, url=url, options={"docname": docname}
        )
        return [node] 
def visit_gdot_node_rst(self, node):
    """
    visit collapse_node
    """
    self.new_state(0)
    self.add_text(".. gdot::" + self.nl)
    if node["format"] != "?":
        self.add_text("    :format: " + node["format"] + self.nl)
    if node["url"]:
        self.add_text("    :url: " + node["url"] + self.nl)
    self.new_state(self.indent)
    for row in node["code"].split("\n"):
        self.add_text(row + self.nl)
def depart_gdot_node_rst(self, node):
    """
    depart collapse_node
    """
    self.end_state()
    self.end_state(wrap=False)
def visit_gdot_node_html_svg(self, node):
    """
    visit collapse_node
    """
    def process(text):
        text = text.replace("\\", "\\\\")
        text = text.replace("\n", "\\n")
        text = text.replace('"', '\\"')
        return text
    nid = str(id(node))
    content = """
    <div id="gdot-{0}-cont"><div id="gdot-{0}" style="width:100%;height:100%;"></div>
    """.format(
        nid
    )
    script = (
        """
    require(['__URL__'], function() { var svgGraph = Viz("__DOT__");
    document.getElementById('gdot-__ID__').innerHTML = svgGraph; });
    """.replace(
            "__ID__", nid
        )
        .replace("__DOT__", process(node["code"]))
        .replace("__URL__", node["url"])
    )
    # find the path
    source = self.document.attributes["source"]
    folder = os.path.dirname(source)
    # from_ = self.builder.get_target_uri(source)
    # req = self.builder.get_target_uri("_static/require.js")
    # rel = self.builder.get_relative_uri(from_, req)
    if os.path.exists(folder):
        while not os.path.exists(os.path.join(folder, "conf.py")):
            cts = set(os.listdir(folder))
            if "conf.py" in cts:
                break
            exts = {os.path.splitext(name)[-1] for name in cts}
            if ".rst" not in exts:
                folder = None
                break
            folder = os.path.split(folder)[0]
    else:
        folder = None
    self.body.append(content)
    if folder is None:
        self.body.append(
            '<script src="_static/require.js"></script><script>'
            "{0}{1}{0}</script>{0}".format("\n", script)
        )
    else:
        current = os.path.dirname(source)
        rel = os.path.relpath(current, folder)
        if rel not in {"", "."}:
            rel = rel.replace("\\", "/")
            rel = f"{'/'.join(['..'] * len(rel.split('/')))}/"
        else:
            rel = ""
        self.body.append(
            '<script src="{2}_static/require.js"></script><script>'
            "{0}{1}{0}</script>{0}".format("\n", script, rel)
        )
def depart_gdot_node_html_svg(self, node):
    """
    depart collapse_node
    """
    self.body.append("</div>")
def visit_gdot_node_html(self, node):
    """
    visit collapse_node, the function switches between
    `graphviz.py <https://github.com/sphinx-doc/sphinx/blob/master/sphinx/ext/graphviz.py>`_
    and the :epkg:`SVG` format.
    """
    if node["format"].lower() == "png":
        from sphinx.ext.graphviz import html_visit_graphviz
        return html_visit_graphviz(self, node)
    if node["format"].lower() in ("?", "svg"):
        return visit_gdot_node_html_svg(self, node)
    raise RuntimeError(f"Unexpected format for graphviz '{node['format']}'.")
def depart_gdot_node_html(self, node):
    """
    depart collapse_node
    """
    if node["format"] == "png":
        return None
    return depart_gdot_node_html_svg(self, node)
def copy_js_files(app):
    try:
        import jyquickhelper
        local = True
    except ImportError:
        local = False
    logger = logging.getLogger("gdot")
    dest = app.config.html_static_path
    if isinstance(dest, list) and len(dest) > 0:
        dest = dest[0]
    else:
        logger.warning(
            "[gdot] unable to locate 'html_static_path' (%r), "
            "unable to use local viz.js.",
            app.config.html_static_path,
        )
        return
    srcdir = app.builder.srcdir
    if not os.path.exists(srcdir):
        raise FileNotFoundError(f"Source file is wrong {srcdir!r}.")
    destf = os.path.join(os.path.abspath(srcdir), dest)
    if not os.path.exists(destf):
        logger.warning(
            "[gdot] destination folder %r does not exists, "
            "unable to use local viz.js.",
            destf,
        )
        return
    # viz.js
    file_dest = os.path.join(destf, "viz.js")
    if os.path.exists(file_dest):
        logger.info("[gdot] %r already installed.", file_dest)
    else:
        if local:
            path = os.path.join(
                os.path.dirname(jyquickhelper.__file__), "js", "vizjs", "viz.js"
            )
            if os.path.exists(path):
                # We copy the file to static path.
                try:
                    shutil.copy(path, file_dest)
                    logger.info("[gdot] copy %r to %r.", path, file_dest)
                except PermissionError as e:  # pragma: no cover
                    logger.warning(
                        "[gdot] permission error (%r), unable to use local viz.js", e
                    )
            else:
                logger.warning("[gdot] unable to find %r", path)
        else:
            logger.warning("[gdot] viz.js, use %r", GDotDirective._default_url)
            file_dest = os.path.join(destf, "require.js")
            try:
                content = get_url_content_timeout(
                    GDotDirective._default_url, output=file_dest, raise_exception=False
                )
            except Exception as e:
                logger.warning("[gdot] download failed due to %r", e)
                content = None
            if content is None:
                logger.warning(
                    "[gdot] unable to download %r to %r",
                    GDotDirective._default_url,
                    file_dest,
                )
            else:
                logger.info(
                    "[gdot] download %r to %r", GDotDirective._default_url, file_dest
                )
    # require.js
    file_dest = os.path.join(destf, "require.js")
    if os.path.exists(file_dest):
        logger.info("[gdot] %r already installed.", file_dest)
    else:
        try:
            download_requirejs(destf)
        except Exception as e:
            logger.warning("[gdot] download_requirejs failed due to %r", e)
    if os.path.exists(file_dest):
        # It adds <script async="defer" src="_static/require.js"></script>
        # at the bottom of the file. It needs to be at the beginning.
        # app.add_js_file("require.js", priority=200)
        logger.info("[gdot] %r installed.", file_dest)
    else:
        logger.warning("[gdot] %r not installed.", file_dest)
def setup(app):
    """
    setup for ``gdot`` (sphinx)
    """
    if "sphinx.ext.graphviz" not in app.config.extensions:
        from sphinx.ext.graphviz import setup as setup_g  # pylint: disable=W0611
        setup_g(app)
    app.connect("builder-inited", copy_js_files)
    app.add_node(
        gdot_node,
        html=(visit_gdot_node_html, depart_gdot_node_html),
        epub=(visit_gdot_node_html, depart_gdot_node_html),
        elatex=(latex_visit_graphviz, None),
        latex=(latex_visit_graphviz, None),
        text=(text_visit_graphviz, None),
        md=(text_visit_graphviz, None),
        rst=(visit_gdot_node_rst, depart_gdot_node_rst),
    )
    app.add_directive("gdot", GDotDirective)
    return {"version": sphinx.__display_version__, "parallel_read_safe": True}