# Source code for ufl_legacy.formatting.ufl2unicode

    # coding: utf-8

    import numbers

    import ufl_legacy as ufl
    from ufl_legacy.log import error
    from ufl_legacy.corealg.multifunction import MultiFunction
    from ufl_legacy.corealg.map_dag import map_expr_dag
    from ufl_legacy.core.multiindex import Index, FixedIndex
    from ufl_legacy.form import Form
    from ufl_legacy.algorithms import compute_form_data

    class PrecedenceRules(MultiFunction):
        "An enum-like class for C operator precedence levels."

        def __init__(self):
            MultiFunction.__init__(self)

        def highest(self, o):
            return 0

        terminal = highest
        list_tensor = highest
        component_tensor = highest

        def restricted(self, o):
            return 5

        cell_avg = restricted
        facet_avg = restricted

        def call(self, o):
            return 10

        indexed = call
        min_value = call
        max_value = call
        math_function = call
        bessel_function = call

        def power(self, o):
            return 12

        def mathop(self, o):
            return 15

        derivative = mathop
        trace = mathop
        deviatoric = mathop
        cofactor = mathop
        skew = mathop
        sym = mathop

        def not_condition(self, o):
            return 20

        def product(self, o):
            return 30

        division = product
        # mod = product
        dot = product
        inner = product
        outer = product
        cross = product

        def add(self, o):
            return 40

        # sub = add
        index_sum = add

        def lt(self, o):
            return 50

        le = lt
        gt = lt
        ge = lt

        def eq(self, o):
            return 60

        ne = eq

        def and_condition(self, o):
            return 70

        def or_condition(self, o):
            return 71

        def conditional(self, o):
            return 72

        def lowest(self, o):
            return 80

        operator = lowest

    _precrules = PrecedenceRules()

    def precedence(expr):
        return _precrules(expr)

    try:
        import colorama
        has_colorama = True
    except ImportError:
        has_colorama = False

    class UC:
        "An enum-like class for unicode characters."

        # Letters in this alphabet have contiguous code point numbers
        bold_math_a = u"𝐚"
        bold_math_A = u"𝐀"

        thin_space = u"\u2009"

        superscript_plus = u'⁺'
        superscript_minus = u'⁻'
        superscript_equals = u'⁼'
        superscript_left_paren = u'⁽'
        superscript_right_paren = u'⁾'
        superscript_digits = ["⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"]

        subscript_plus = u'₊'
        subscript_minus = u'₋'
        subscript_equals = u'₌'
        subscript_left_paren = u'₍'
        subscript_right_paren = u'₎'
        subscript_digits = ["₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"]

        sqrt = u'√'
        transpose = u'ᵀ'

        integral = u'∫'
        integral_double = u'∬'
        integral_triple = u'∭'
        integral_contour = u'∮'
        integral_surface = u'∯'
        integral_volume = u'∰'

        sum = u'∑'
        division_slash = '∕'
        partial = u'∂'
        epsilon = u'ε'
        omega = u'ω'
        Omega = u'Ω'
        gamma = u'γ'
        Gamma = u'Γ'
        nabla = u'∇'
        for_all = u'∀'

        dot = u'⋅'
        cross_product = u'⨯'
        circled_times = u'⊗'
        nary_product = u'∏'

        ne = u'≠'
        lt = u'<'
        le = u'≤'
        gt = u'>'
        ge = u'≥'

        logical_and = u'∧'
        logical_or = u'∨'
        logical_not = u'¬'

        element_of = u'∈'
        not_element_of = u'∉'

        left_white_square_bracket = u'⟦'
        right_white_squared_bracket = u'⟧'
        left_angled_bracket = u'⟨'
        right_angled_bracket = u'⟩'
        left_double_angled_bracket = u'⟪'
        right_double_angled_bracket = u'⟫'

        combining_right_arrow_above = '\u20D7'
        combining_overline = '\u0305'

    def bolden_letter(c):
        if ord("A") <= ord(c) <= ord("Z"):
            c = chr(ord(c) - ord(u"A") + ord(UC.bold_math_A))
        elif ord("a") <= ord(c) <= ord("z"):
            c = chr(ord(c) - ord(u"a") + ord(UC.bold_math_a))
        return c

    def superscript_digit(digit):
        return UC.superscript_digits[ord(digit) - ord("0")]

    def subscript_digit(digit):
        return UC.subscript_digits[ord(digit) - ord("0")]

    def bolden_string(s):
        return u"".join(bolden_letter(c) for c in s)

    def overline_string(f):
        return u"".join("%s%s" % (c, UC.combining_overline) for c in f)

    def subscript_number(number):
        assert isinstance(number, int)
        prefix = UC.subscript_minus if number < 0 else ''
        number = str(number)
        return prefix + ''.join(subscript_digit(c) for c in str(number))

    def superscript_number(number):
        assert isinstance(number, int)
        prefix = UC.superscript_minus if number < 0 else ''
        number = str(number)
        return prefix + ''.join(superscript_digit(c) for c in str(number))

    def opfont(opname):
        return bolden_string(opname)

    def measure_font(dx):
        return bolden_string(dx)

    integral_by_dim = {
        3: UC.integral_triple,
        2: UC.integral_double,
        1: UC.integral,
    }

    integral_type_to_codim = {
        "cell": 0,
        "exterior_facet": 1,
        "interior_facet": 1,
        "vertex": "tdim",
        "point": "tdim",
        "custom": 0,
        "overlap": 0,
        "interface": 1,
        "cutcell": 0,
    }

    integral_symbols = {
        "cell": UC.integral_volume,
        "exterior_facet": UC.integral_surface,
        "interior_facet": UC.integral_surface,
        "vertex": UC.integral,
        "point": UC.integral,
        "custom": UC.integral,
        "overlap": UC.integral,
        "interface": UC.integral,
        "cutcell": UC.integral,
    }

    integral_postfixes = {
        "cell": "",
        "exterior_facet": "ext",
        "interior_facet": "int",
        "vertex": "vertex",
        "point": "point",
        "custom": "custom",
        "overlap": "overlap",
        "interface": "interface",
        "cutcell": "cutcell",
    }

    def get_integral_symbol(integral_type, domain, subdomain_id):
        tdim = domain.topological_dimension()
        codim = integral_type_to_codim[integral_type]
        itgdim = tdim - codim

        # ipost = integral_postfixes[integral_type]
        istr = integral_by_dim[itgdim]

        # TODO: Render domain description

        if isinstance(subdomain_id, numbers.Integral):
            istr += subscript_number(int(subdomain_id))
        elif subdomain_id == "everywhere":
            pass
        elif subdomain_id == "otherwise":
            istr += "[rest of domain]"
        elif isinstance(subdomain_id, tuple):
            istr += ",".join([subscript_number(int(i)) for i in subdomain_id])

        dxstr = ufl.measure.integral_type_to_measure_name[integral_type]
        dxstr = measure_font(dxstr)

        return istr, dxstr

    def par(s):
        return "(%s)" % s

    def prec(expr):
        return 0  # FIXME

        # return precedence[expr._ufl_class_]

    def is_int(s):
        try:
            int(s)
            return True
        except ValueError:
            return False

    def format_index(ii):
        if isinstance(ii, FixedIndex):
            s = "%d" % ii._value
        elif isinstance(ii, Index):
            s = "i%s" % subscript_number(ii._count)
        else:
            error("Invalid index type %s." % type(ii))
        return s

    def ufl2unicode(expression):
        "Generate Unicode string for a UFL expression or form."
        if isinstance(expression, Form):
            form_data = compute_form_data(expression)
            preprocessed_form = form_data.preprocessed_form
            return form2unicode(preprocessed_form, form_data)
        else:
            return expression2unicode(expression)

    def expression2unicode(expression, argument_names=None, coefficient_names=None):
        rules = Expression2UnicodeHandler(argument_names, coefficient_names)
        return map_expr_dag(rules, expression)

    def form2unicode(form, formdata):
        # formname = formdata.name
        argument_names = None
        coefficient_names = None

        # Define form as sum of integrals
        lines = []
        integrals = form.integrals()
        for itg in integrals:
            integrand_string = expression2unicode(
                itg.integrand(), argument_names, coefficient_names)

            istr, dxstr = get_integral_symbol(itg.integral_type(), itg.ufl_domain(), itg.subdomain_id())

            line = "%s %s %s" % (istr, integrand_string, dxstr)
            lines.append(line)

        return '\n  + '.join(lines)

    def binop(expr, a, b, op, sep=" "):
        eprec = precedence(expr)
        op0, op1 = expr.ufl_operands
        aprec = precedence(op0)
        bprec = precedence(op1)
        # Assuming left-to-right evaluation, therefore >= and > here:
        if aprec >= eprec:
            a = par(a)
        if bprec > eprec:
            b = par(b)
        return sep.join((a, op, b))

    def mathop(expr, arg, opname):
        eprec = precedence(expr)
        aprec = precedence(expr.ufl_operands[0])
        op = opfont(opname)
        if aprec > eprec:
            arg = par(arg)
            sep = ""
        else:
            sep = UC.thin_space
        return "%s%s%s" % (op, sep, arg)

    class Expression2UnicodeHandler(MultiFunction):
        def __init__(self, argument_names=None, coefficient_names=None, colorama_bold=False):
            MultiFunction.__init__(self)
            self.argument_names = argument_names
            self.coefficient_names = coefficient_names
            self.colorama_bold = colorama_bold and has_colorama

        # --- Terminal objects ---

        def scalar_value(self, o):
            if o.ufl_shape and self.colorama_bold:
                return "%s%s%s" % (colorama.Style.BRIGHT, o._value, colorama.Style.RESET_ALL)
            return "%s" % o._value

        def zero(self, o):
            if o.ufl_shape and self.colorama_bold:
                if len(o.ufl_shape) == 1:
                    return "0%s" % UC.combining_right_arrow_above
                return "%s0%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL)
            return "0"

        def identity(self, o):
            if self.colorama_bold:
                return "%sI%s" % (colorama.Style.BRIGHT, colorama.Style.RESET_ALL)
            return "I"

        def permutation_symbol(self, o):
            if self.colorama_bold:
                return "%s%s%s" % (colorama.Style.BRIGHT, UC.epsilon, colorama.Style.RESET_ALL)
            return UC.epsilon

        def facet_normal(self, o):
            return "n%s" % UC.combining_right_arrow_above

        def spatial_coordinate(self, o):
            return "x%s" % UC.combining_right_arrow_above

        def argument(self, o):
            # Using ^ for argument numbering and _ for indexing since
            # indexing is more common than exponentiation
            if self.argument_names is None:
                i = o.number()
                bfn = "v" if i == 0 else "u"
                if not o.ufl_shape:
                    return bfn
                elif len(o.ufl_shape) == 1:
                    return "%s%s" % (bfn, UC.combining_right_arrow_above)
                elif self.colorama_bold:
                    return "%s%s%s" % (colorama.Style.BRIGHT, bfn, colorama.Style.RESET_ALL)
                else:
                    return bfn
            return self.argument_names[(o.number(), o.part())]

        def coefficient(self, o):
            # Using ^ for coefficient numbering and _ for indexing since
            # indexing is more common than exponentiation
            if self.coefficient_names is None:
                i = o.count()
                var = "w"
                if len(o.ufl_shape) == 1:
                    var += UC.combining_right_arrow_above
                elif len(o.ufl_shape) > 1 and self.colorama_bold:
                    var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL)
                return "%s%s" % (var, subscript_number(i))
            return self.coefficient_names[o.count()]

        def constant(self, o):
            i = o.count()
            var = "c"
            if len(o.ufl_shape) == 1:
                var += UC.combining_right_arrow_above
            elif len(o.ufl_shape) > 1 and self.colorama_bold:
                var = "%s%s%s" % (colorama.Style.BRIGHT, var, colorama.Style.RESET_ALL)
            return "%s%s" % (var, superscript_number(i))

        def multi_index(self, o):
            return ",".join(format_index(i) for i in o)

        def label(self, o):
            return "l%s" % (subscript_number(o.count()),)

        # --- Non-terminal objects ---

        def variable(self, o, f, l):
            return "var(%s,%s)" % (f, l)

        def index_sum(self, o, f, i):
            if 1:  # prec(o.ufl_operands[0]) >? prec(o):
                f = par(f)
            return "%s[%s]%s" % (UC.sum, i, f)

        def sum(self, o, a, b):
            return binop(o, a, b, "+")

        def product(self, o, a, b):
            return binop(o, a, b, " ", sep="")

        def division(self, o, a, b):
            if is_int(b):
                b = subscript_number(int(b))
                if is_int(a):
                    # Return as a fraction
                    # NOTE: Maybe consider using fractional slash
                    #  with normal numbers if terminals can handle it
                    a = superscript_number(int(a))
                else:
                    a = par(a)
                return "%s %s %s" % (a, UC.division_slash, b)
            return binop(o, a, b, UC.division_slash)

        def abs(self, o, a):
            return "|%s|" % (a,)

        def transposed(self, o, a):
            a = par(a)
            return "%s%s" % (a, UC.transpose)

        def indexed(self, o, A, ii):
            op0, op1 = o.ufl_operands
            Aprec = precedence(op0)
            oprec = precedence(o)
            if Aprec > oprec:
                A = par(A)
            return "%s[%s]" % (A, ii)

        def variable_derivative(self, o, f, v):
            f = par(f)
            v = par(v)
            nom = r"%s%s" % (UC.partial, f)
            denom = r"%s%s" % (UC.partial, v)
            return par(r"%s%s%s" % (nom, UC.division_slash, denom))

        def coefficient_derivative(self, o, f, w, v, cd):
            f = par(f)
            w = par(w)
            nom = r"%s%s" % (UC.partial, f)
            denom = r"%s%s" % (UC.partial, w)
            return par(r"%s%s%s[%s]" % (nom, UC.division_slash, denom, v))  # TODO: Fix this syntax...

        def grad(self, o, f):
            return mathop(o, f, "grad")

        def div(self, o, f):
            return mathop(o, f, "div")

        def nabla_grad(self, o, f):
            oprec = precedence(o)
            fprec = precedence(o.ufl_operands[0])
            if fprec > oprec:
                f = par(f)
            return "%s%s%s" % (UC.nabla, UC.thin_space, f)

        def nabla_div(self, o, f):
            oprec = precedence(o)
            fprec = precedence(o.ufl_operands[0])
            if fprec > oprec:
                f = par(f)
            return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.dot, UC.thin_space, f)

        def curl(self, o, f):
            oprec = precedence(o)
            fprec = precedence(o.ufl_operands[0])
            if fprec > oprec:
                f = par(f)
            return "%s%s%s%s%s" % (UC.nabla, UC.thin_space, UC.cross_product, UC.thin_space, f)

        def math_function(self, o, f):
            op = opfont(o._name)
            f = par(f)
            return "%s%s" % (op, f)

        def sqrt(self, o, f):
            f = par(f)
            return "%s%s" % (UC.sqrt, f)

        def exp(self, o, f):
            op = opfont("exp")
            f = par(f)
            return "%s%s" % (op, f)

        def atan2(self, o, f1, f2):
            f1 = par(f1)
            f2 = par(f2)
            op = opfont("arctan2")
            return "%s(%s, %s)" % (op, f1, f2)

        def bessel_j(self, o, nu, f):
            op = opfont("J")
            f = par(f)
            nu = subscript_number(int(nu))
            return "%s%s%s" % (op, nu, f)

        def bessel_y(self, o, nu, f):
            op = opfont("Y")
            f = par(f)
            nu = subscript_number(int(nu))
            return "%s%s%s" % (op, nu, f)

        def bessel_i(self, o, nu, f):
            op = opfont("I")
            f = par(f)
            nu = subscript_number(int(nu))
            return "%s%s%s" % (op, nu, f)

        def bessel_K(self, o, nu, f):
            op = opfont("K")
            f = par(f)
            nu = subscript_number(int(nu))
            return "%s%s%s" % (op, nu, f)

        def power(self, o, a, b):
            if is_int(b):
                b = superscript_number(int(b))
                return binop(o, a, b, "", sep="")
            return binop(o, a, b, "^", sep="")

        def outer(self, o, a, b):
            return binop(o, a, b, UC.circled_times)

        def inner(self, o, a, b):
            return "%s%s, %s%s" % (UC.left_angled_bracket, a, b, UC.right_angled_bracket)

        def dot(self, o, a, b):
            return binop(o, a, b, UC.dot)

        def cross(self, o, a, b):
            return binop(o, a, b, UC.cross_product)

        def determinant(self, o, A):
            return "|%s|" % (A,)

        def inverse(self, o, A):
            A = par(A)
            return "%s%s" % (A, superscript_number(-1))

        def trace(self, o, A):
            return mathop(o, A, "tr")

        def deviatoric(self, o, A):
            return mathop(o, A, "dev")

        def cofactor(self, o, A):
            return mathop(o, A, "cofac")

        def skew(self, o, A):
            return mathop(o, A, "skew")

        def sym(self, o, A):
            return mathop(o, A, "sym")

        def conj(self, o, a):
            # Overbar is already taken for average, and there is no superscript asterix in unicode.
            return mathop(o, a, "conj")

        def real(self, o, a):
            return mathop(o, a, "Re")

        def imag(self, o, a):
            return mathop(o, a, "Im")

        def list_tensor(self, o, *ops):
            l = ", ".join(ops)  # noqa: E741
            return "%s%s%s" % ("[", l, "]")

        def component_tensor(self, o, A, ii):
            return "[%s %s %s]" % (A, UC.for_all, ii)

        def positive_restricted(self, o, f):
            f = par(f)
            return "%s%s" % (f, UC.superscript_plus)

        def negative_restricted(self, o, f):
            f = par(f)
            return "%s%s" % (f, UC.superscript_minus)

        def cell_avg(self, o, f):
            f = overline_string(f)
            return f

        def facet_avg(self, o, f):
            f = overline_string(f)
            return f

        def eq(self, o, a, b):
            return binop(o, a, b, "=")

        def ne(self, o, a, b):
            return binop(o, a, b, UC.ne)

        def le(self, o, a, b):
            return binop(o, a, b, UC.le)

        def ge(self, o, a, b):
            return binop(o, a, b, UC.ge)

        def lt(self, o, a, b):
            return binop(o, a, b, UC.lt)

        def gt(self, o, a, b):
            return binop(o, a, b, UC.gt)

        def and_condition(self, o, a, b):
            return binop(o, a, b, UC.logical_and)

        def or_condition(self, o, a, b):
            return binop(o, a, b, UC.logical_or)

        def not_condition(self, o, a):
            a = par(a)
            return "%s%s" % (UC.logical_not, a)

        def conditional(self, o, c, t, f):
            c = par(c)
            t = par(t)
            f = par(t)
            If = opfont("if")
            Else = opfont("else")
            l = " ".join((t, If, c, Else, f))  # noqa: E741
            return l

        def min_value(self, o, a, b):
            op = opfont("min")
            return "%s(%s, %s)" % (op, a, b)

        def max_value(self, o, a, b):
            op = opfont("max")
            return "%s(%s, %s)" % (op, a, b)

        def expr_list(self, o, *ops):
            items = ", ".join(ops)
            return "%s %s %s" % (UC.left_white_square_bracket, items,
                                 UC.right_white_squared_bracket)

        def expr_mapping(self, o, *ops):
            items = ", ".join(ops)
            return "%s %s %s" % (UC.left_double_angled_bracket, items,
                                 UC.left_double_angled_bracket)

        def expr(self, o):
            raise ValueError("Missing handler for type %s" % str(type(o)))