"""Simple sphinx extension that executes code in jupyter and inserts output."""

from ._version import version_info, __version__
from sphinx.util import logging
import docutils
import ipywidgets
import os
from sphinx.util.fileutil import copy_asset
from sphinx.errors import ExtensionError
from IPython.lib.lexers import IPythonTracebackLexer, IPython3Lexer

from .ast import (
    JupyterCell,
    JupyterCellNode,
    CellInputNode,
    CellOutputNode,
    CellOutputBundleNode,
    JupyterKernelNode,
    JupyterWidgetViewNode,
    JupyterWidgetStateNode,
    WIDGET_VIEW_MIMETYPE,
    JupyterDownloadRole,
    CellOutputsToNodes,
)
from .execute import JupyterKernel, ExecuteJupyterCells
from .thebelab import ThebeButton, ThebeButtonNode, ThebeOutputNode, ThebeSourceNode

REQUIRE_URL_DEFAULT = (
    "https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.4/require.min.js"
)
THEBELAB_URL_DEFAULT = "https://unpkg.com/thebelab@^0.4.0"

logger = logging.getLogger(__name__)

##############################################################################
# Constants and functions we'll use later

# Used for nodes that do not need to be rendered
def skip(self, node):
    raise docutils.nodes.SkipNode


# Used for nodes that should be gone by rendering time (OutputMimeBundleNode)
def halt(self, node):
    raise ExtensionError(
        (
            "Rendering encountered a node type that should "
            "have been removed before rendering: %s" % type(node)
        )
    )


# Renders the children of a container
render_container = (
    lambda self, node: self.visit_container(node),
    lambda self, node: self.depart_container(node),
)

# Used to render the container and its children as HTML
def visit_container_html(self, node):
    self.body.append(node.visit_html())
    self.visit_container(node)


def depart_container_html(self, node):
    self.depart_container(node)
    self.body.append(node.depart_html())


# Used to render an element node as HTML
def visit_element_html(self, node):
    self.body.append(node.html())
    raise docutils.nodes.SkipNode


# Used to render the ThebeSourceNode conditionally for non-HTML builders
def visit_thebe_source(self, node):
    if node["hide_code"]:
        raise docutils.nodes.SkipNode
    else:
        self.visit_container(node)


render_thebe_source = (
    visit_thebe_source,
    lambda self, node: self.depart_container(node),
)

##############################################################################
# Sphinx callback functions
def builder_inited(app):
    """
    2 cases
    case 1: ipywidgets 7, with require
    case 2: ipywidgets 7, no require
    """
    require_url = app.config.jupyter_sphinx_require_url
    if require_url:
        app.add_js_file(require_url)
        embed_url = (
            app.config.jupyter_sphinx_embed_url
            or ipywidgets.embed.DEFAULT_EMBED_REQUIREJS_URL
        )
    else:
        embed_url = (
            app.config.jupyter_sphinx_embed_url
            or ipywidgets.embed.DEFAULT_EMBED_SCRIPT_URL
        )
    if embed_url:
        app.add_js_file(embed_url)

    # add jupyter-sphinx css
    app.add_css_file("jupyter-sphinx.css")
    # Check if a thebelab config was specified
    if app.config.jupyter_sphinx_thebelab_config:
        app.add_js_file("thebelab-helper.js")
        app.add_css_file("thebelab.css")


def build_finished(app, env):
    if app.builder.format != "html":
        return

    # Copy stylesheet
    src = os.path.join(os.path.dirname(__file__), "css")
    dst = os.path.join(app.outdir, "_static")
    copy_asset(src, dst)

    thebe_config = app.config.jupyter_sphinx_thebelab_config
    if not thebe_config:
        return

    # Copy all thebelab related assets
    src = os.path.join(os.path.dirname(__file__), "thebelab")
    dst = os.path.join(app.outdir, "_static")
    copy_asset(src, dst)


##############################################################################
# Main setup
def setup(app):
    """A temporary setup function so that we can use it here and in execute.

    This should be removed and converted into `setup` after a deprecation
    cycle.
    """
    # Configuration

    app.add_config_value(
        "jupyter_execute_kwargs",
        dict(timeout=-1, allow_errors=True, store_widget_state=True),
        "env",
    )
    app.add_config_value("jupyter_execute_default_kernel", "python3", "env")
    app.add_config_value(
        "jupyter_execute_data_priority",
        [
            WIDGET_VIEW_MIMETYPE,
            "application/javascript",
            "text/html",
            "image/svg+xml",
            "image/png",
            "image/jpeg",
            "text/latex",
            "text/plain",
        ],
        "env",
    )

    # ipywidgets config
    app.add_config_value("jupyter_sphinx_require_url", REQUIRE_URL_DEFAULT, "html")
    app.add_config_value("jupyter_sphinx_embed_url", None, "html")

    # thebelab config, can be either a filename or a dict
    app.add_config_value("jupyter_sphinx_thebelab_config", None, "html")
    app.add_config_value("jupyter_sphinx_thebelab_url", THEBELAB_URL_DEFAULT, "html")

    # linenos config
    app.add_config_value("jupyter_sphinx_linenos", False, "env")
    app.add_config_value("jupyter_sphinx_continue_linenos", False, "env")

    # JupyterKernelNode is just a doctree marker for the
    # ExecuteJupyterCells transform, so we don't actually render it.
    app.add_node(
        JupyterKernelNode,
        html=(skip, None),
        latex=(skip, None),
        textinfo=(skip, None),
        text=(skip, None),
        man=(skip, None),
    )

    # Register our container nodes, these should behave just like a regular container
    for node in [JupyterCellNode, CellInputNode, CellOutputNode]:
        app.add_node(
            node,
            override=True,
            html=(render_container),
            latex=(render_container),
            textinfo=(render_container),
            text=(render_container),
            man=(render_container),
        )

    # Register the output bundle node.
    # No translators should touch this node because we'll replace it in a post-transform
    app.add_node(
        CellOutputBundleNode,
        override=True,
        html=(halt, None),
        latex=(halt, None),
        textinfo=(halt, None),
        text=(halt, None),
        man=(halt, None),
    )

    # JupyterWidgetViewNode holds widget view JSON,
    # but is only rendered properly in HTML documents.
    app.add_node(
        JupyterWidgetViewNode,
        html=(visit_element_html, None),
        latex=(skip, None),
        textinfo=(skip, None),
        text=(skip, None),
        man=(skip, None),
    )
    # JupyterWidgetStateNode holds the widget state JSON,
    # but is only rendered in HTML documents.
    app.add_node(
        JupyterWidgetStateNode,
        html=(visit_element_html, None),
        latex=(skip, None),
        textinfo=(skip, None),
        text=(skip, None),
        man=(skip, None),
    )

    # ThebeSourceNode holds the source code and is rendered if
    # hide-code is not specified. For HTML it is always rendered,
    # but hidden using the stylesheet
    app.add_node(
        ThebeSourceNode,
        html=(visit_container_html, depart_container_html),
        latex=render_thebe_source,
        textinfo=render_thebe_source,
        text=render_thebe_source,
        man=render_thebe_source,
    )

    # ThebeOutputNode holds the output of the Jupyter cells
    # and is rendered if hide-output is not specified.
    app.add_node(
        ThebeOutputNode,
        html=(visit_container_html, depart_container_html),
        latex=render_container,
        textinfo=render_container,
        text=render_container,
        man=render_container,
    )

    # ThebeButtonNode is the button that activates thebelab
    # and is only rendered for the HTML builder
    app.add_node(
        ThebeButtonNode,
        html=(visit_element_html, None),
        latex=(skip, None),
        textinfo=(skip, None),
        text=(skip, None),
        man=(skip, None),
    )

    app.add_directive("jupyter-execute", JupyterCell)
    app.add_directive("jupyter-kernel", JupyterKernel)
    app.add_directive("thebe-button", ThebeButton)
    app.add_role("jupyter-download:notebook", JupyterDownloadRole())
    app.add_role("jupyter-download:nb", JupyterDownloadRole())
    app.add_role("jupyter-download:script", JupyterDownloadRole())
    app.add_transform(ExecuteJupyterCells)
    app.add_post_transform(CellOutputsToNodes)

    # For syntax highlighting
    app.add_lexer("ipythontb", IPythonTracebackLexer())
    app.add_lexer("ipython", IPython3Lexer())

    app.connect("builder-inited", builder_inited)
    app.connect("build-finished", build_finished)

    return {"version": __version__, "parallel_read_safe": True}