import os
import logging
import re

from sympy.external import import_module
from sympy.parsing.latex.lark.transformer import TransformToSymPyExpr

_lark = import_module("lark")


class LarkLaTeXParser:
    r"""Class for converting input `\mathrm{\LaTeX}` strings into SymPy Expressions.
    It holds all the necessary internal data for doing so, and exposes hooks for
    customizing its behavior.

    Parameters
    ==========

    print_debug_output : bool, optional

        If set to ``True``, prints debug output to the logger. Defaults to ``False``.

    transform : bool, optional

        If set to ``True``, the class runs the Transformer class on the parse tree
        generated by running ``Lark.parse`` on the input string. Defaults to ``True``.

        Setting it to ``False`` can help with debugging the `\mathrm{\LaTeX}` grammar.

    grammar_file : str, optional

        The path to the grammar file that the parser should use. If set to ``None``,
        it uses the default grammar, which is in ``grammar/latex.lark``, relative to
        the ``sympy/parsing/latex/lark/`` directory.

    transformer : str, optional

        The name of the Transformer class to use. If set to ``None``, it uses the
        default transformer class, which is :py:func:`TransformToSymPyExpr`.

    """
    def __init__(self, print_debug_output=False, transform=True, grammar_file=None, transformer=None):
        grammar_dir_path = os.path.join(os.path.dirname(__file__), "grammar/")

        if grammar_file is None:
            with open(os.path.join(grammar_dir_path, "latex.lark"), encoding="utf-8") as f:
                latex_grammar = f.read()
        else:
            with open(grammar_file, encoding="utf-8") as f:
                latex_grammar = f.read()

        self.parser = _lark.Lark(
            latex_grammar,
            source_path=grammar_dir_path,
            parser="earley",
            start="latex_string",
            lexer="auto",
            ambiguity="explicit",
            debug=True,
            propagate_positions=False,
            maybe_placeholders=False,
            keep_all_tokens=True)

        self.print_debug_output = print_debug_output
        self.transform_expr = transform

        if transformer is None:
            self.transformer = TransformToSymPyExpr()
        else:
            self.transformer = transformer()

    def doparse(self, s: str):
        if self.print_debug_output:
            _lark.logger.setLevel(logging.DEBUG)

        parse_tree = self.parser.parse(s)

        if not self.transform_expr:
            # exit early and return the parse tree
            _lark.logger.debug("expression =", s)
            _lark.logger.debug(parse_tree)
            _lark.logger.debug(parse_tree.pretty())
            return parse_tree

        if self.print_debug_output:
            # print this stuff before attempting to run the transformer
            _lark.logger.debug("expression =", s)
            # print the `parse_tree` variable
            _lark.logger.debug(parse_tree.pretty())

        sympy_expression = self.transformer.transform(parse_tree)

        if self.print_debug_output:
            _lark.logger.debug("SymPy expression =", sympy_expression)

        return sympy_expression


if _lark is not None:
    _lark_latex_parser = LarkLaTeXParser()


def parse_latex_lark(s: str):
    """
    Experimental LaTeX parser using Lark.

    This function is still under development and its API may change with the
    next releases of SymPy.
    """
    if _lark is None:
        raise ImportError("Lark is probably not installed")
    return _lark_latex_parser.doparse(s)


def _pretty_print_lark_trees(tree, indent=0, show_expr=True):
    if isinstance(tree, _lark.Token):
        return tree.value

    data = str(tree.data)

    is_expr = data.startswith("expression")

    if is_expr:
        data = re.sub(r"^expression", "E", data)

    is_ambig = (data == "_ambig")

    if is_ambig:
        new_indent = indent + 2
    else:
        new_indent = indent

    output = ""
    show_node = not is_expr or show_expr

    if show_node:
        output += str(data) + "("

    if is_ambig:
        output += "\n" + "\n".join([" " * new_indent + _pretty_print_lark_trees(i, new_indent, show_expr) for i in tree.children])
    else:
        output += ",".join([_pretty_print_lark_trees(i, new_indent, show_expr) for i in tree.children])

    if show_node:
        output += ")"

    return output
