#!/usr/bin/env python3
#
#  rest_example.py
"""
Directive to show example reStructuredText and the rendered output.

.. extensions:: sphinx_toolbox.rest_example


Usage
---------

.. rst:directive:: rest-example

	Directive to show example reStructuredText and the rendered output.

	.. rst:directive:option:: force
		:type: flag

		If given, minor errors on highlighting are ignored.

	.. rst:directive:option:: emphasize-lines: line numbers
		:type: comma separated numbers

		Emphasize particular lines of the code block:

	.. rst:directive:option:: tab-width: number
		:type: number

		Sets the size of the indentation in spaces.

	.. rst:directive:option:: dedent: number
		:type: number

		Strip indentation characters from the code block,

	.. latex:clearpage::

	:bold-title:`Example`

	.. rest-example::

		.. rest-example::

			:source:`sphinx_toolbox/config.py`

			Here is the :source:`source code <sphinx_toolbox/config.py>`


API Reference
---------------

"""
#
#  Copyright © 2020-2021 Dominic Davis-Foster <dominic@davis-foster.co.uk>
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to deal
#  in the Software without restriction, including without limitation the rights
#  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#  copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in all
#  copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
#  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
#  MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
#  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
#  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
#  OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
#  OR OTHER DEALINGS IN THE SOFTWARE.
#

# stdlib
from typing import Any, Dict, List, Sequence

# 3rd party
import sphinx.environment
from docutils import nodes
from docutils.parsers.rst import directives
from docutils.statemachine import ViewList
from domdf_python_tools.stringlist import StringList
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective

# this package
from sphinx_toolbox.utils import OptionSpec, Purger, SphinxExtMetadata, metadata_add_version

__all__ = ("reSTExampleDirective", "make_rest_example", "rest_example_purger", "setup")


class reSTExampleDirective(SphinxDirective):
	"""
	Directive to show some reStructuredText source, and the rendered output.
	"""

	has_content: bool = True

	# Options to pass through to .. code-block::
	option_spec: OptionSpec = {  # type: ignore[assignment]
		"force": directives.flag,
		"emphasize-lines": directives.unchanged,
		"tab-width": int,
		"dedent": int,
		}

	def run(self) -> List[nodes.Node]:
		"""
		Create the rest_example node.
		"""

		targetid = f'example-{self.env.new_serialno("sphinx-toolbox rest_example"):d}'
		targetnode = nodes.target('', '', ids=[targetid])

		content = make_rest_example(
				self.options,
				self.env,
				self.content,  # type: ignore[arg-type]
				)
		view = ViewList(content)

		example_node = nodes.paragraph(rawsource=content)  # type: ignore[arg-type]
		self.state.nested_parse(view, self.content_offset, example_node)  # type: ignore[arg-type]

		rest_example_purger.add_node(self.env, example_node, targetnode, self.lineno)

		return [targetnode, example_node]


def make_rest_example(
		options: Dict[str, Any],
		env: sphinx.environment.BuildEnvironment,
		content: Sequence[str],
		) -> List[str]:
	"""
	Make the content of a reST Example node.

	:param options:
	:param content: The user-provided content of the directive.
	"""

	output = StringList(".. container:: rest-example")
	output.indent_type = ' ' * env.config.docutils_tab_width

	output.blankline()

	with output.with_indent_size(1):

		output.append(".. code-block:: rest")

		with output.with_indent_size(2):
			for option, value in options.items():
				if value is None:
					output.append(f":{option}:")
				else:
					output.append(f":{option}: {value}")

			output.blankline()
			output.extend(content)

		output.blankline(ensure_single=True)
		output.extend(content)
		output.blankline(ensure_single=True)

	return list(output)


#: Purger to track rest-example nodes, and remove redundant ones.
rest_example_purger = Purger("all_rest_example_nodes")


@metadata_add_version
def setup(app: Sphinx) -> SphinxExtMetadata:
	"""
	Setup :mod:`sphinx_toolbox.rest_example`.

	.. versionadded:: 0.7.0

	:param app: The Sphinx application.
	"""

	# Hack to get the docutils tab size, as there doesn't appear to be any other way
	app.setup_extension("sphinx_toolbox.tweaks.tabsize")
	app.setup_extension("sphinx_toolbox._css")

	app.add_directive("rest-example", reSTExampleDirective)
	app.connect("env-purge-doc", rest_example_purger.purge_nodes)

	return {"parallel_read_safe": True}
