import random as _random
import string as _string

import railroad as _railroad

from ..._utilities import operating_system as _operating_system
from .. import data_structures as _data_structures


def create_syntax_diagram(grammar):
    """Create a figure of a syntax diagram that represents the grammar.

    References
    ----------
    - https://github.com/tabatkins/railroad-diagrams

    """
    # Railroad diagram styling options
    _railroad.DIAGRAM_CLASS = (
        "rd"  # class set on the root <svg> element of each diagram
    )
    _railroad.VS = 14  # vertical separation between two items
    _railroad.AR = 14  # radius of the arcs used in branching containers

    # Generate a title and diagram for each non-terminal
    html_parts = []
    title_template = '<div class="rd-title" id="{text}_§TOKEN§">{text}:</div>'
    for lhs in grammar.nonterminal_symbols:
        # Create div element holding the title
        title = title_template.format(text=lhs.text)
        # Create SVG element holding the diagram
        rhs_multiple = grammar.production_rules[lhs]
        rr_choices = []
        for rhs in rhs_multiple:
            sequence = []
            for symbol in rhs:
                if isinstance(symbol, _data_structures.NonterminalSymbol):
                    href_to_div = "#{text}_§TOKEN§".format(text=symbol.text)
                    rr_symbol = _railroad.NonTerminal(symbol.text, href_to_div)
                else:
                    if symbol.text == "":
                        rr_symbol = _railroad.Skip()
                    else:
                        rr_symbol = _railroad.Terminal(symbol.text)
                sequence.append(rr_symbol)
            rr_sequence = _railroad.Sequence(*sequence)
            rr_choices.append(rr_sequence)
        rr_content = _railroad.Choice(0, *rr_choices)
        rr_diagram = _railroad.Diagram(rr_content, type="complex", css=None)
        rr_diagram.format(15)
        svg = _railroad_diagram_to_svg(rr_diagram)
        # Collect results
        html_parts.append(title)
        html_parts.append(svg)

    # Convert parts to a HTML string
    html_text = _CSS + "".join(html_parts)
    fig = GrammarFigure(html_text)
    return fig


def _railroad_diagram_to_svg(diagram):
    """Convert a railroad diagram object into SVG text."""
    roc = _RailroadOutputCollector()
    diagram.writeSvg(roc)
    return roc.text


class _RailroadOutputCollector:
    """Collect the outputs generated by writeSvg of a railroad diagram object."""

    def __init__(self):
        """Prepare the collection of multiple outputs."""
        self.parts = []

    def __call__(self, part):
        """Collect a single output."""
        self.parts.append(part)

    @property
    def text(self):
        """Combine all outputs to a single string."""
        return "".join(self.parts)


class GrammarFigure:
    """Data structure for wrapping, displaying and exporting a Grammar figure."""

    # Initialization
    def __init__(self, html_template):
        """Initialize a figure with a partly filled HTML template containing a visualization."""
        self._html_template = html_template

    # Representations
    def __repr__(self):
        """Compute the "official" string representation of the figure."""
        return "<{} object at {}>".format(self.__class__.__name__, hex(id(self)))

    def _repr_html_(self):
        """Provide rich display representation in HTML format for Jupyter notebooks."""
        return self.html_text_partial

    # Display in browser or notebook
    def display(self, inline=False):
        """Display the plot in a webbrowser or as IPython rich display representation.

        Parameters
        ----------
        inline : bool
            If True, the plot will be shown inline in a Jupyter notebook.

        """
        if inline:
            from IPython.display import HTML, display

            display(HTML(self.html_text_partial))
        else:
            _operating_system.open_in_webbrowser(self.html_text_standalone)

    # Further representations
    @property
    def html_text(self):
        """Create a HTML text representation."""
        return self.html_text_standalone

    @property
    def html_text_standalone(self):
        """Create a standalone HTML text representation."""
        html_text = _HTML.format(self.html_text_partial)
        return html_text

    @property
    def html_text_partial(self):
        """Create a partial HTML text representation without html, head and body tags."""
        html_text = self._html_template.replace("§TOKEN§", self._generate_random_id())
        return html_text

    # Export as HTML file (interactive)
    def export_html(self, filepath):
        """Export the plot as HTML file.

        Parameters
        ----------
        filepath : str
            Filepath of the created HTML file.
            If the file exists it will be overwritten without warning.
            If the path does not end with ".html" it will be changed to do so.
            If the parent directory does not exist it will be created.

        Returns
        -------
        filepath : str
            Filepath of the generated HTML file, guaranteed to end with ".html".

        """
        # Precondition
        used_filepath = _operating_system.ensure_file_extension(filepath, "html")

        # Transformation
        with open(used_filepath, "w", encoding="utf-8") as file_handle:
            file_handle.write(self.html_text)
        return used_filepath

    @staticmethod
    def _generate_random_id(length=32):
        symbols = _string.ascii_letters + _string.digits
        return "r" + "".join(_random.choice(symbols) for _ in range(length))


_HTML = """<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
</head>
<body>

{}

</body>
</html>"""

_CSS = """
<style>
  .rd-title {
    font: 14px monospace;
    white-space: pre;
    margin-top: 1ex;
    font-weight: bold;
  }
  svg.rd text {
    font: 14px monospace;
    white-space: pre;
    text-anchor: middle;
  }
  svg.rd path {
    stroke-width: 1;
    stroke: black;
    fill: rgba(0,0,0,0);
  }
  svg.rd rect {
    stroke-width: 1;
    stroke: black;
  }
  svg.rd g.non-terminal rect {
    fill: white;
  }
  svg.rd g.non-terminal a {
    text-decoration: none;
  }
  svg.rd g.non-terminal a text:hover {
    font-weight:bold;
  }
  svg.rd g.terminal rect {
    fill: #00864b;
  }
  svg.rd g.terminal text {
    fill: white;
  }
</style>
"""
