# -*- coding: utf-8 -*-
"""
Layout

This module contains symbols used to define the high-level layout for expression formatting.

For instance, to represent a set of consecutive expressions in a row, we can use 'Row'.

"""


from mathics.builtin.box.layout import GridBox, PaneBox, RowBox, to_boxes
from mathics.builtin.makeboxes import MakeBoxes
from mathics.core.atoms import Integer, Real, String
from mathics.core.builtin import Builtin, Operator, PostfixOperator, PrefixOperator
from mathics.core.expression import Evaluation, Expression
from mathics.core.list import ListExpression
from mathics.core.symbols import Symbol
from mathics.core.systemsymbols import (
    SymbolMakeBoxes,
    SymbolPostfix,
    SymbolPrefix,
    SymbolSubscriptBox,
)
from mathics.eval.lists import list_boxes
from mathics.format.box import eval_infix, eval_postprefix, format_element, parenthesize


class Center(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Center.html</url>

    <dl>
      <dt>'Center'
      <dd>is used with the 'ColumnAlignments' option to 'Grid' or
        'TableForm' to specify a centered column.
    </dl>
    """

    summary_text = "center alignment"


class Format(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Format.html</url>

    <dl>
      <dt>'Format'[$expr$]
      <dd>used on the left-hand side of an assignment to specify how $expr$ should be printed.
    </dl>

    First, we set up a 'Format' definition for 'f' to display its arguments as if it were equivalent to an infix operator "~":

    >> Format[f[x___]] := Infix[{x}, "~"]

    Now, to see this format in use:

    >> f[1, 2, 3]
     = 1 ~ 2 ~ 3
    >> f[1]
     = 1

    Raw objects cannot be formatted:
    >> Format[3] = "three";
     : Tag Integer in 3 is Protected.

    Format types must be symbols:
    >> Format[r, a + b] = "r";
     : Format type a + b is not a symbol.

    Formats must be attached to the head of an expression:
    >> f /: Format[g[f]] = "my f";
     : Tag f not found or too deep for an assigned rule.

    Format can be used to specify the request format:
    >> Format[Integrate[F[x], x], TeXForm]
     = \\int F(x) \\, dx

    Format evaluates its first element before applying the format:
    >> Format[Integrate[Cos[x], x], TeXForm]
     = ...
    but the result keeps the structure:
    >> % //FullForm
     = Format[Sin[x], TeXForm]

    If the second parameter is omitted, 'Format' is ignored:
    >> Format[F[x]]
     = F[x]

    If the second argument is not one of '$PrintForms', a message \
    is shown, and the argument is discarded:
    >> Format[F[x], NoFormat]
     : Value of option FormatType -> NoFormat is not valid.
     = F[x]

    Mathics3 'Format' output can differ slightly from WMA in what we hope \
    is a more useful way.

    Use 'InputForm' if you want to get a 'Format' definition that can be used as \
    Mathics3 input:

    >> Format[{a -> Integrate[F[x], x]}, StandardForm] //InputForm
     = Format[{a ⇾ Integrate[F[x], x]}, StandardForm]

    In WMA, you might not get something that can be used as input.

    Similarly, use 'Fullform' to get a valid FullForm equivalent expression:

    >> Format[{a->Integrate[F[x], x]}, StandardForm] //FullForm
     = Format[{Rule[a, Integrate[F[x], x]]}, StandardForm]
    """

    messages = {"fttp": "Format type `1` is not a symbol."}
    summary_text = (
        "settable low-level translator from various forms to evaluatable expressions"
    )
    rules = {"MakeBoxes[Format[expr_], fmt_]": "MakeBoxes[expr, fmt]"}

    def eval_Makeboxes(self, expr, form, evaluation):
        """MakeBoxes[Format[expr_, form_], _]"""
        if form not in evaluation.definitions.printforms:
            evaluation.message("FormatType", "ftype", form)
            return format_element(expr, evaluation)
        return format_element(expr, evaluation, form)


class Grid(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Grid.html</url>

    <dl>
      <dt>'Grid'[{{$a_1$, $a_2$, ...}, {$b_1$, $b_2$, ...}, ...}]
      <dd>formats several expressions inside a 'GridBox'.
    </dl>

    >> Grid[{{a, b}, {c, d}}]
     = a   b
     .
     . c   d

    For shallow lists, elements are shown as a column:
    >> Grid[{a, b, c}]
     = a
     .
     . b
     .
     . c

    If the sublists have different sizes, the grid has the number of columns of the \
    largest one. Incomplete rows are completed with empty strings:

    >> Grid[{{"first", "second", "third"},{a},{1, 2, 3}}]
     = first   second   third
     .
     . a
     .
     . 1       2        3

    If the list is a mixture of lists and other expressions, the non-list expressions are
    shown as rows:

    >> Grid[{"This is a long title", {"first", "second", "third"},{a},{1, 2, 3}}]
     = This is a long title
     .
     . first   second   third
     .
     . a
     .
     . 1       2        3

    """

    options = GridBox.options
    summary_text = " 2D layout containing arbitrary objects"

    def eval_makeboxes(self, array, f, evaluation: Evaluation, options) -> Expression:
        """MakeBoxes[Grid[array_List, OptionsPattern[Grid]],
        f:StandardForm|TraditionalForm]"""

        elements = array.elements

        rows = (
            element.elements if element.has_form("List", None) else element
            for element in elements
        )

        def format_row(row):
            if isinstance(row, tuple):
                return ListExpression(
                    *(format_element(item, evaluation, f) for item in row),
                )
            return format_element(row, evaluation, f)

        return GridBox(
            ListExpression(*(format_row(row) for row in rows)),
            **options,
        )


class Infix(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Infix.html</url>

    <dl>
      <dt>'Infix'[$expr$, $oper$, $prec$, $assoc$]
      <dd>displays $expr$ with the infix operator $oper$, with precedence $prec$ and associativity $assoc$.
    </dl>

    'Infix' can be used with 'Format' to display certain forms with
    user-defined infix notation:
    >> Format[g[x_, y_]] := Infix[{x, y}, "#", 350, Left]
    >> g[a, g[b, c]]
     = a # (b # c)
    >> g[g[a, b], c]
     = a # b # c
    >> g[a + b, c]
     = (a + b) # c
    >> g[a * b, c]
     = a b # c
    >> g[a, b] + c
     = c + a # b
    >> g[a, b] * c
     = c (a # b)

    >> Infix[{a, b, c}, {"+", "-"}]
     = a + b - c
    """

    rules = {
        (
            "MakeBoxes[Infix[head_[elements___]], "
            "    f:StandardForm|TraditionalForm]"
        ): ('MakeBoxes[Infix[head[elements], StringForm["~`1`~", head]], f]'),
    }
    summary_text = "infix form"

    def eval_makeboxes_infix(
        self, expr, operator, precedence: Integer, grouping, form: Symbol, evaluation
    ):
        """MakeBoxes[Infix[expr_, operator_, precedence_:None, grouping_:None], form:StandardForm|TraditionalForm]"""
        return eval_infix(self, expr, operator, precedence, grouping, form, evaluation)


class Left(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Left.html</url>

    <dl>
      <dt>'Left'
      <dd>is used with operator formatting constructs to specify a \
          left-associative operator.
    </dl>
    """

    summary_text = "left alignment/left associative"


class NonAssociative(Builtin):
    """
        ## For some reason, this is a Builtin symbol in WMA, but it is not available in WR.
        ## <url>:WMA link:https://reference.wolfram.com/language/ref/NonAssociative.html</url>
    on, logic, comparison, datentime, attributes and binary)

        <dl>
          <dt>'NonAssociative'
          <dd>is used with operator formatting constructs to specify a non-associative operator.
        </dl>
    """

    summary_text = "non-associative operator"


class Pane(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Pane.html</url>

    <dl>
      <dt>'Pane[$expr$]'
      <dd> display $expr$ inside a pane.

      <dt>'Pane[$expr$, $width$]'
      <dd> display $expr$ inside a pane $width$ points wide.

      <dt>'Pane[$expr$, {$width$, $height$}]'
      <dd> display $expr$ in a pane with width $width$ and height $height$.
    </dl>
    A Pane is treated as an unbroken rectangular region for purposes of line breaking.

    >> Pane[37!]
     = Pane[13763753091226345046315979581580902400000000]

    In TeXForm, $Pane$ produce minipage environments:
    >> {{Pane[a,3], Pane[expt, 3]}}//TableForm//TeXForm
     = ...

    In MathMLForm, $Pane$ wraps the elements in <div>...</div> tags:
    >> {{Pane[a,3], Pane[expt, 3]}}//TableForm//MathMLForm
     = ...
    """

    summary_text = "put expressions inside a pane"
    options = {
        "ImageSize": "Automatic",
    }

    def eval_makeboxes(self, expr, f, evaluation, options):
        """MakeBoxes[Pane[expr_, OptionsPattern[Pane]], f_]"""
        box_expr = Expression(SymbolMakeBoxes, expr, f).evaluate(evaluation)
        return PaneBox(box_expr, **options)

    def eval_makeboxes2(self, expr, width, f, evaluation, options):
        """MakeBoxes[Pane[expr_, width_Integer, OptionsPattern[Pane]], f_]"""
        box_expr = Expression(SymbolMakeBoxes, expr, f).evaluate(evaluation)
        options["System`ImageSize"] = width
        return PaneBox(box_expr, **options)

    def eval_makeboxes3(self, expr, width, height, f, evaluation, options):
        """MakeBoxes[Pane[expr_, List[width_Integer, height_Integer], OptionsPattern[Pane]], f_]"""
        box_expr = Expression(SymbolMakeBoxes, expr, f).evaluate(evaluation)
        options["System`ImageSize"] = ListExpression(width, height)
        return PaneBox(box_expr, **options)


class Postfix(PostfixOperator):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Postfix.html</url>

    <dl>
      <dt>'$x$ // $f$'
      <dd>is equivalent to '$f$[$x$]'.
    </dl>

    >> b // a
     = a[b]
    >> c // b // a
     = a[b[c]]

    The postfix operator '//' is parsed to an expression before evaluation:
    >> Hold[x // a // b // c // d // e // f]
     = Hold[f[e[d[c[b[a[x]]]]]]]
    """

    grouping = "Left"
    operator_display = None
    summary_text = "postfix form"

    def eval_makeboxes_postfix(self, expr, h, precedence, form, evaluation):
        """MakeBoxes[Postfix[expr_, h_, precedence_:None],
        form:StandardForm|TraditionalForm]"""
        return eval_postprefix(
            self, SymbolPostfix, expr, h, precedence, form, evaluation
        )


class Precedence(Builtin):
    """
        ## As NonAssociative, this is a Builtin in WMA that does not have an entry in WR.
        ## <url>:WMA link:https://reference.wolfram.com/language/ref/Precedence.html</url>
    on, logic, comparison, datentime, attributes and binary)

        <dl>
          <dt>'Precedence'[$op$]
          <dd>returns the precedence of the built-in operator $op$.
        </dl>

        >> Precedence[Plus]
         = 310.
        >> Precedence[Plus] < Precedence[Times]
         = True

        Unknown symbols have precedence 670:
        >> Precedence[f]
         = 670.
        Other expressions have precedence 1000:
        >> Precedence[a + b]
         = 1000.
    """

    summary_text = "an object to be parenthesized with a given precedence level"

    def eval(self, expr, evaluation) -> Real:
        "Precedence[expr_]"

        name = expr.get_name()
        precedence = 1000
        if name:
            try:
                builtin = evaluation.definitions.get_definition(
                    name, only_if_exists=True
                ).builtin
            except KeyError:
                builtin = None
            if builtin is not None and isinstance(builtin, Operator):
                precedence = builtin.precedence
            else:
                precedence = 670
        return Real(precedence)


class PrecedenceForm(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/PrecedenceForm.html</url>

    <dl>
      <dt>'PrecedenceForm'[$expr$, $prec$]
      <dd> format $expr$ parenthesized as it would be if it contained an operator of precedence $prec$.
    </dl>

    >> PrecedenceForm[x/y, 12] - z
     = -z + (x / y)

    """

    def eval_outerprecedenceform(self, expr, precedence, form, evaluation):
        """MakeBoxes[PrecedenceForm[expr_, precedence_],
        form:StandardForm|TraditionalForm]"""

        py_precedence = precedence.get_int_value()
        boxes = format_element(expr, evaluation, form)
        return parenthesize(py_precedence, expr, boxes, True)

    summary_text = "parenthesize with a precedence"


class Prefix(PrefixOperator):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Prefix.html</url>

    <dl>
      <dt>'$f$ @ $x$'
      <dd>is equivalent to '$f$[$x$]'.
    </dl>

    >> a @ b
     = a[b]
    >> a @ b @ c
     = a[b[c]]
    >> Format[p[x_]] := Prefix[{x}, "*"]
    >> p[3]
     = *3
    >> Format[q[x_]] := Prefix[{x}, "~", 350]
    >> q[a+b]
     = ~(a + b)
    >> q[a*b]
     = ~a b
    >> q[a]+b
     = b + ~a

    The prefix operator '@' is parsed to an expression before evaluation:
    >> Hold[a @ b @ c @ d @ e @ f @ x]
     = Hold[a[b[c[d[e[f[x]]]]]]]
    """

    grouping = "Right"
    operator_display = None
    summary_text = "prefix form"

    def eval_makeboxes_prefix(self, expr, h, precedence, form, evaluation):
        """MakeBoxes[Prefix[expr_, h_, precedence_:None],
        form:StandardForm|TraditionalForm]"""
        return eval_postprefix(
            self, SymbolPrefix, expr, h, precedence, form, evaluation
        )


class Right(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Right.html</url>

    <dl>
      <dt>'Right'
      <dd>is used with operator formatting constructs to specify a right-associative operator.
    </dl>
    """

    summary_text = "right alignment/right associative"


class Row(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Row.html</url>

    <dl>
      <dt>'Row'[{$expr$, ...}]
      <dd>formats several expressions inside a 'RowBox'.
    </dl>
    """

    summary_text = "1D layouts containing arbitrary objects in a row"

    def eval_makeboxes(self, items, sep, form, evaluation: Evaluation):
        """MakeBoxes[Row[{items___}, sep_:""],
        form:StandardForm|TraditionalForm]"""

        items = items.get_sequence()
        if not isinstance(sep, String):
            sep = MakeBoxes(sep, form)
        if len(items) == 1:
            return MakeBoxes(items[0], form)
        else:
            result = []
            for index, item in enumerate(items):
                if index > 0 and not sep.sameQ(String("")):
                    result.append(to_boxes(sep, evaluation))
                item = MakeBoxes(item, form).evaluate(evaluation)
                item = to_boxes(item, evaluation)
                result.append(item)
            return RowBox(*result)


class Style(Builtin):
    """
    <url>:WMA link:https://reference.wolfram.com/language/ref/Style.html</url>

    <dl>
      <dt>'Style'[$expr$, options]
      <dd>displays $expr$ formatted using the specified option settings.

      <dt>'Style'[$expr$, "style"]
      <dd> uses the option settings for the specified style in the current notebook.

      <dt>'Style'[$expr$, $color$]
      <dd>displays using the specified color.

      <dt>'Style'[$expr$, $Bold$]
      <dd>displays with fonts made bold.

      <dt>'Style'[$expr$, $Italic$]
      <dd>displays with fonts made italic.

      <dt>'Style'[$expr$, $Underlined$]
      <dd>displays with fonts underlined.

      <dt>'Style[$expr$, $Larger$]
      <dd>displays with fonts made larger.

      <dt>'Style'[$expr$, $Smaller$]
      <dd>displays with fonts made smaller.

      <dt>'Style'[$expr$, $n$]
      <dd>displays with font size n.

      <dt>'Style'[$expr$, $Tiny$]
      <dt>'Style'[$expr$, $Small$], etc.
      <dd>display with fonts that are tiny, small, etc.
    </dl>
    """

    summary_text = "wrapper for styles and style options to apply"
    options = {
        "ImageSizeMultipliers": "Automatic",
        "$OptionSyntax": "Ignore",
    }
    rules = {
        "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": (
            "StyleBox[MakeBoxes[expr, f], "
            "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]"
        ),
    }
    rules = {
        "MakeBoxes[Style[expr_, OptionsPattern[Style]], f_]": (
            "StyleBox[MakeBoxes[expr, f], "
            "ImageSizeMultipliers -> OptionValue[ImageSizeMultipliers]]"
        )
    }


class Subscript(Builtin):
    """
    <url>
    :WMA link:
    https://reference.wolfram.com/language/ref/Subscript.html</url>

    <dl>
      <dt>'Subscript'[$a$, $i$]
      <dd>displays as $a_i$.
    </dl>

    >> Subscript[x, 1, 2, 3] // TeXForm
     = x_{1, 2, 3}
    """

    summary_text = "format an expression with a subscript"

    def eval_makeboxes(self, x, y, f, evaluation) -> Expression:
        "MakeBoxes[Subscript[x_, y__], f:StandardForm|TraditionalForm]"

        y = y.get_sequence()
        return Expression(
            SymbolSubscriptBox,
            Expression(SymbolMakeBoxes, x, f),
            *list_boxes(y, f, evaluation),
        )


class Subsuperscript(Builtin):
    """
    <url>
    :WMA link:
    https://reference.wolfram.com/language/ref/Subsuperscript.html</url>

    <dl>
      <dt>'Subsuperscript'[$a$, $b$, $c$]
      <dd>displays as $a_b^c$.
    </dl>

    >> Subsuperscript[a, b, c] // TeXForm
     = a_b^c
    """

    rules = {
        "MakeBoxes[Subsuperscript[x_, y_, z_], "
        "f:StandardForm|TraditionalForm]": (
            "SubsuperscriptBox[MakeBoxes[x, f], MakeBoxes[y, f], " "MakeBoxes[z, f]]"
        )
    }
    summary_text = "format an expression with a subscript and a superscript"


class Superscript(Builtin):
    """
    <url>
    :WMA link:
    https://reference.wolfram.com/language/ref/Superscript.html</url>

    <dl>
      <dt>'Superscript'[$x$, $y$]
      <dd>displays as $x$^$y$.
    </dl>

    >> Superscript[x,3] // TeXForm
     = x^3
    """

    summary_text = "format an expression with a superscript"
    rules = {
        "MakeBoxes[Superscript[x_, y_], f:StandardForm|TraditionalForm]": (
            "SuperscriptBox[MakeBoxes[x, f], MakeBoxes[y, f]]"
        )
    }
