import itertools

from sympy.core import S
from sympy.core.add import Add
from sympy.core.containers import Tuple
from sympy.core.function import Function
from sympy.core.mul import Mul
from sympy.core.numbers import Number, Rational
from sympy.core.power import Pow
from sympy.core.sorting import default_sort_key
from sympy.core.symbol import Symbol
from sympy.core.sympify import SympifyError
from sympy.printing.conventions import requires_partial
from sympy.printing.precedence import PRECEDENCE, precedence, precedence_traditional
from sympy.printing.printer import Printer, print_function
from sympy.printing.str import sstr
from sympy.utilities.iterables import has_variety
from sympy.utilities.exceptions import sympy_deprecation_warning

from sympy.printing.pretty.stringpict import prettyForm, stringPict
from sympy.printing.pretty.pretty_symbology import hobj, vobj, xobj, \
    xsym, pretty_symbol, pretty_atom, pretty_use_unicode, greek_unicode, U, \
    pretty_try_use_unicode, annotated, is_subscriptable_in_unicode, center_pad,  root as nth_root

# rename for usage from outside
pprint_use_unicode = pretty_use_unicode
pprint_try_use_unicode = pretty_try_use_unicode


class PrettyPrinter(Printer):
    """Printer, which converts an expression into 2D ASCII-art figure."""
    printmethod = "_pretty"

    _default_settings = {
        "order": None,
        "full_prec": "auto",
        "use_unicode": None,
        "wrap_line": True,
        "num_columns": None,
        "use_unicode_sqrt_char": True,
        "root_notation": True,
        "mat_symbol_style": "plain",
        "imaginary_unit": "i",
        "perm_cyclic": True
    }

    def __init__(self, settings=None):
        Printer.__init__(self, settings)

        if not isinstance(self._settings['imaginary_unit'], str):
            raise TypeError("'imaginary_unit' must a string, not {}".format(self._settings['imaginary_unit']))
        elif self._settings['imaginary_unit'] not in ("i", "j"):
            raise ValueError("'imaginary_unit' must be either 'i' or 'j', not '{}'".format(self._settings['imaginary_unit']))

    def emptyPrinter(self, expr):
        return prettyForm(str(expr))

    @property
    def _use_unicode(self):
        if self._settings['use_unicode']:
            return True
        else:
            return pretty_use_unicode()

    def doprint(self, expr):
        return self._print(expr).render(**self._settings)

    # empty op so _print(stringPict) returns the same
    def _print_stringPict(self, e):
        return e

    def _print_basestring(self, e):
        return prettyForm(e)

    def _print_atan2(self, e):
        pform = prettyForm(*self._print_seq(e.args).parens())
        pform = prettyForm(*pform.left('atan2'))
        return pform

    def _print_Symbol(self, e, bold_name=False):
        symb = pretty_symbol(e.name, bold_name)
        return prettyForm(symb)
    _print_RandomSymbol = _print_Symbol
    def _print_MatrixSymbol(self, e):
        return self._print_Symbol(e, self._settings['mat_symbol_style'] == "bold")

    def _print_Float(self, e):
        # we will use StrPrinter's Float printer, but we need to handle the
        # full_prec ourselves, according to the self._print_level
        full_prec = self._settings["full_prec"]
        if full_prec == "auto":
            full_prec = self._print_level == 1
        return prettyForm(sstr(e, full_prec=full_prec))

    def _print_Cross(self, e):
        vec1 = e._expr1
        vec2 = e._expr2
        pform = self._print(vec2)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('MULTIPLICATION SIGN'))))
        pform = prettyForm(*pform.left(')'))
        pform = prettyForm(*pform.left(self._print(vec1)))
        pform = prettyForm(*pform.left('('))
        return pform

    def _print_Curl(self, e):
        vec = e._expr
        pform = self._print(vec)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('MULTIPLICATION SIGN'))))
        pform = prettyForm(*pform.left(self._print(U('NABLA'))))
        return pform

    def _print_Divergence(self, e):
        vec = e._expr
        pform = self._print(vec)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('DOT OPERATOR'))))
        pform = prettyForm(*pform.left(self._print(U('NABLA'))))
        return pform

    def _print_Dot(self, e):
        vec1 = e._expr1
        vec2 = e._expr2
        pform = self._print(vec2)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('DOT OPERATOR'))))
        pform = prettyForm(*pform.left(')'))
        pform = prettyForm(*pform.left(self._print(vec1)))
        pform = prettyForm(*pform.left('('))
        return pform

    def _print_Gradient(self, e):
        func = e._expr
        pform = self._print(func)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('NABLA'))))
        return pform

    def _print_Laplacian(self, e):
        func = e._expr
        pform = self._print(func)
        pform = prettyForm(*pform.left('('))
        pform = prettyForm(*pform.right(')'))
        pform = prettyForm(*pform.left(self._print(U('INCREMENT'))))
        return pform

    def _print_Atom(self, e):
        try:
            # print atoms like Exp1 or Pi
            return prettyForm(pretty_atom(e.__class__.__name__, printer=self))
        except KeyError:
            return self.emptyPrinter(e)

    # Infinity inherits from Number, so we have to override _print_XXX order
    _print_Infinity = _print_Atom
    _print_NegativeInfinity = _print_Atom
    _print_EmptySet = _print_Atom
    _print_Naturals = _print_Atom
    _print_Naturals0 = _print_Atom
    _print_Integers = _print_Atom
    _print_Rationals = _print_Atom
    _print_Complexes = _print_Atom

    _print_EmptySequence = _print_Atom

    def _print_Reals(self, e):
        if self._use_unicode:
            return self._print_Atom(e)
        else:
            inf_list = ['-oo', 'oo']
            return self._print_seq(inf_list, '(', ')')

    def _print_subfactorial(self, e):
        x = e.args[0]
        pform = self._print(x)
        # Add parentheses if needed
        if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol):
            pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left('!'))
        return pform

    def _print_factorial(self, e):
        x = e.args[0]
        pform = self._print(x)
        # Add parentheses if needed
        if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol):
            pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.right('!'))
        return pform

    def _print_factorial2(self, e):
        x = e.args[0]
        pform = self._print(x)
        # Add parentheses if needed
        if not ((x.is_Integer and x.is_nonnegative) or x.is_Symbol):
            pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.right('!!'))
        return pform

    def _print_binomial(self, e):
        n, k = e.args

        n_pform = self._print(n)
        k_pform = self._print(k)

        bar = ' '*max(n_pform.width(), k_pform.width())

        pform = prettyForm(*k_pform.above(bar))
        pform = prettyForm(*pform.above(n_pform))
        pform = prettyForm(*pform.parens('(', ')'))

        pform.baseline = (pform.baseline + 1)//2

        return pform

    def _print_Relational(self, e):
        op = prettyForm(' ' + xsym(e.rel_op) + ' ')

        l = self._print(e.lhs)
        r = self._print(e.rhs)
        pform = prettyForm(*stringPict.next(l, op, r), binding=prettyForm.OPEN)
        return pform

    def _print_Not(self, e):
        from sympy.logic.boolalg import (Equivalent, Implies)
        if self._use_unicode:
            arg = e.args[0]
            pform = self._print(arg)
            if isinstance(arg, Equivalent):
                return self._print_Equivalent(arg, altchar=pretty_atom('NotEquiv'))
            if isinstance(arg, Implies):
                return self._print_Implies(arg, altchar=pretty_atom('NotArrow'))

            if arg.is_Boolean and not arg.is_Not:
                pform = prettyForm(*pform.parens())

            return prettyForm(*pform.left(pretty_atom('Not')))
        else:
            return self._print_Function(e)

    def __print_Boolean(self, e, char, sort=True):
        args = e.args
        if sort:
            args = sorted(e.args, key=default_sort_key)
        arg = args[0]
        pform = self._print(arg)

        if arg.is_Boolean and not arg.is_Not:
            pform = prettyForm(*pform.parens())

        for arg in args[1:]:
            pform_arg = self._print(arg)

            if arg.is_Boolean and not arg.is_Not:
                pform_arg = prettyForm(*pform_arg.parens())

            pform = prettyForm(*pform.right(' %s ' % char))
            pform = prettyForm(*pform.right(pform_arg))

        return pform

    def _print_And(self, e):
        if self._use_unicode:
            return self.__print_Boolean(e, pretty_atom('And'))
        else:
            return self._print_Function(e, sort=True)

    def _print_Or(self, e):
        if self._use_unicode:
            return self.__print_Boolean(e, pretty_atom('Or'))
        else:
            return self._print_Function(e, sort=True)

    def _print_Xor(self, e):
        if self._use_unicode:
            return self.__print_Boolean(e, pretty_atom("Xor"))
        else:
            return self._print_Function(e, sort=True)

    def _print_Nand(self, e):
        if self._use_unicode:
            return self.__print_Boolean(e, pretty_atom('Nand'))
        else:
            return self._print_Function(e, sort=True)

    def _print_Nor(self, e):
        if self._use_unicode:
            return self.__print_Boolean(e, pretty_atom('Nor'))
        else:
            return self._print_Function(e, sort=True)

    def _print_Implies(self, e, altchar=None):
        if self._use_unicode:
            return self.__print_Boolean(e, altchar or pretty_atom('Arrow'), sort=False)
        else:
            return self._print_Function(e)

    def _print_Equivalent(self, e, altchar=None):
        if self._use_unicode:
            return self.__print_Boolean(e, altchar or pretty_atom('Equiv'))
        else:
            return self._print_Function(e, sort=True)

    def _print_conjugate(self, e):
        pform = self._print(e.args[0])
        return prettyForm( *pform.above( hobj('_', pform.width())) )

    def _print_Abs(self, e):
        pform = self._print(e.args[0])
        pform = prettyForm(*pform.parens('|', '|'))
        return pform

    def _print_floor(self, e):
        if self._use_unicode:
            pform = self._print(e.args[0])
            pform = prettyForm(*pform.parens('lfloor', 'rfloor'))
            return pform
        else:
            return self._print_Function(e)

    def _print_ceiling(self, e):
        if self._use_unicode:
            pform = self._print(e.args[0])
            pform = prettyForm(*pform.parens('lceil', 'rceil'))
            return pform
        else:
            return self._print_Function(e)

    def _print_Derivative(self, deriv):
        if requires_partial(deriv.expr) and self._use_unicode:
            deriv_symbol = U('PARTIAL DIFFERENTIAL')
        else:
            deriv_symbol = r'd'
        x = None
        count_total_deriv = 0

        for sym, num in reversed(deriv.variable_count):
            s = self._print(sym)
            ds = prettyForm(*s.left(deriv_symbol))
            count_total_deriv += num

            if (not num.is_Integer) or (num > 1):
                ds = ds**prettyForm(str(num))

            if x is None:
                x = ds
            else:
                x = prettyForm(*x.right(' '))
                x = prettyForm(*x.right(ds))

        f = prettyForm(
            binding=prettyForm.FUNC, *self._print(deriv.expr).parens())

        pform = prettyForm(deriv_symbol)

        if (count_total_deriv > 1) != False:
            pform = pform**prettyForm(str(count_total_deriv))

        pform = prettyForm(*pform.below(stringPict.LINE, x))
        pform.baseline = pform.baseline + 1
        pform = prettyForm(*stringPict.next(pform, f))
        pform.binding = prettyForm.MUL

        return pform

    def _print_Cycle(self, dc):
        from sympy.combinatorics.permutations import Permutation, Cycle
        # for Empty Cycle
        if dc == Cycle():
            cyc = stringPict('')
            return prettyForm(*cyc.parens())

        dc_list = Permutation(dc.list()).cyclic_form
        # for Identity Cycle
        if dc_list == []:
            cyc = self._print(dc.size - 1)
            return prettyForm(*cyc.parens())

        cyc = stringPict('')
        for i in dc_list:
            l = self._print(str(tuple(i)).replace(',', ''))
            cyc = prettyForm(*cyc.right(l))
        return cyc

    def _print_Permutation(self, expr):
        from sympy.combinatorics.permutations import Permutation, Cycle

        perm_cyclic = Permutation.print_cyclic
        if perm_cyclic is not None:
            sympy_deprecation_warning(
                f"""
                Setting Permutation.print_cyclic is deprecated. Instead use
                init_printing(perm_cyclic={perm_cyclic}).
                """,
                deprecated_since_version="1.6",
                active_deprecations_target="deprecated-permutation-print_cyclic",
                stacklevel=7,
            )
        else:
            perm_cyclic = self._settings.get("perm_cyclic", True)

        if perm_cyclic:
            return self._print_Cycle(Cycle(expr))

        lower = expr.array_form
        upper = list(range(len(lower)))

        result = stringPict('')
        first = True
        for u, l in zip(upper, lower):
            s1 = self._print(u)
            s2 = self._print(l)
            col = prettyForm(*s1.below(s2))
            if first:
                first = False
            else:
                col = prettyForm(*col.left(" "))
            result = prettyForm(*result.right(col))
        return prettyForm(*result.parens())


    def _print_Integral(self, integral):
        f = integral.function

        # Add parentheses if arg involves addition of terms and
        # create a pretty form for the argument
        prettyF = self._print(f)
        # XXX generalize parens
        if f.is_Add:
            prettyF = prettyForm(*prettyF.parens())

        # dx dy dz ...
        arg = prettyF
        for x in integral.limits:
            prettyArg = self._print(x[0])
            # XXX qparens (parens if needs-parens)
            if prettyArg.width() > 1:
                prettyArg = prettyForm(*prettyArg.parens())

            arg = prettyForm(*arg.right(' d', prettyArg))

        # \int \int \int ...
        firstterm = True
        s = None
        for lim in integral.limits:
            # Create bar based on the height of the argument
            h = arg.height()
            H = h + 2

            # XXX hack!
            ascii_mode = not self._use_unicode
            if ascii_mode:
                H += 2

            vint = vobj('int', H)

            # Construct the pretty form with the integral sign and the argument
            pform = prettyForm(vint)
            pform.baseline = arg.baseline + (
                H - h)//2    # covering the whole argument

            if len(lim) > 1:
                # Create pretty forms for endpoints, if definite integral.
                # Do not print empty endpoints.
                if len(lim) == 2:
                    prettyA = prettyForm("")
                    prettyB = self._print(lim[1])
                if len(lim) == 3:
                    prettyA = self._print(lim[1])
                    prettyB = self._print(lim[2])

                if ascii_mode:  # XXX hack
                    # Add spacing so that endpoint can more easily be
                    # identified with the correct integral sign
                    spc = max(1, 3 - prettyB.width())
                    prettyB = prettyForm(*prettyB.left(' ' * spc))

                    spc = max(1, 4 - prettyA.width())
                    prettyA = prettyForm(*prettyA.right(' ' * spc))

                pform = prettyForm(*pform.above(prettyB))
                pform = prettyForm(*pform.below(prettyA))

            if not ascii_mode:  # XXX hack
                pform = prettyForm(*pform.right(' '))

            if firstterm:
                s = pform   # first term
                firstterm = False
            else:
                s = prettyForm(*s.left(pform))

        pform = prettyForm(*arg.left(s))
        pform.binding = prettyForm.MUL
        return pform

    def _print_Product(self, expr):
        func = expr.term
        pretty_func = self._print(func)

        horizontal_chr = xobj('_', 1)
        corner_chr = xobj('_', 1)
        vertical_chr = xobj('|', 1)

        if self._use_unicode:
            # use unicode corners
            horizontal_chr = xobj('-', 1)
            corner_chr = xobj('UpTack', 1)

        func_height = pretty_func.height()

        first = True
        max_upper = 0
        sign_height = 0

        for lim in expr.limits:
            pretty_lower, pretty_upper = self.__print_SumProduct_Limits(lim)

            width = (func_height + 2) * 5 // 3 - 2
            sign_lines = [horizontal_chr + corner_chr + (horizontal_chr * (width-2)) + corner_chr + horizontal_chr]
            for _ in range(func_height + 1):
                sign_lines.append(' ' + vertical_chr + (' ' * (width-2)) + vertical_chr + ' ')

            pretty_sign = stringPict('')
            pretty_sign = prettyForm(*pretty_sign.stack(*sign_lines))


            max_upper = max(max_upper, pretty_upper.height())

            if first:
                sign_height = pretty_sign.height()

            pretty_sign = prettyForm(*pretty_sign.above(pretty_upper))
            pretty_sign = prettyForm(*pretty_sign.below(pretty_lower))

            if first:
                pretty_func.baseline = 0
                first = False

            height = pretty_sign.height()
            padding = stringPict('')
            padding = prettyForm(*padding.stack(*[' ']*(height - 1)))
            pretty_sign = prettyForm(*pretty_sign.right(padding))

            pretty_func = prettyForm(*pretty_sign.right(pretty_func))

        pretty_func.baseline = max_upper + sign_height//2
        pretty_func.binding = prettyForm.MUL
        return pretty_func

    def __print_SumProduct_Limits(self, lim):
        def print_start(lhs, rhs):
            op = prettyForm(' ' + xsym("==") + ' ')
            l = self._print(lhs)
            r = self._print(rhs)
            pform = prettyForm(*stringPict.next(l, op, r))
            return pform

        prettyUpper = self._print(lim[2])
        prettyLower = print_start(lim[0], lim[1])
        return prettyLower, prettyUpper

    def _print_Sum(self, expr):
        ascii_mode = not self._use_unicode

        def asum(hrequired, lower, upper, use_ascii):
            def adjust(s, wid=None, how='<^>'):
                if not wid or len(s) > wid:
                    return s
                need = wid - len(s)
                if how in ('<^>', "<") or how not in list('<^>'):
                    return s + ' '*need
                half = need//2
                lead = ' '*half
                if how == ">":
                    return " "*need + s
                return lead + s + ' '*(need - len(lead))

            h = max(hrequired, 2)
            d = h//2
            w = d + 1
            more = hrequired % 2

            lines = []
            if use_ascii:
                lines.append("_"*(w) + ' ')
                lines.append(r"\%s`" % (' '*(w - 1)))
                for i in range(1, d):
                    lines.append('%s\\%s' % (' '*i, ' '*(w - i)))
                if more:
                    lines.append('%s)%s' % (' '*(d), ' '*(w - d)))
                for i in reversed(range(1, d)):
                    lines.append('%s/%s' % (' '*i, ' '*(w - i)))
                lines.append("/" + "_"*(w - 1) + ',')
                return d, h + more, lines, more
            else:
                w = w + more
                d = d + more
                vsum = vobj('sum', 4)
                lines.append("_"*(w))
                for i in range(0, d):
                    lines.append('%s%s%s' % (' '*i, vsum[2], ' '*(w - i - 1)))
                for i in reversed(range(0, d)):
                    lines.append('%s%s%s' % (' '*i, vsum[4], ' '*(w - i - 1)))
                lines.append(vsum[8]*(w))
                return d, h + 2*more, lines, more

        f = expr.function

        prettyF = self._print(f)

        if f.is_Add:  # add parens
            prettyF = prettyForm(*prettyF.parens())

        H = prettyF.height() + 2

        # \sum \sum \sum ...
        first = True
        max_upper = 0
        sign_height = 0

        for lim in expr.limits:
            prettyLower, prettyUpper = self.__print_SumProduct_Limits(lim)

            max_upper = max(max_upper, prettyUpper.height())

            # Create sum sign based on the height of the argument
            d, h, slines, adjustment = asum(
                H, prettyLower.width(), prettyUpper.width(), ascii_mode)
            prettySign = stringPict('')
            prettySign = prettyForm(*prettySign.stack(*slines))

            if first:
                sign_height = prettySign.height()

            prettySign = prettyForm(*prettySign.above(prettyUpper))
            prettySign = prettyForm(*prettySign.below(prettyLower))

            if first:
                # change F baseline so it centers on the sign
                prettyF.baseline -= d - (prettyF.height()//2 -
                                         prettyF.baseline)
                first = False

            # put padding to the right
            pad = stringPict('')
            pad = prettyForm(*pad.stack(*[' ']*h))
            prettySign = prettyForm(*prettySign.right(pad))
            # put the present prettyF to the right
            prettyF = prettyForm(*prettySign.right(prettyF))

        # adjust baseline of ascii mode sigma with an odd height so that it is
        # exactly through the center
        ascii_adjustment = ascii_mode if not adjustment else 0
        prettyF.baseline = max_upper + sign_height//2 + ascii_adjustment

        prettyF.binding = prettyForm.MUL
        return prettyF

    def _print_Limit(self, l):
        e, z, z0, dir = l.args

        E = self._print(e)
        if precedence(e) <= PRECEDENCE["Mul"]:
            E = prettyForm(*E.parens('(', ')'))
        Lim = prettyForm('lim')

        LimArg = self._print(z)
        if self._use_unicode:
            LimArg = prettyForm(*LimArg.right(f"{xobj('-', 1)}{pretty_atom('Arrow')}"))
        else:
            LimArg = prettyForm(*LimArg.right('->'))
        LimArg = prettyForm(*LimArg.right(self._print(z0)))

        if str(dir) == '+-' or z0 in (S.Infinity, S.NegativeInfinity):
            dir = ""
        else:
            if self._use_unicode:
                dir = pretty_atom('SuperscriptPlus') if str(dir) == "+" else pretty_atom('SuperscriptMinus')

        LimArg = prettyForm(*LimArg.right(self._print(dir)))

        Lim = prettyForm(*Lim.below(LimArg))
        Lim = prettyForm(*Lim.right(E), binding=prettyForm.MUL)

        return Lim

    def _print_matrix_contents(self, e):
        """
        This method factors out what is essentially grid printing.
        """
        M = e   # matrix
        Ms = {}  # i,j -> pretty(M[i,j])
        for i in range(M.rows):
            for j in range(M.cols):
                Ms[i, j] = self._print(M[i, j])

        # h- and v- spacers
        hsep = 2
        vsep = 1

        # max width for columns
        maxw = [-1] * M.cols

        for j in range(M.cols):
            maxw[j] = max([Ms[i, j].width() for i in range(M.rows)] or [0])

        # drawing result
        D = None

        for i in range(M.rows):

            D_row = None
            for j in range(M.cols):
                s = Ms[i, j]

                # reshape s to maxw
                # XXX this should be generalized, and go to stringPict.reshape ?
                assert s.width() <= maxw[j]

                # hcenter it, +0.5 to the right                        2
                # ( it's better to align formula starts for say 0 and r )
                # XXX this is not good in all cases -- maybe introduce vbaseline?
                left, right = center_pad(s.width(), maxw[j])

                s = prettyForm(*s.right(right))
                s = prettyForm(*s.left(left))

                # we don't need vcenter cells -- this is automatically done in
                # a pretty way because when their baselines are taking into
                # account in .right()

                if D_row is None:
                    D_row = s   # first box in a row
                    continue

                D_row = prettyForm(*D_row.right(' '*hsep))  # h-spacer
                D_row = prettyForm(*D_row.right(s))

            if D is None:
                D = D_row       # first row in a picture
                continue

            # v-spacer
            for _ in range(vsep):
                D = prettyForm(*D.below(' '))

            D = prettyForm(*D.below(D_row))

        if D is None:
            D = prettyForm('')  # Empty Matrix

        return D

    def _print_MatrixBase(self, e, lparens='[', rparens=']'):
        D = self._print_matrix_contents(e)
        D.baseline = D.height()//2
        D = prettyForm(*D.parens(lparens, rparens))
        return D

    def _print_Determinant(self, e):
        mat = e.arg
        if mat.is_MatrixExpr:
            from sympy.matrices.expressions.blockmatrix import BlockMatrix
            if isinstance(mat, BlockMatrix):
                return self._print_MatrixBase(mat.blocks, lparens='|', rparens='|')
            D = self._print(mat)
            D.baseline = D.height()//2
            return prettyForm(*D.parens('|', '|'))
        else:
            return self._print_MatrixBase(mat, lparens='|', rparens='|')

    def _print_TensorProduct(self, expr):
        # This should somehow share the code with _print_WedgeProduct:
        if self._use_unicode:
            circled_times = "\u2297"
        else:
            circled_times = ".*"
        return self._print_seq(expr.args, None, None, circled_times,
            parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"])

    def _print_WedgeProduct(self, expr):
        # This should somehow share the code with _print_TensorProduct:
        if self._use_unicode:
            wedge_symbol = "\u2227"
        else:
            wedge_symbol = '/\\'
        return self._print_seq(expr.args, None, None, wedge_symbol,
            parenthesize=lambda x: precedence_traditional(x) <= PRECEDENCE["Mul"])

    def _print_Trace(self, e):
        D = self._print(e.arg)
        D = prettyForm(*D.parens('(',')'))
        D.baseline = D.height()//2
        D = prettyForm(*D.left('\n'*(0) + 'tr'))
        return D


    def _print_MatrixElement(self, expr):
        from sympy.matrices import MatrixSymbol
        if (isinstance(expr.parent, MatrixSymbol)
                and expr.i.is_number and expr.j.is_number):
            return self._print(
                    Symbol(expr.parent.name + '_%d%d' % (expr.i, expr.j)))
        else:
            prettyFunc = self._print(expr.parent)
            prettyFunc = prettyForm(*prettyFunc.parens())
            prettyIndices = self._print_seq((expr.i, expr.j), delimiter=', '
                    ).parens(left='[', right=']')[0]
            pform = prettyForm(binding=prettyForm.FUNC,
                    *stringPict.next(prettyFunc, prettyIndices))

            # store pform parts so it can be reassembled e.g. when powered
            pform.prettyFunc = prettyFunc
            pform.prettyArgs = prettyIndices

            return pform


    def _print_MatrixSlice(self, m):
        # XXX works only for applied functions
        from sympy.matrices import MatrixSymbol
        prettyFunc = self._print(m.parent)
        if not isinstance(m.parent, MatrixSymbol):
            prettyFunc = prettyForm(*prettyFunc.parens())
        def ppslice(x, dim):
            x = list(x)
            if x[2] == 1:
                del x[2]
            if x[0] == 0:
                x[0] = ''
            if x[1] == dim:
                x[1] = ''
            return prettyForm(*self._print_seq(x, delimiter=':'))
        prettyArgs = self._print_seq((ppslice(m.rowslice, m.parent.rows),
            ppslice(m.colslice, m.parent.cols)), delimiter=', ').parens(left='[', right=']')[0]

        pform = prettyForm(
            binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs))

        # store pform parts so it can be reassembled e.g. when powered
        pform.prettyFunc = prettyFunc
        pform.prettyArgs = prettyArgs

        return pform

    def _print_Transpose(self, expr):
        mat = expr.arg
        pform = self._print(mat)
        from sympy.matrices import MatrixSymbol, BlockMatrix
        if (not isinstance(mat, MatrixSymbol) and
            not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr):
            pform = prettyForm(*pform.parens())
        pform = pform**(prettyForm('T'))
        return pform

    def _print_Adjoint(self, expr):
        mat = expr.arg
        pform = self._print(mat)
        if self._use_unicode:
            dag = prettyForm(pretty_atom('Dagger'))
        else:
            dag = prettyForm('+')
        from sympy.matrices import MatrixSymbol, BlockMatrix
        if (not isinstance(mat, MatrixSymbol) and
            not isinstance(mat, BlockMatrix) and mat.is_MatrixExpr):
            pform = prettyForm(*pform.parens())
        pform = pform**dag
        return pform

    def _print_BlockMatrix(self, B):
        if B.blocks.shape == (1, 1):
            return self._print(B.blocks[0, 0])
        return self._print(B.blocks)

    def _print_MatAdd(self, expr):
        s = None
        for item in expr.args:
            pform = self._print(item)
            if s is None:
                s = pform     # First element
            else:
                coeff = item.as_coeff_mmul()[0]
                if S(coeff).could_extract_minus_sign():
                    s = prettyForm(*stringPict.next(s, ' '))
                    pform = self._print(item)
                else:
                    s = prettyForm(*stringPict.next(s, ' + '))
                s = prettyForm(*stringPict.next(s, pform))

        return s

    def _print_MatMul(self, expr):
        args = list(expr.args)
        from sympy.matrices.expressions.hadamard import HadamardProduct
        from sympy.matrices.expressions.kronecker import KroneckerProduct
        from sympy.matrices.expressions.matadd import MatAdd
        for i, a in enumerate(args):
            if (isinstance(a, (Add, MatAdd, HadamardProduct, KroneckerProduct))
                    and len(expr.args) > 1):
                args[i] = prettyForm(*self._print(a).parens())
            else:
                args[i] = self._print(a)

        return prettyForm.__mul__(*args)

    def _print_Identity(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_atom('IdentityMatrix'))
        else:
            return prettyForm('I')

    def _print_ZeroMatrix(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_atom('ZeroMatrix'))
        else:
            return prettyForm('0')

    def _print_OneMatrix(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_atom("OneMatrix"))
        else:
            return prettyForm('1')

    def _print_MatrixUnit(self, expr):
        if self._use_unicode:
            s = self._print(Symbol(f'{pretty_atom("MatrixUnit")}_{expr._i}{expr._j}'))
        else:
            s = self._print(Symbol(f'E_{expr._i}{expr._j}'))
        return s

    def _print_DotProduct(self, expr):
        args = list(expr.args)

        for i, a in enumerate(args):
            args[i] = self._print(a)
        return prettyForm.__mul__(*args)

    def _print_MatPow(self, expr):
        pform = self._print(expr.base)
        from sympy.matrices import MatrixSymbol
        if not isinstance(expr.base, MatrixSymbol) and expr.base.is_MatrixExpr:
            pform = prettyForm(*pform.parens())
        pform = pform**(self._print(expr.exp))
        return pform

    def _print_HadamardProduct(self, expr):
        from sympy.matrices.expressions.hadamard import HadamardProduct
        from sympy.matrices.expressions.matadd import MatAdd
        from sympy.matrices.expressions.matmul import MatMul
        if self._use_unicode:
            delim = pretty_atom('Ring')
        else:
            delim = '.*'
        return self._print_seq(expr.args, None, None, delim,
                parenthesize=lambda x: isinstance(x, (MatAdd, MatMul, HadamardProduct)))

    def _print_HadamardPower(self, expr):
        # from sympy import MatAdd, MatMul
        if self._use_unicode:
            circ = pretty_atom('Ring')
        else:
            circ = self._print('.')
        pretty_base = self._print(expr.base)
        pretty_exp = self._print(expr.exp)
        if precedence(expr.exp) < PRECEDENCE["Mul"]:
            pretty_exp = prettyForm(*pretty_exp.parens())
        pretty_circ_exp = prettyForm(
            binding=prettyForm.LINE,
            *stringPict.next(circ, pretty_exp)
        )
        return pretty_base**pretty_circ_exp

    def _print_KroneckerProduct(self, expr):
        from sympy.matrices.expressions.matadd import MatAdd
        from sympy.matrices.expressions.matmul import MatMul
        if self._use_unicode:
            delim = f" {pretty_atom('TensorProduct')} "
        else:
            delim = ' x '
        return self._print_seq(expr.args, None, None, delim,
                parenthesize=lambda x: isinstance(x, (MatAdd, MatMul)))

    def _print_FunctionMatrix(self, X):
        D = self._print(X.lamda.expr)
        D = prettyForm(*D.parens('[', ']'))
        return D

    def _print_TransferFunction(self, expr):
        if not expr.num == 1:
            num, den = expr.num, expr.den
            res = Mul(num, Pow(den, -1, evaluate=False), evaluate=False)
            return self._print_Mul(res)
        else:
            return self._print(1)/self._print(expr.den)

    def _print_DiscreteTransferFunction(self, expr):
        if not expr.num == 1:
            res = Mul(expr.num, Pow(expr.den, -1, evaluate=False),
                      evaluate=False)
            result = self._print_Mul(res)
        else:
            result =  self._print(1)/self._print(expr.den)

        result = prettyForm(\
            *result.right(f" [st: {expr.sampling_time}]"))
        return result


    def _print_Series(self, expr):
        args = list(expr.args)
        for i, a in enumerate(expr.args):
            args[i] = prettyForm(*self._print(a).parens())
        return prettyForm.__mul__(*args)

    def _print_MIMOSeries(self, expr):
        from sympy.physics.control.lti import MIMOParallel
        args = list(expr.args)
        pretty_args = []
        for a in reversed(args):
            if (isinstance(a, MIMOParallel) and len(expr.args) > 1):
                expression = self._print(a)
                expression.baseline = expression.height()//2
                pretty_args.append(prettyForm(*expression.parens()))
            else:
                expression = self._print(a)
                expression.baseline = expression.height()//2
                pretty_args.append(expression)
        return prettyForm.__mul__(*pretty_args)

    def _print_Parallel(self, expr):
        s = None
        for item in expr.args:
            pform = self._print(item)
            if s is None:
                s = pform     # First element
            else:
                s = prettyForm(*stringPict.next(s))
                s.baseline = s.height()//2
                s = prettyForm(*stringPict.next(s, ' + '))
                s = prettyForm(*stringPict.next(s, pform))
        return s

    def _print_MIMOParallel(self, expr):
        from sympy.physics.control.lti import TransferFunctionMatrix
        s = None
        for item in expr.args:
            pform = self._print(item)
            if s is None:
                s = pform     # First element
            else:
                s = prettyForm(*stringPict.next(s))
                s.baseline = s.height()//2
                s = prettyForm(*stringPict.next(s, ' + '))
                if isinstance(item, TransferFunctionMatrix):
                    s.baseline = s.height() - 1
                s = prettyForm(*stringPict.next(s, pform))
            # s.baseline = s.height()//2
        return s

    def _print_Feedback(self, expr):
        from sympy.physics.control import TransferFunction, Series

        num, tf = expr.sys1, TransferFunction(1, 1, expr.var)
        num_arg_list = list(num.args) if isinstance(num, Series) else [num]
        den_arg_list = list(expr.sys2.args) if \
            isinstance(expr.sys2, Series) else [expr.sys2]

        if isinstance(num, Series) and isinstance(expr.sys2, Series):
            den = Series(*num_arg_list, *den_arg_list)
        elif isinstance(num, Series) and isinstance(expr.sys2, TransferFunction):
            if expr.sys2 == tf:
                den = Series(*num_arg_list)
            else:
                den = Series(*num_arg_list, expr.sys2)
        elif isinstance(num, TransferFunction) and isinstance(expr.sys2, Series):
            if num == tf:
                den = Series(*den_arg_list)
            else:
                den = Series(num, *den_arg_list)
        else:
            if num == tf:
                den = Series(*den_arg_list)
            elif expr.sys2 == tf:
                den = Series(*num_arg_list)
            else:
                den = Series(*num_arg_list, *den_arg_list)

        denom = prettyForm(*stringPict.next(self._print(tf)))
        denom.baseline = denom.height()//2
        denom = prettyForm(*stringPict.next(denom, ' + ')) if expr.sign == -1 \
            else prettyForm(*stringPict.next(denom, ' - '))
        denom = prettyForm(*stringPict.next(denom, self._print(den)))

        return self._print(num)/denom

    def _print_MIMOFeedback(self, expr):
        from sympy.physics.control import MIMOSeries, TransferFunctionMatrix

        inv_mat = self._print(MIMOSeries(expr.sys2, expr.sys1))
        plant = self._print(expr.sys1)
        _feedback = prettyForm(*stringPict.next(inv_mat))
        _feedback = prettyForm(*stringPict.right("I + ", _feedback)) if expr.sign == -1 \
            else prettyForm(*stringPict.right("I - ", _feedback))
        _feedback = prettyForm(*stringPict.parens(_feedback))
        _feedback.baseline = 0
        _feedback = prettyForm(*stringPict.right(_feedback, '-1 '))
        _feedback.baseline = _feedback.height()//2
        _feedback = prettyForm.__mul__(_feedback, prettyForm(" "))
        if isinstance(expr.sys1, TransferFunctionMatrix):
            _feedback.baseline = _feedback.height() - 1
        _feedback = prettyForm(*stringPict.next(_feedback, plant))
        return _feedback

    def _print_TransferFunctionMatrix(self, expr):
        mat = self._print(expr._expr_mat)
        mat.baseline = mat.height() - 1
        if expr.sampling_time == 0:
            subscript = greek_unicode['tau'] if self._use_unicode else r'{t}'
        else:
            subscript = r'{k}'
        mat = prettyForm(*mat.right(subscript))

        if expr.sampling_time == 0:
            return mat

        return prettyForm(*mat.below(f"[st: {expr.sampling_time}]"))

    def _print_StateSpace(self, expr):
        from sympy.matrices.expressions.blockmatrix import BlockMatrix
        A = expr._A
        B = expr._B
        C = expr._C
        D = expr._D
        mat = BlockMatrix([[A, B], [C, D]])
        return self._print(mat.blocks)

    def _print_DiscreteStateSpace(self, expr):
        from sympy.matrices.expressions.blockmatrix import BlockMatrix
        A = expr._A
        B = expr._B
        C = expr._C
        D = expr._D
        mat = BlockMatrix([[A, B], [C, D]])
        mat = self._print(mat)
        return prettyForm(*mat.below(f"\n[st: {expr.sampling_time}]"))

    def _print_BasisDependent(self, expr):
        from sympy.vector import Vector

        if not self._use_unicode:
            raise NotImplementedError("ASCII pretty printing of BasisDependent is not implemented")

        if expr == expr.zero:
            return prettyForm(expr.zero._pretty_form)
        o1 = []
        vectstrs = []
        if isinstance(expr, Vector):
            items = expr.separate().items()
        else:
            items = [(0, expr)]
        for system, vect in items:
            inneritems = list(vect.components.items())
            inneritems.sort(key = lambda x: x[0].__str__())
            for k, v in inneritems:
                #if the coef of the basis vector is 1
                #we skip the 1
                if v == 1:
                    o1.append("" +
                              k._pretty_form)
                #Same for -1
                elif v == -1:
                    o1.append("(-1) " +
                              k._pretty_form)
                #For a general expr
                else:
                    #We always wrap the measure numbers in
                    #parentheses
                    arg_str = self._print(
                        v).parens()[0]

                    o1.append(arg_str + ' ' + k._pretty_form)
                vectstrs.append(k._pretty_form)

        #outstr = u("").join(o1)
        if o1[0].startswith(" + "):
            o1[0] = o1[0][3:]
        elif o1[0].startswith(" "):
            o1[0] = o1[0][1:]
        #Fixing the newlines
        lengths = []
        strs = ['']
        flag = []
        for i, partstr in enumerate(o1):
            flag.append(0)
            # XXX: What is this hack?
            if '\n' in partstr:
                tempstr = partstr
                tempstr = tempstr.replace(vectstrs[i], '')
                if xobj(')_ext', 1) in tempstr:   # If scalar is a fraction
                    for paren in range(len(tempstr)):
                        flag[i] = 1
                        if tempstr[paren] == xobj(')_ext', 1) and tempstr[paren + 1] == '\n':
                            # We want to place the vector string after all the right parentheses, because
                            # otherwise, the vector will be in the middle of the string
                            tempstr = tempstr[:paren] + xobj(')_ext', 1)\
                                         + ' '  + vectstrs[i] + tempstr[paren + 1:]
                            break
                elif xobj(')_lower_hook', 1) in tempstr:
                    # We want to place the vector string after all the right parentheses, because
                    # otherwise, the vector will be in the middle of the string. For this reason,
                    # we insert the vector string at the rightmost index.
                    index = tempstr.rfind(xobj(')_lower_hook', 1))
                    if index != -1: # then this character was found in this string
                        flag[i] = 1
                        tempstr = tempstr[:index] + xobj(')_lower_hook', 1)\
                                     + ' '  + vectstrs[i] + tempstr[index + 1:]
                o1[i] = tempstr

        o1 = [x.split('\n') for x in o1]
        n_newlines = max(len(x) for x in o1)  # Width of part in its pretty form

        if 1 in flag:                           # If there was a fractional scalar
            for i, parts in enumerate(o1):
                if len(parts) == 1:             # If part has no newline
                    parts.insert(0, ' ' * (len(parts[0])))
                    flag[i] = 1

        for i, parts in enumerate(o1):
            lengths.append(len(parts[flag[i]]))
            for j in range(n_newlines):
                if j+1 <= len(parts):
                    if j >= len(strs):
                        strs.append(' ' * (sum(lengths[:-1]) +
                                           3*(len(lengths)-1)))
                    if j == flag[i]:
                        strs[flag[i]] += parts[flag[i]] + ' + '
                    else:
                        strs[j] += parts[j] + ' '*(lengths[-1] -
                                                   len(parts[j])+
                                                   3)
                else:
                    if j >= len(strs):
                        strs.append(' ' * (sum(lengths[:-1]) +
                                           3*(len(lengths)-1)))
                    strs[j] += ' '*(lengths[-1]+3)

        return prettyForm('\n'.join([s[:-3] for s in strs]))

    def _print_NDimArray(self, expr):
        from sympy.matrices.immutable import ImmutableMatrix

        if expr.rank() == 0:
            return self._print(expr[()])

        level_str = [[]] + [[] for i in range(expr.rank())]
        shape_ranges = [list(range(i)) for i in expr.shape]
        # leave eventual matrix elements unflattened
        mat = lambda x: ImmutableMatrix(x, evaluate=False)
        for outer_i in itertools.product(*shape_ranges):
            level_str[-1].append(expr[outer_i])
            even = True
            for back_outer_i in range(expr.rank()-1, -1, -1):
                if len(level_str[back_outer_i+1]) < expr.shape[back_outer_i]:
                    break
                if even:
                    level_str[back_outer_i].append(level_str[back_outer_i+1])
                else:
                    level_str[back_outer_i].append(mat(
                        level_str[back_outer_i+1]))
                    if len(level_str[back_outer_i + 1]) == 1:
                        level_str[back_outer_i][-1] = mat(
                            [[level_str[back_outer_i][-1]]])
                even = not even
                level_str[back_outer_i+1] = []

        out_expr = level_str[0][0]
        if expr.rank() % 2 == 1:
            out_expr = mat([out_expr])

        return self._print(out_expr)

    def _printer_tensor_indices(self, name, indices, index_map={}):
        center = stringPict(pretty_symbol(name))
        top = stringPict(" "*center.width())
        bot = stringPict(" "*center.width())

        last_valence = None
        prev_map = None

        for index in indices:
            indpic = self._print(index.args[0])
            if ((index in index_map) or prev_map) and last_valence == index.is_up:
                if index.is_up:
                    top = prettyForm(*stringPict.next(top, ","))
                else:
                    bot = prettyForm(*stringPict.next(bot, ","))
            if index in index_map:
                indpic = prettyForm(*stringPict.next(indpic, "="))
                indpic = prettyForm(*stringPict.next(indpic, self._print(index_map[index])))
                prev_map = True
            else:
                prev_map = False
            if index.is_up:
                top = stringPict(*top.right(indpic))
                center = stringPict(*center.right(" "*indpic.width()))
                bot = stringPict(*bot.right(" "*indpic.width()))
            else:
                bot = stringPict(*bot.right(indpic))
                center = stringPict(*center.right(" "*indpic.width()))
                top = stringPict(*top.right(" "*indpic.width()))
            last_valence = index.is_up

        pict = prettyForm(*center.above(top))
        pict = prettyForm(*pict.below(bot))
        return pict

    def _print_Tensor(self, expr):
        name = expr.args[0].name
        indices = expr.get_indices()
        return self._printer_tensor_indices(name, indices)

    def _print_TensorElement(self, expr):
        name = expr.expr.args[0].name
        indices = expr.expr.get_indices()
        index_map = expr.index_map
        return self._printer_tensor_indices(name, indices, index_map)

    def _print_TensMul(self, expr):
        sign, args = expr._get_args_for_traditional_printer()
        args = [
            prettyForm(*self._print(i).parens()) if
            precedence_traditional(i) < PRECEDENCE["Mul"] else self._print(i)
            for i in args
        ]
        pform = prettyForm.__mul__(*args)
        if sign:
            return prettyForm(*pform.left(sign))
        else:
            return pform

    def _print_TensAdd(self, expr):
        args = [
            prettyForm(*self._print(i).parens()) if
            precedence_traditional(i) < PRECEDENCE["Mul"] else self._print(i)
            for i in expr.args
        ]
        return prettyForm.__add__(*args)

    def _print_TensorIndex(self, expr):
        sym = expr.args[0]
        if not expr.is_up:
            sym = -sym
        return self._print(sym)

    def _print_PartialDerivative(self, deriv):
        if self._use_unicode:
            deriv_symbol = U('PARTIAL DIFFERENTIAL')
        else:
            deriv_symbol = r'd'
        x = None

        for variable in reversed(deriv.variables):
            s = self._print(variable)
            ds = prettyForm(*s.left(deriv_symbol))

            if x is None:
                x = ds
            else:
                x = prettyForm(*x.right(' '))
                x = prettyForm(*x.right(ds))

        f = prettyForm(
            binding=prettyForm.FUNC, *self._print(deriv.expr).parens())

        pform = prettyForm(deriv_symbol)

        if len(deriv.variables) > 1:
            pform = pform**self._print(len(deriv.variables))

        pform = prettyForm(*pform.below(stringPict.LINE, x))
        pform.baseline = pform.baseline + 1
        pform = prettyForm(*stringPict.next(pform, f))
        pform.binding = prettyForm.MUL

        return pform

    def _print_Piecewise(self, pexpr):

        P = {}
        for n, ec in enumerate(pexpr.args):
            P[n, 0] = self._print(ec.expr)
            if ec.cond == True:
                P[n, 1] = prettyForm('otherwise')
            else:
                P[n, 1] = prettyForm(
                    *prettyForm('for ').right(self._print(ec.cond)))
        hsep = 2
        vsep = 1
        len_args = len(pexpr.args)

        # max widths
        maxw = [max(P[i, j].width() for i in range(len_args))
                for j in range(2)]

        # FIXME: Refactor this code and matrix into some tabular environment.
        # drawing result
        D = None

        for i in range(len_args):
            D_row = None
            for j in range(2):
                p = P[i, j]
                assert p.width() <= maxw[j]

                wdelta = maxw[j] - p.width()
                wleft = wdelta // 2
                wright = wdelta - wleft

                p = prettyForm(*p.right(' '*wright))
                p = prettyForm(*p.left(' '*wleft))

                if D_row is None:
                    D_row = p
                    continue

                D_row = prettyForm(*D_row.right(' '*hsep))  # h-spacer
                D_row = prettyForm(*D_row.right(p))
            if D is None:
                D = D_row       # first row in a picture
                continue

            # v-spacer
            for _ in range(vsep):
                D = prettyForm(*D.below(' '))

            D = prettyForm(*D.below(D_row))

        D = prettyForm(*D.parens('{', ''))
        D.baseline = D.height()//2
        D.binding = prettyForm.OPEN
        return D

    def _print_ITE(self, ite):
        from sympy.functions.elementary.piecewise import Piecewise
        return self._print(ite.rewrite(Piecewise))

    def _hprint_vec(self, v):
        D = None

        for a in v:
            p = a
            if D is None:
                D = p
            else:
                D = prettyForm(*D.right(', '))
                D = prettyForm(*D.right(p))
        if D is None:
            D = stringPict(' ')

        return D

    def _hprint_vseparator(self, p1, p2, left=None, right=None, delimiter='', ifascii_nougly=False):
        if ifascii_nougly and not self._use_unicode:
            return self._print_seq((p1, '|', p2), left=left, right=right,
                                   delimiter=delimiter, ifascii_nougly=True)
        tmp = self._print_seq((p1, p2,), left=left, right=right, delimiter=delimiter)
        sep = stringPict(vobj('|', tmp.height()), baseline=tmp.baseline)
        return self._print_seq((p1, sep, p2), left=left, right=right,
                               delimiter=delimiter)

    def _print_hyper(self, e):
        # FIXME refactor Matrix, Piecewise, and this into a tabular environment
        ap = [self._print(a) for a in e.ap]
        bq = [self._print(b) for b in e.bq]

        P = self._print(e.argument)
        P.baseline = P.height()//2

        # Drawing result - first create the ap, bq vectors
        D = None
        for v in [ap, bq]:
            D_row = self._hprint_vec(v)
            if D is None:
                D = D_row       # first row in a picture
            else:
                D = prettyForm(*D.below(' '))
                D = prettyForm(*D.below(D_row))

        # make sure that the argument `z' is centred vertically
        D.baseline = D.height()//2

        # insert horizontal separator
        P = prettyForm(*P.left(' '))
        D = prettyForm(*D.right(' '))

        # insert separating `|`
        D = self._hprint_vseparator(D, P)

        # add parens
        D = prettyForm(*D.parens('(', ')'))

        # create the F symbol
        above = D.height()//2 - 1
        below = D.height() - above - 1

        sz, t, b, add, img = annotated('F')
        F = prettyForm('\n' * (above - t) + img + '\n' * (below - b),
                       baseline=above + sz)
        add = (sz + 1)//2

        F = prettyForm(*F.left(self._print(len(e.ap))))
        F = prettyForm(*F.right(self._print(len(e.bq))))
        F.baseline = above + add

        D = prettyForm(*F.right(' ', D))

        return D

    def _print_meijerg(self, e):
        # FIXME refactor Matrix, Piecewise, and this into a tabular environment

        v = {}
        v[(0, 0)] = [self._print(a) for a in e.an]
        v[(0, 1)] = [self._print(a) for a in e.aother]
        v[(1, 0)] = [self._print(b) for b in e.bm]
        v[(1, 1)] = [self._print(b) for b in e.bother]

        P = self._print(e.argument)
        P.baseline = P.height()//2

        vp = {}
        for idx in v:
            vp[idx] = self._hprint_vec(v[idx])

        for i in range(2):
            maxw = max(vp[(0, i)].width(), vp[(1, i)].width())
            for j in range(2):
                s = vp[(j, i)]
                left = (maxw - s.width()) // 2
                right = maxw - left - s.width()
                s = prettyForm(*s.left(' ' * left))
                s = prettyForm(*s.right(' ' * right))
                vp[(j, i)] = s

        D1 = prettyForm(*vp[(0, 0)].right('  ', vp[(0, 1)]))
        D1 = prettyForm(*D1.below(' '))
        D2 = prettyForm(*vp[(1, 0)].right('  ', vp[(1, 1)]))
        D = prettyForm(*D1.below(D2))

        # make sure that the argument `z' is centred vertically
        D.baseline = D.height()//2

        # insert horizontal separator
        P = prettyForm(*P.left(' '))
        D = prettyForm(*D.right(' '))

        # insert separating `|`
        D = self._hprint_vseparator(D, P)

        # add parens
        D = prettyForm(*D.parens('(', ')'))

        # create the G symbol
        above = D.height()//2 - 1
        below = D.height() - above - 1

        sz, t, b, add, img = annotated('G')
        F = prettyForm('\n' * (above - t) + img + '\n' * (below - b),
                       baseline=above + sz)

        pp = self._print(len(e.ap))
        pq = self._print(len(e.bq))
        pm = self._print(len(e.bm))
        pn = self._print(len(e.an))

        def adjust(p1, p2):
            diff = p1.width() - p2.width()
            if diff == 0:
                return p1, p2
            elif diff > 0:
                return p1, prettyForm(*p2.left(' '*diff))
            else:
                return prettyForm(*p1.left(' '*-diff)), p2
        pp, pm = adjust(pp, pm)
        pq, pn = adjust(pq, pn)
        pu = prettyForm(*pm.right(', ', pn))
        pl = prettyForm(*pp.right(', ', pq))

        ht = F.baseline - above - 2
        if ht > 0:
            pu = prettyForm(*pu.below('\n'*ht))
        p = prettyForm(*pu.below(pl))

        F.baseline = above
        F = prettyForm(*F.right(p))

        F.baseline = above + add

        D = prettyForm(*F.right(' ', D))

        return D

    def _print_ExpBase(self, e):
        # TODO should exp_polar be printed differently?
        #      what about exp_polar(0), exp_polar(1)?
        base = prettyForm(pretty_atom('Exp1', 'e'))
        return base ** self._print(e.args[0])

    def _print_Exp1(self, e):
        return prettyForm(pretty_atom('Exp1', 'e'))

    def _print_Function(self, e, sort=False, func_name=None, left='(',
                        right=')'):
        # optional argument func_name for supplying custom names
        # XXX works only for applied functions
        return self._helper_print_function(e.func, e.args, sort=sort, func_name=func_name, left=left, right=right)

    def _print_mathieuc(self, e):
        return self._print_Function(e, func_name='C')

    def _print_mathieus(self, e):
        return self._print_Function(e, func_name='S')

    def _print_mathieucprime(self, e):
        return self._print_Function(e, func_name="C'")

    def _print_mathieusprime(self, e):
        return self._print_Function(e, func_name="S'")

    def _helper_print_function(self, func, args, sort=False, func_name=None,
                               delimiter=', ', elementwise=False, left='(',
                               right=')'):
        if sort:
            args = sorted(args, key=default_sort_key)

        if not func_name and hasattr(func, "__name__"):
            func_name = func.__name__

        if func_name:
            prettyFunc = self._print(Symbol(func_name))
        else:
            prettyFunc = prettyForm(*self._print(func).parens())

        if elementwise:
            if self._use_unicode:
                circ = pretty_atom('Modifier Letter Low Ring')
            else:
                circ = '.'
            circ = self._print(circ)
            prettyFunc = prettyForm(
                binding=prettyForm.LINE,
                *stringPict.next(prettyFunc, circ)
            )

        prettyArgs = prettyForm(*self._print_seq(args, delimiter=delimiter).parens(
                                                 left=left, right=right))

        pform = prettyForm(
            binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs))

        # store pform parts so it can be reassembled e.g. when powered
        pform.prettyFunc = prettyFunc
        pform.prettyArgs = prettyArgs

        return pform

    def _print_ElementwiseApplyFunction(self, e):
        func = e.function
        arg = e.expr
        args = [arg]
        return self._helper_print_function(func, args, delimiter="", elementwise=True)

    @property
    def _special_function_classes(self):
        from sympy.functions.special.tensor_functions import KroneckerDelta
        from sympy.functions.special.gamma_functions import gamma, lowergamma
        from sympy.functions.special.zeta_functions import lerchphi
        from sympy.functions.special.beta_functions import beta
        from sympy.functions.special.delta_functions import DiracDelta
        from sympy.functions.special.error_functions import Chi
        return {KroneckerDelta: [greek_unicode['delta'], 'delta'],
                gamma: [greek_unicode['Gamma'], 'Gamma'],
                lerchphi: [greek_unicode['Phi'], 'lerchphi'],
                lowergamma: [greek_unicode['gamma'], 'gamma'],
                beta: [greek_unicode['Beta'], 'B'],
                DiracDelta: [greek_unicode['delta'], 'delta'],
                Chi: ['Chi', 'Chi']}

    def _print_FunctionClass(self, expr):
        for cls in self._special_function_classes:
            if issubclass(expr, cls) and expr.__name__ == cls.__name__:
                if self._use_unicode:
                    return prettyForm(self._special_function_classes[cls][0])
                else:
                    return prettyForm(self._special_function_classes[cls][1])
        func_name = expr.__name__
        return prettyForm(pretty_symbol(func_name))

    def _print_GeometryEntity(self, expr):
        # GeometryEntity is based on Tuple but should not print like a Tuple
        return self.emptyPrinter(expr)

    def _print_polylog(self, e):
        subscript = self._print(e.args[0])
        if self._use_unicode and is_subscriptable_in_unicode(subscript):
            return self._print_Function(Function('Li_%s' % subscript)(e.args[1]))
        return self._print_Function(e)

    def _print_lerchphi(self, e):
        func_name = greek_unicode['Phi'] if self._use_unicode else 'lerchphi'
        return self._print_Function(e, func_name=func_name)

    def _print_dirichlet_eta(self, e):
        func_name = greek_unicode['eta'] if self._use_unicode else 'dirichlet_eta'
        return self._print_Function(e, func_name=func_name)

    def _print_Heaviside(self, e):
        func_name = greek_unicode['theta'] if self._use_unicode else 'Heaviside'
        if e.args[1] is S.Half:
            pform = prettyForm(*self._print(e.args[0]).parens())
            pform = prettyForm(*pform.left(func_name))
            return pform
        else:
            return self._print_Function(e, func_name=func_name)

    def _print_fresnels(self, e):
        return self._print_Function(e, func_name="S")

    def _print_fresnelc(self, e):
        return self._print_Function(e, func_name="C")

    def _print_airyai(self, e):
        return self._print_Function(e, func_name="Ai")

    def _print_airybi(self, e):
        return self._print_Function(e, func_name="Bi")

    def _print_airyaiprime(self, e):
        return self._print_Function(e, func_name="Ai'")

    def _print_airybiprime(self, e):
        return self._print_Function(e, func_name="Bi'")

    def _print_LambertW(self, e):
        return self._print_Function(e, func_name="W")

    def _print_Covariance(self, e):
        return self._print_Function(e, func_name="Cov")

    def _print_Variance(self, e):
        return self._print_Function(e, func_name="Var")

    def _print_Probability(self, e):
        return self._print_Function(e, func_name="P")

    def _print_Expectation(self, e):
        return self._print_Function(e, func_name="E", left='[', right=']')

    def _print_Lambda(self, e):
        expr = e.expr
        sig = e.signature
        if self._use_unicode:
            arrow = f" {pretty_atom('ArrowFromBar')} "
        else:
            arrow = " -> "
        if len(sig) == 1 and sig[0].is_symbol:
            sig = sig[0]
        var_form = self._print(sig)

        return prettyForm(*stringPict.next(var_form, arrow, self._print(expr)), binding=8)

    def _print_Order(self, expr):
        pform = self._print(expr.expr)
        if (expr.point and any(p != S.Zero for p in expr.point)) or \
           len(expr.variables) > 1:
            pform = prettyForm(*pform.right("; "))
            if len(expr.variables) > 1:
                pform = prettyForm(*pform.right(self._print(expr.variables)))
            elif len(expr.variables):
                pform = prettyForm(*pform.right(self._print(expr.variables[0])))
            if self._use_unicode:
                pform = prettyForm(*pform.right(f" {pretty_atom('Arrow')} "))
            else:
                pform = prettyForm(*pform.right(" -> "))
            if len(expr.point) > 1:
                pform = prettyForm(*pform.right(self._print(expr.point)))
            else:
                pform = prettyForm(*pform.right(self._print(expr.point[0])))
        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left("O"))
        return pform

    def _print_SingularityFunction(self, e):
        if self._use_unicode:
            shift = self._print(e.args[0]-e.args[1])
            n = self._print(e.args[2])
            base = prettyForm("<")
            base = prettyForm(*base.right(shift))
            base = prettyForm(*base.right(">"))
            pform = base**n
            return pform
        else:
            n = self._print(e.args[2])
            shift = self._print(e.args[0]-e.args[1])
            base = self._print_seq(shift, "<", ">", ' ')
            return base**n

    def _print_beta(self, e):
        func_name = greek_unicode['Beta'] if self._use_unicode else 'B'
        return self._print_Function(e, func_name=func_name)

    def _print_betainc(self, e):
        func_name = "B'"
        return self._print_Function(e, func_name=func_name)

    def _print_betainc_regularized(self, e):
        func_name = 'I'
        return self._print_Function(e, func_name=func_name)

    def _print_gamma(self, e):
        func_name = greek_unicode['Gamma'] if self._use_unicode else 'Gamma'
        return self._print_Function(e, func_name=func_name)

    def _print_uppergamma(self, e):
        func_name = greek_unicode['Gamma'] if self._use_unicode else 'Gamma'
        return self._print_Function(e, func_name=func_name)

    def _print_lowergamma(self, e):
        func_name = greek_unicode['gamma'] if self._use_unicode else 'lowergamma'
        return self._print_Function(e, func_name=func_name)

    def _print_DiracDelta(self, e):
        if self._use_unicode:
            if len(e.args) == 2:
                a = prettyForm(greek_unicode['delta'])
                b = self._print(e.args[1])
                b = prettyForm(*b.parens())
                c = self._print(e.args[0])
                c = prettyForm(*c.parens())
                pform = a**b
                pform = prettyForm(*pform.right(' '))
                pform = prettyForm(*pform.right(c))
                return pform
            pform = self._print(e.args[0])
            pform = prettyForm(*pform.parens())
            pform = prettyForm(*pform.left(greek_unicode['delta']))
            return pform
        else:
            return self._print_Function(e)

    def _print_expint(self, e):
        subscript = self._print(e.args[0])
        if self._use_unicode and is_subscriptable_in_unicode(subscript):
            return self._print_Function(Function('E_%s' % subscript)(e.args[1]))
        return self._print_Function(e)

    def _print_Chi(self, e):
        # This needs a special case since otherwise it comes out as greek
        # letter chi...
        prettyFunc = prettyForm("Chi")
        prettyArgs = prettyForm(*self._print_seq(e.args).parens())

        pform = prettyForm(
            binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs))

        # store pform parts so it can be reassembled e.g. when powered
        pform.prettyFunc = prettyFunc
        pform.prettyArgs = prettyArgs

        return pform

    def _print_elliptic_e(self, e):
        pforma0 = self._print(e.args[0])
        if len(e.args) == 1:
            pform = pforma0
        else:
            pforma1 = self._print(e.args[1])
            pform = self._hprint_vseparator(pforma0, pforma1)
        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left('E'))
        return pform

    def _print_elliptic_k(self, e):
        pform = self._print(e.args[0])
        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left('K'))
        return pform

    def _print_elliptic_f(self, e):
        pforma0 = self._print(e.args[0])
        pforma1 = self._print(e.args[1])
        pform = self._hprint_vseparator(pforma0, pforma1)
        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left('F'))
        return pform

    def _print_elliptic_pi(self, e):
        name = greek_unicode['Pi'] if self._use_unicode else 'Pi'
        pforma0 = self._print(e.args[0])
        pforma1 = self._print(e.args[1])
        if len(e.args) == 2:
            pform = self._hprint_vseparator(pforma0, pforma1)
        else:
            pforma2 = self._print(e.args[2])
            pforma = self._hprint_vseparator(pforma1, pforma2, ifascii_nougly=False)
            pforma = prettyForm(*pforma.left('; '))
            pform = prettyForm(*pforma.left(pforma0))
        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left(name))
        return pform

    def _print_GoldenRatio(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_symbol('phi'))
        return self._print(Symbol("GoldenRatio"))

    def _print_EulerGamma(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_symbol('gamma'))
        return self._print(Symbol("EulerGamma"))

    def _print_Catalan(self, expr):
        return self._print(Symbol("G"))

    def _print_Mod(self, expr):
        pform = self._print(expr.args[0])
        if pform.binding > prettyForm.MUL:
            pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.right(' mod '))
        pform = prettyForm(*pform.right(self._print(expr.args[1])))
        pform.binding = prettyForm.OPEN
        return pform

    def _print_Add(self, expr, order=None):
        terms = self._as_ordered_terms(expr, order=order)
        pforms, indices = [], []

        def pretty_negative(pform, index):
            """Prepend a minus sign to a pretty form. """
            #TODO: Move this code to prettyForm
            if index == 0:
                if pform.height() > 1:
                    pform_neg = '- '
                else:
                    pform_neg = '-'
            else:
                pform_neg = ' - '

            if (pform.binding > prettyForm.NEG
                or pform.binding == prettyForm.ADD):
                p = stringPict(*pform.parens())
            else:
                p = pform
            p = stringPict.next(pform_neg, p)
            # Lower the binding to NEG, even if it was higher. Otherwise, it
            # will print as a + ( - (b)), instead of a - (b).
            return prettyForm(binding=prettyForm.NEG, *p)

        for i, term in enumerate(terms):
            if term.is_Mul and term.could_extract_minus_sign():
                coeff, other = term.as_coeff_mul(rational=False)
                if coeff == -1:
                    negterm = Mul(*other, evaluate=False)
                else:
                    negterm = Mul(-coeff, *other, evaluate=False)
                pform = self._print(negterm)
                pforms.append(pretty_negative(pform, i))
            elif term.is_Rational and term.q > 1:
                pforms.append(None)
                indices.append(i)
            elif term.is_Number and term < 0:
                pform = self._print(-term)
                pforms.append(pretty_negative(pform, i))
            elif term.is_Relational:
                pforms.append(prettyForm(*self._print(term).parens()))
            else:
                pforms.append(self._print(term))

        if indices:
            large = True

            for pform in pforms:
                if pform is not None and pform.height() > 1:
                    break
            else:
                large = False

            for i in indices:
                term, negative = terms[i], False

                if term < 0:
                    term, negative = -term, True

                if large:
                    pform = prettyForm(str(term.p))/prettyForm(str(term.q))
                else:
                    pform = self._print(term)

                if negative:
                    pform = pretty_negative(pform, i)

                pforms[i] = pform

        return prettyForm.__add__(*pforms)

    def _print_Mul(self, product):
        from sympy.physics.units import Quantity

        # Check for unevaluated Mul. In this case we need to make sure the
        # identities are visible, multiple Rational factors are not combined
        # etc so we display in a straight-forward form that fully preserves all
        # args and their order.
        args = product.args
        if args[0] is S.One or any(isinstance(arg, Number) for arg in args[1:]):
            strargs = list(map(self._print, args))
            # XXX: This is a hack to work around the fact that
            # prettyForm.__mul__ absorbs a leading -1 in the args. Probably it
            # would be better to fix this in prettyForm.__mul__ instead.
            negone = strargs[0] == '-1'
            if negone:
                strargs[0] = prettyForm('1', 0, 0)
            obj = prettyForm.__mul__(*strargs)
            if negone:
                obj = prettyForm('-' + obj.s, obj.baseline, obj.binding)
            return obj

        a = []  # items in the numerator
        b = []  # items that are in the denominator (if any)

        if self.order not in ('old', 'none'):
            args = product.as_ordered_factors()
        else:
            args = list(product.args)

        # If quantities are present append them at the back
        args = sorted(args, key=lambda x: isinstance(x, Quantity) or
                     (isinstance(x, Pow) and isinstance(x.base, Quantity)))

        # Gather terms for numerator/denominator
        for item in args:
            if item.is_commutative and item.is_Pow and item.exp.is_Rational and item.exp.is_negative:
                if item.exp != -1:
                    b.append(Pow(item.base, -item.exp, evaluate=False))
                else:
                    b.append(Pow(item.base, -item.exp))
            elif item.is_Rational and item is not S.Infinity:
                if item.p != 1:
                    a.append( Rational(item.p) )
                if item.q != 1:
                    b.append( Rational(item.q) )
            else:
                a.append(item)

        # Convert to pretty forms. Parentheses are added by `__mul__`.
        a = [self._print(ai) for ai in a]
        b = [self._print(bi) for bi in b]

        # Construct a pretty form
        if len(b) == 0:
            return prettyForm.__mul__(*a)
        else:
            if len(a) == 0:
                a.append( self._print(S.One) )
            return prettyForm.__mul__(*a)/prettyForm.__mul__(*b)

    # A helper function for _print_Pow to print x**(1/n)
    def _print_nth_root(self, base, root):
        bpretty = self._print(base)

        # In very simple cases, use a single-char root sign
        if (self._settings['use_unicode_sqrt_char'] and self._use_unicode
            and root == 2 and bpretty.height() == 1
            and (bpretty.width() == 1
                 or (base.is_Integer and base.is_nonnegative))):
            return prettyForm(*bpretty.left(nth_root[2]))

        # Construct root sign, start with the \/ shape
        _zZ = xobj('/', 1)
        rootsign = xobj('\\', 1) + _zZ
        # Constructing the number to put on root
        rpretty = self._print(root)
        # roots look bad if they are not a single line
        if rpretty.height() != 1:
            return self._print(base)**self._print(1/root)
        # If power is half, no number should appear on top of root sign
        exp = '' if root == 2 else str(rpretty).ljust(2)
        if len(exp) > 2:
            rootsign = ' '*(len(exp) - 2) + rootsign
        # Stack the exponent
        rootsign = stringPict(exp + '\n' + rootsign)
        rootsign.baseline = 0
        # Diagonal: length is one less than height of base
        linelength = bpretty.height() - 1
        diagonal = stringPict('\n'.join(
            ' '*(linelength - i - 1) + _zZ + ' '*i
            for i in range(linelength)
        ))
        # Put baseline just below lowest line: next to exp
        diagonal.baseline = linelength - 1
        # Make the root symbol
        rootsign = prettyForm(*rootsign.right(diagonal))
        # Det the baseline to match contents to fix the height
        # but if the height of bpretty is one, the rootsign must be one higher
        rootsign.baseline = max(1, bpretty.baseline)
        #build result
        s = prettyForm(hobj('_', 2 + bpretty.width()))
        s = prettyForm(*bpretty.above(s))
        s = prettyForm(*s.left(rootsign))
        return s

    def _print_Pow(self, power):
        from sympy.simplify.simplify import fraction
        b, e = power.as_base_exp()
        if power.is_commutative:
            if e is S.NegativeOne:
                return prettyForm("1")/self._print(b)
            n, d = fraction(e)
            if n is S.One and d.is_Atom and not e.is_Integer and (e.is_Rational or d.is_Symbol) \
                    and self._settings['root_notation']:
                return self._print_nth_root(b, d)
            if e.is_Rational and e < 0:
                return prettyForm("1")/self._print(Pow(b, -e, evaluate=False))

        if b.is_Relational:
            return prettyForm(*self._print(b).parens()).__pow__(self._print(e))

        return self._print(b)**self._print(e)

    def _print_UnevaluatedExpr(self, expr):
        return self._print(expr.args[0])

    def __print_numer_denom(self, p, q):
        if q == 1:
            if p < 0:
                return prettyForm(str(p), binding=prettyForm.NEG)
            else:
                return prettyForm(str(p))
        elif abs(p) >= 10 and abs(q) >= 10:
            # If more than one digit in numer and denom, print larger fraction
            if p < 0:
                return prettyForm(str(p), binding=prettyForm.NEG)/prettyForm(str(q))
                # Old printing method:
                #pform = prettyForm(str(-p))/prettyForm(str(q))
                #return prettyForm(binding=prettyForm.NEG, *pform.left('- '))
            else:
                return prettyForm(str(p))/prettyForm(str(q))
        else:
            return None

    def _print_Rational(self, expr):
        result = self.__print_numer_denom(expr.p, expr.q)

        if result is not None:
            return result
        else:
            return self.emptyPrinter(expr)

    def _print_Fraction(self, expr):
        result = self.__print_numer_denom(expr.numerator, expr.denominator)

        if result is not None:
            return result
        else:
            return self.emptyPrinter(expr)

    def _print_ProductSet(self, p):
        if len(p.sets) >= 1 and not has_variety(p.sets):
            return self._print(p.sets[0]) ** self._print(len(p.sets))
        else:
            prod_char = pretty_atom('Multiplication') if self._use_unicode else 'x'
            return self._print_seq(p.sets, None, None, ' %s ' % prod_char,
                                   parenthesize=lambda set: set.is_Union or
                                   set.is_Intersection or set.is_ProductSet)

    def _print_FiniteSet(self, s):
        items = sorted(s.args, key=default_sort_key)
        return self._print_seq(items, '{', '}', ', ' )

    def _print_Range(self, s):

        if self._use_unicode:
            dots = pretty_atom('Dots')
        else:
            dots = '...'

        if s.start.is_infinite and s.stop.is_infinite:
            if s.step.is_positive:
                printset = dots, -1, 0, 1, dots
            else:
                printset = dots, 1, 0, -1, dots
        elif s.start.is_infinite:
            printset = dots, s[-1] - s.step, s[-1]
        elif s.stop.is_infinite:
            it = iter(s)
            printset = next(it), next(it), dots
        elif len(s) > 4:
            it = iter(s)
            printset = next(it), next(it), dots, s[-1]
        else:
            printset = tuple(s)

        return self._print_seq(printset, '{', '}', ', ' )

    def _print_Interval(self, i):
        if i.start == i.end:
            return self._print_seq(i.args[:1], '{', '}')

        else:
            if i.left_open:
                left = '('
            else:
                left = '['

            if i.right_open:
                right = ')'
            else:
                right = ']'

            return self._print_seq(i.args[:2], left, right)

    def _print_AccumulationBounds(self, i):
        left = '<'
        right = '>'

        return self._print_seq(i.args[:2], left, right)

    def _print_Intersection(self, u):

        delimiter = ' %s ' % pretty_atom('Intersection', 'n')

        return self._print_seq(u.args, None, None, delimiter,
                               parenthesize=lambda set: set.is_ProductSet or
                               set.is_Union or set.is_Complement)

    def _print_Union(self, u):

        union_delimiter = ' %s ' % pretty_atom('Union', 'U')

        return self._print_seq(u.args, None, None, union_delimiter,
                               parenthesize=lambda set: set.is_ProductSet or
                               set.is_Intersection or set.is_Complement)

    def _print_SymmetricDifference(self, u):
        if not self._use_unicode:
            raise NotImplementedError("ASCII pretty printing of SymmetricDifference is not implemented")

        sym_delimeter = ' %s ' % pretty_atom('SymmetricDifference')

        return self._print_seq(u.args, None, None, sym_delimeter)

    def _print_Complement(self, u):

        delimiter = r' \ '

        return self._print_seq(u.args, None, None, delimiter,
             parenthesize=lambda set: set.is_ProductSet or set.is_Intersection
                               or set.is_Union)

    def _print_ImageSet(self, ts):
        if self._use_unicode:
            inn = pretty_atom("SmallElementOf")
        else:
            inn = 'in'
        fun = ts.lamda
        sets = ts.base_sets
        signature = fun.signature
        expr = self._print(fun.expr)

        # TODO: the stuff to the left of the | and the stuff to the right of
        # the | should have independent baselines, that way something like
        # ImageSet(Lambda(x, 1/x**2), S.Naturals) prints the "x in N" part
        # centered on the right instead of aligned with the fraction bar on
        # the left. The same also applies to ConditionSet and ComplexRegion
        if len(signature) == 1:
            S = self._print_seq((signature[0], inn, sets[0]),
                                delimiter=' ')
            return self._hprint_vseparator(expr, S,
                                           left='{', right='}',
                                           ifascii_nougly=True, delimiter=' ')
        else:
            pargs = tuple(j for var, setv in zip(signature, sets) for j in
                          (var, ' ', inn, ' ', setv, ", "))
            S = self._print_seq(pargs[:-1], delimiter='')
            return self._hprint_vseparator(expr, S,
                                           left='{', right='}',
                                           ifascii_nougly=True, delimiter=' ')

    def _print_ConditionSet(self, ts):
        if self._use_unicode:
            inn = pretty_atom('SmallElementOf')
            # using _and because and is a keyword and it is bad practice to
            # overwrite them
            _and = pretty_atom('And')
        else:
            inn = 'in'
            _and = 'and'

        variables = self._print_seq(Tuple(ts.sym))
        as_expr = getattr(ts.condition, 'as_expr', None)
        if as_expr is not None:
            cond = self._print(ts.condition.as_expr())
        else:
            cond = self._print(ts.condition)
            if self._use_unicode:
                cond = self._print(cond)
                cond = prettyForm(*cond.parens())

        if ts.base_set is S.UniversalSet:
            return self._hprint_vseparator(variables, cond, left="{",
                                           right="}", ifascii_nougly=True,
                                           delimiter=' ')

        base = self._print(ts.base_set)
        C = self._print_seq((variables, inn, base, _and, cond),
                            delimiter=' ')
        return self._hprint_vseparator(variables, C, left="{", right="}",
                                       ifascii_nougly=True, delimiter=' ')

    def _print_ComplexRegion(self, ts):
        if self._use_unicode:
            inn = pretty_atom('SmallElementOf')
        else:
            inn = 'in'
        variables = self._print_seq(ts.variables)
        expr = self._print(ts.expr)
        prodsets = self._print(ts.sets)

        C = self._print_seq((variables, inn, prodsets),
                            delimiter=' ')
        return self._hprint_vseparator(expr, C, left="{", right="}",
                                       ifascii_nougly=True, delimiter=' ')

    def _print_Contains(self, e):
        var, set = e.args
        if self._use_unicode:
            el = f" {pretty_atom('ElementOf')} "
            return prettyForm(*stringPict.next(self._print(var),
                                               el, self._print(set)), binding=8)
        else:
            return prettyForm(sstr(e))

    def _print_FourierSeries(self, s):
        if s.an.formula is S.Zero and s.bn.formula is S.Zero:
            return self._print(s.a0)
        if self._use_unicode:
            dots = pretty_atom('Dots')
        else:
            dots = '...'
        return self._print_Add(s.truncate()) + self._print(dots)

    def _print_FormalPowerSeries(self, s):
        return self._print_Add(s.infinite)

    def _print_SetExpr(self, se):
        pretty_set = prettyForm(*self._print(se.set).parens())
        pretty_name = self._print(Symbol("SetExpr"))
        return prettyForm(*pretty_name.right(pretty_set))

    def _print_SeqFormula(self, s):
        if self._use_unicode:
            dots = pretty_atom('Dots')
        else:
            dots = '...'

        if len(s.start.free_symbols) > 0 or len(s.stop.free_symbols) > 0:
            raise NotImplementedError("Pretty printing of sequences with symbolic bound not implemented")

        if s.start is S.NegativeInfinity:
            stop = s.stop
            printset = (dots, s.coeff(stop - 3), s.coeff(stop - 2),
                s.coeff(stop - 1), s.coeff(stop))
        elif s.stop is S.Infinity or s.length > 4:
            printset = s[:4]
            printset.append(dots)
            printset = tuple(printset)
        else:
            printset = tuple(s)
        return self._print_list(printset)

    _print_SeqPer = _print_SeqFormula
    _print_SeqAdd = _print_SeqFormula
    _print_SeqMul = _print_SeqFormula

    def _print_seq(self, seq, left=None, right=None, delimiter=', ',
            parenthesize=lambda x: False, ifascii_nougly=True):

        pforms = []
        for item in seq:
            pform = self._print(item)
            if parenthesize(item):
                pform = prettyForm(*pform.parens())
            if pforms:
                pforms.append(delimiter)
            pforms.append(pform)

        if not pforms:
            s = stringPict('')
        else:
            s = prettyForm(*stringPict.next(*pforms))

        s = prettyForm(*s.parens(left, right, ifascii_nougly=ifascii_nougly))
        return s

    def join(self, delimiter, args):
        pform = None

        for arg in args:
            if pform is None:
                pform = arg
            else:
                pform = prettyForm(*pform.right(delimiter))
                pform = prettyForm(*pform.right(arg))

        if pform is None:
            return prettyForm("")
        else:
            return pform

    def _print_list(self, l):
        return self._print_seq(l, '[', ']')

    def _print_tuple(self, t):
        if len(t) == 1:
            ptuple = prettyForm(*stringPict.next(self._print(t[0]), ','))
            return prettyForm(*ptuple.parens('(', ')', ifascii_nougly=True))
        else:
            return self._print_seq(t, '(', ')')

    def _print_Tuple(self, expr):
        return self._print_tuple(expr)

    def _print_dict(self, d):
        keys = sorted(d.keys(), key=default_sort_key)
        items = []

        for k in keys:
            K = self._print(k)
            V = self._print(d[k])
            s = prettyForm(*stringPict.next(K, ': ', V))

            items.append(s)

        return self._print_seq(items, '{', '}')

    def _print_Dict(self, d):
        return self._print_dict(d)

    def _print_set(self, s):
        if not s:
            return prettyForm('set()')
        items = sorted(s, key=default_sort_key)
        pretty = self._print_seq(items)
        pretty = prettyForm(*pretty.parens('{', '}', ifascii_nougly=True))
        return pretty

    def _print_frozenset(self, s):
        if not s:
            return prettyForm('frozenset()')
        items = sorted(s, key=default_sort_key)
        pretty = self._print_seq(items)
        pretty = prettyForm(*pretty.parens('{', '}', ifascii_nougly=True))
        pretty = prettyForm(*pretty.parens('(', ')', ifascii_nougly=True))
        pretty = prettyForm(*stringPict.next(type(s).__name__, pretty))
        return pretty

    def _print_UniversalSet(self, s):
        if self._use_unicode:
            return prettyForm(pretty_atom('Universe'))
        else:
            return prettyForm('UniversalSet')

    def _print_PolyRing(self, ring):
        return prettyForm(sstr(ring))

    def _print_FracField(self, field):
        return prettyForm(sstr(field))

    def _print_FreeGroupElement(self, elm):
        return prettyForm(str(elm))

    def _print_PolyElement(self, poly):
        return prettyForm(sstr(poly))

    def _print_FracElement(self, frac):
        return prettyForm(sstr(frac))

    def _print_AlgebraicNumber(self, expr):
        if expr.is_aliased:
            return self._print(expr.as_poly().as_expr())
        else:
            return self._print(expr.as_expr())

    def _print_ComplexRootOf(self, expr):
        args = [self._print_Add(expr.expr, order='lex'), expr.index]
        pform = prettyForm(*self._print_seq(args).parens())
        pform = prettyForm(*pform.left('CRootOf'))
        return pform

    def _print_RootSum(self, expr):
        args = [self._print_Add(expr.expr, order='lex')]

        if expr.fun is not S.IdentityFunction:
            args.append(self._print(expr.fun))

        pform = prettyForm(*self._print_seq(args).parens())
        pform = prettyForm(*pform.left('RootSum'))

        return pform

    def _print_FiniteField(self, expr):
        if self._use_unicode:
            form = f"{pretty_atom('Integers')}_%d"
        else:
            form = 'GF(%d)'

        return prettyForm(pretty_symbol(form % expr.mod))

    def _print_IntegerRing(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_atom('Integers'))
        else:
            return prettyForm('ZZ')

    def _print_RationalField(self, expr):
        if self._use_unicode:
            return prettyForm(pretty_atom('Rationals'))
        else:
            return prettyForm('QQ')

    def _print_RealField(self, domain):
        if self._use_unicode:
            prefix = pretty_atom("Reals")
        else:
            prefix = 'RR'

        if domain.has_default_precision:
            return prettyForm(prefix)
        else:
            return self._print(pretty_symbol(prefix + "_" + str(domain.precision)))

    def _print_ComplexField(self, domain):
        if self._use_unicode:
            prefix = pretty_atom('Complexes')
        else:
            prefix = 'CC'

        if domain.has_default_precision:
            return prettyForm(prefix)
        else:
            return self._print(pretty_symbol(prefix + "_" + str(domain.precision)))

    def _print_PolynomialRing(self, expr):
        args = list(expr.symbols)

        if not expr.order.is_default:
            order = prettyForm(*prettyForm("order=").right(self._print(expr.order)))
            args.append(order)

        pform = self._print_seq(args, '[', ']')
        pform = prettyForm(*pform.left(self._print(expr.domain)))

        return pform

    def _print_FractionField(self, expr):
        args = list(expr.symbols)

        if not expr.order.is_default:
            order = prettyForm(*prettyForm("order=").right(self._print(expr.order)))
            args.append(order)

        pform = self._print_seq(args, '(', ')')
        pform = prettyForm(*pform.left(self._print(expr.domain)))

        return pform

    def _print_PolynomialRingBase(self, expr):
        g = expr.symbols
        if str(expr.order) != str(expr.default_order):
            g = g + ("order=" + str(expr.order),)
        pform = self._print_seq(g, '[', ']')
        pform = prettyForm(*pform.left(self._print(expr.domain)))

        return pform

    def _print_GroebnerBasis(self, basis):
        exprs = [ self._print_Add(arg, order=basis.order)
                  for arg in basis.exprs ]
        exprs = prettyForm(*self.join(", ", exprs).parens(left="[", right="]"))

        gens = [ self._print(gen) for gen in basis.gens ]

        domain = prettyForm(
            *prettyForm("domain=").right(self._print(basis.domain)))
        order = prettyForm(
            *prettyForm("order=").right(self._print(basis.order)))

        pform = self.join(", ", [exprs] + gens + [domain, order])

        pform = prettyForm(*pform.parens())
        pform = prettyForm(*pform.left(basis.__class__.__name__))

        return pform

    def _print_Subs(self, e):
        pform = self._print(e.expr)
        pform = prettyForm(*pform.parens())

        h = pform.height() if pform.height() > 1 else 2
        rvert = stringPict(vobj('|', h), baseline=pform.baseline)
        pform = prettyForm(*pform.right(rvert))

        b = pform.baseline
        pform.baseline = pform.height() - 1
        pform = prettyForm(*pform.right(self._print_seq([
            self._print_seq((self._print(v[0]), xsym('=='), self._print(v[1])),
                delimiter='') for v in zip(e.variables, e.point) ])))

        pform.baseline = b
        return pform

    def _print_number_function(self, e, name):
        # Print name_arg[0] for one argument or name_arg[0](arg[1])
        # for more than one argument
        pform = prettyForm(name)
        arg = self._print(e.args[0])
        pform_arg = prettyForm(" "*arg.width())
        pform_arg = prettyForm(*pform_arg.below(arg))
        pform = prettyForm(*pform.right(pform_arg))
        if len(e.args) == 1:
            return pform
        m, x = e.args
        # TODO: copy-pasted from _print_Function: can we do better?
        prettyFunc = pform
        prettyArgs = prettyForm(*self._print_seq([x]).parens())
        pform = prettyForm(
            binding=prettyForm.FUNC, *stringPict.next(prettyFunc, prettyArgs))
        pform.prettyFunc = prettyFunc
        pform.prettyArgs = prettyArgs
        return pform

    def _print_euler(self, e):
        return self._print_number_function(e, "E")

    def _print_catalan(self, e):
        return self._print_number_function(e, "C")

    def _print_bernoulli(self, e):
        return self._print_number_function(e, "B")

    _print_bell = _print_bernoulli

    def _print_lucas(self, e):
        return self._print_number_function(e, "L")

    def _print_fibonacci(self, e):
        return self._print_number_function(e, "F")

    def _print_tribonacci(self, e):
        return self._print_number_function(e, "T")

    def _print_stieltjes(self, e):
        if self._use_unicode:
            return self._print_number_function(e, greek_unicode['gamma'])
        else:
            return self._print_number_function(e, "stieltjes")

    def _print_KroneckerDelta(self, e):
        pform = self._print(e.args[0])
        pform = prettyForm(*pform.right(prettyForm(',')))
        pform = prettyForm(*pform.right(self._print(e.args[1])))
        if self._use_unicode:
            a = stringPict(pretty_symbol('delta'))
        else:
            a = stringPict('d')
        b = pform
        top = stringPict(*b.left(' '*a.width()))
        bot = stringPict(*a.right(' '*b.width()))
        return prettyForm(binding=prettyForm.POW, *bot.below(top))

    def _print_RandomDomain(self, d):
        if hasattr(d, 'as_boolean'):
            pform = self._print('Domain: ')
            pform = prettyForm(*pform.right(self._print(d.as_boolean())))
            return pform
        elif hasattr(d, 'set'):
            pform = self._print('Domain: ')
            pform = prettyForm(*pform.right(self._print(d.symbols)))
            pform = prettyForm(*pform.right(self._print(' in ')))
            pform = prettyForm(*pform.right(self._print(d.set)))
            return pform
        elif hasattr(d, 'symbols'):
            pform = self._print('Domain on ')
            pform = prettyForm(*pform.right(self._print(d.symbols)))
            return pform
        else:
            return self._print(None)

    def _print_DMP(self, p):
        try:
            if p.ring is not None:
                # TODO incorporate order
                return self._print(p.ring.to_sympy(p))
        except SympifyError:
            pass
        return self._print(repr(p))

    def _print_DMF(self, p):
        return self._print_DMP(p)

    def _print_Object(self, object):
        return self._print(pretty_symbol(object.name))

    def _print_Morphism(self, morphism):
        arrow = xsym("-->")

        domain = self._print(morphism.domain)
        codomain = self._print(morphism.codomain)
        tail = domain.right(arrow, codomain)[0]

        return prettyForm(tail)

    def _print_NamedMorphism(self, morphism):
        pretty_name = self._print(pretty_symbol(morphism.name))
        pretty_morphism = self._print_Morphism(morphism)
        return prettyForm(pretty_name.right(":", pretty_morphism)[0])

    def _print_IdentityMorphism(self, morphism):
        from sympy.categories import NamedMorphism
        return self._print_NamedMorphism(
            NamedMorphism(morphism.domain, morphism.codomain, "id"))

    def _print_CompositeMorphism(self, morphism):

        circle = xsym(".")

        # All components of the morphism have names and it is thus
        # possible to build the name of the composite.
        component_names_list = [pretty_symbol(component.name) for
                                component in morphism.components]
        component_names_list.reverse()
        component_names = circle.join(component_names_list) + ":"

        pretty_name = self._print(component_names)
        pretty_morphism = self._print_Morphism(morphism)
        return prettyForm(pretty_name.right(pretty_morphism)[0])

    def _print_Category(self, category):
        return self._print(pretty_symbol(category.name))

    def _print_Diagram(self, diagram):
        if not diagram.premises:
            # This is an empty diagram.
            return self._print(S.EmptySet)

        pretty_result = self._print(diagram.premises)
        if diagram.conclusions:
            results_arrow = " %s " % xsym("==>")

            pretty_conclusions = self._print(diagram.conclusions)[0]
            pretty_result = pretty_result.right(
                results_arrow, pretty_conclusions)

        return prettyForm(pretty_result[0])

    def _print_DiagramGrid(self, grid):
        from sympy.matrices import Matrix
        matrix = Matrix([[grid[i, j] if grid[i, j] else Symbol(" ")
                          for j in range(grid.width)]
                         for i in range(grid.height)])
        return self._print_matrix_contents(matrix)

    def _print_FreeModuleElement(self, m):
        # Print as row vector for convenience, for now.
        return self._print_seq(m, '[', ']')

    def _print_SubModule(self, M):
        gens = [[M.ring.to_sympy(g) for g in gen] for gen in M.gens]
        return self._print_seq(gens, '<', '>')

    def _print_FreeModule(self, M):
        return self._print(M.ring)**self._print(M.rank)

    def _print_ModuleImplementedIdeal(self, M):
        sym = M.ring.to_sympy
        return self._print_seq([sym(x) for [x] in M._module.gens], '<', '>')

    def _print_QuotientRing(self, R):
        return self._print(R.ring) / self._print(R.base_ideal)

    def _print_QuotientRingElement(self, R):
        return self._print(R.ring.to_sympy(R)) + self._print(R.ring.base_ideal)

    def _print_QuotientModuleElement(self, m):
        return self._print(m.data) + self._print(m.module.killed_module)

    def _print_QuotientModule(self, M):
        return self._print(M.base) / self._print(M.killed_module)

    def _print_MatrixHomomorphism(self, h):
        matrix = self._print(h._sympy_matrix())
        matrix.baseline = matrix.height() // 2
        pform = prettyForm(*matrix.right(' : ', self._print(h.domain),
            ' %s> ' % hobj('-', 2), self._print(h.codomain)))
        return pform

    def _print_Manifold(self, manifold):
        return self._print(manifold.name)

    def _print_Patch(self, patch):
        return self._print(patch.name)

    def _print_CoordSystem(self, coords):
        return self._print(coords.name)

    def _print_BaseScalar(self, expr):
        coord_sys_name, scalar_name = expr.name.split(".")
        if scalar_name in greek_unicode:
            scalar_name = greek_unicode[scalar_name]
        return self._print(pretty_symbol(scalar_name + "_" + coord_sys_name))

    def _print_BaseScalarField(self, field):
        string = field._coord_sys.symbols[field._index].name
        return self._print(pretty_symbol(string))

    def _print_BaseVectorField(self, field):
        s = U('PARTIAL DIFFERENTIAL') + '_' + field._coord_sys.symbols[field._index].name
        return self._print(pretty_symbol(s))

    def _print_Differential(self, diff):
        if self._use_unicode:
            d = pretty_atom('Differential')
        else:
            d = 'd'
        field = diff._form_field
        if hasattr(field, '_coord_sys'):
            string = field._coord_sys.symbols[field._index].name
            return self._print(d + ' ' + pretty_symbol(string))
        else:
            pform = self._print(field)
            pform = prettyForm(*pform.parens())
            return prettyForm(*pform.left(d))

    def _print_Tr(self, p):
        #TODO: Handle indices
        pform = self._print(p.args[0])
        pform = prettyForm(*pform.left('%s(' % (p.__class__.__name__)))
        pform = prettyForm(*pform.right(')'))
        return pform

    def _print_primenu(self, e):
        pform = self._print(e.args[0])
        pform = prettyForm(*pform.parens())
        if self._use_unicode:
            pform = prettyForm(*pform.left(greek_unicode['nu']))
        else:
            pform = prettyForm(*pform.left('nu'))
        return pform

    def _print_primeomega(self, e):
        pform = self._print(e.args[0])
        pform = prettyForm(*pform.parens())
        if self._use_unicode:
            pform = prettyForm(*pform.left(greek_unicode['Omega']))
        else:
            pform = prettyForm(*pform.left('Omega'))
        return pform

    def _print_Quantity(self, e):
        if e.name.name == 'degree':
            if self._use_unicode:
                pform = self._print(pretty_atom('Degree'))
            else:
                pform = self._print(chr(176))
            return pform
        else:
            return self.emptyPrinter(e)

    def _print_AssignmentBase(self, e):

        op = prettyForm(' ' + xsym(e.op) + ' ')

        l = self._print(e.lhs)
        r = self._print(e.rhs)
        pform = prettyForm(*stringPict.next(l, op, r))
        return pform

    def _print_Str(self, s):
        return self._print(s.name)


@print_function(PrettyPrinter)
def pretty(expr, **settings):
    """Returns a string containing the prettified form of expr.

    For information on keyword arguments see pretty_print function.

    """
    pp = PrettyPrinter(settings)

    # XXX: this is an ugly hack, but at least it works
    use_unicode = pp._settings['use_unicode']
    uflag = pretty_use_unicode(use_unicode)

    try:
        return pp.doprint(expr)
    finally:
        pretty_use_unicode(uflag)


def pretty_print(expr, **kwargs):
    """Prints expr in pretty form.

    pprint is just a shortcut for this function.

    Parameters
    ==========

    expr : expression
        The expression to print.

    wrap_line : bool, optional (default=True)
        Line wrapping enabled/disabled.

    num_columns : int or None, optional (default=None)
        Number of columns before line breaking (default to None which reads
        the terminal width), useful when using SymPy without terminal.

    use_unicode : bool or None, optional (default=None)
        Use unicode characters, such as the Greek letter pi instead of
        the string pi.

    full_prec : bool or string, optional (default="auto")
        Use full precision.

    order : bool or string, optional (default=None)
        Set to 'none' for long expressions if slow; default is None.

    use_unicode_sqrt_char : bool, optional (default=True)
        Use compact single-character square root symbol (when unambiguous).

    root_notation : bool, optional (default=True)
        Set to 'False' for printing exponents of the form 1/n in fractional form.
        By default exponent is printed in root form.

    mat_symbol_style : string, optional (default="plain")
        Set to "bold" for printing MatrixSymbols using a bold mathematical symbol face.
        By default the standard face is used.

    imaginary_unit : string, optional (default="i")
        Letter to use for imaginary unit when use_unicode is True.
        Can be "i" (default) or "j".
    """
    print(pretty(expr, **kwargs))

pprint = pretty_print


def pager_print(expr, **settings):
    """Prints expr using the pager, in pretty form.

    This invokes a pager command using pydoc. Lines are not wrapped
    automatically. This routine is meant to be used with a pager that allows
    sideways scrolling, like ``less -S``.

    Parameters are the same as for ``pretty_print``. If you wish to wrap lines,
    pass ``num_columns=None`` to auto-detect the width of the terminal.

    """
    from pydoc import pager
    from locale import getpreferredencoding
    if 'num_columns' not in settings:
        settings['num_columns'] = 500000  # disable line wrap
    pager(pretty(expr, **settings).encode(getpreferredencoding()))
