"""ASCII Printer"""
from ..core.exceptions import BasisNotSetError
from ..utils.singleton import Singleton
from ._precedence import PRECEDENCE, precedence
from .base import QalgebraBasePrinter
from .sympy import SympyStrPrinter


__all__ = []
__private__ = ['QalgebraAsciiPrinter', 'QalgebraAsciiDefaultPrinter']


class QalgebraAsciiPrinter(QalgebraBasePrinter):
    r"""Printer for a string (ASCII) representation.

    Attributes:
        _parenth_left (str): String to use for a left parenthesis
            (e.g. '\left(' in LaTeX).
        _parenth_left (str): String to use for a right parenthesis
        _dagger_sym (str): Symbol that indicates the complex conjugate of an
            operator.
        _tensor_sym (str): Symbol to use for tensor products.
    """

    sympy_printer_cls = SympyStrPrinter
    printmethod = '_ascii'

    _default_settings = {
        'show_hs_label': True,  # alternatively: False, 'subscript'
        'sig_as_ketbra': True,
    }

    _parenth_left = '('
    _parenth_right = ')'
    _bracket_left = '['
    _bracket_right = ']'
    _dagger_sym = 'H'
    _tensor_sym = '*'  # used by _render_hs_label
    _product_sym = '*'
    _cid = 'cid(%d)'
    _sum_sym = 'Sum'
    _element_sym = 'in'
    _ellipsis = '...'
    _set_delim_left = '{'
    _set_delim_right = '}'

    @property
    def _spaced_product_sym(self):
        if len(self._product_sym.strip()) == 0:
            return self._product_sym
        else:
            return " %s " % self._product_sym

    def _split_identifier(self, identifier):
        """Split the given identifier at the first underscore into (rendered)
        name and subscript. Both `name` and `subscript` are rendered as
        strings"""
        try:
            name, subscript = identifier.split("_", 1)
        except (TypeError, ValueError, AttributeError):
            name = identifier
            subscript = ''
        return self._render_str(name), self._render_str(subscript)

    def _split_op(self, identifier, hs_label=None, dagger=False, args=None):
        """Return `name`, total `subscript`, total `superscript` and
        `arguments` str. All of the returned strings are fully rendered.

        Args:
            identifier (str or SymbolicLabelBase): A (non-rendered/ascii)
                identifier that may include a subscript. The output `name` will
                be the `identifier` without any subscript
            hs_label (str): The rendered label for the Hilbert space of the
                operator, or None. Returned unchanged.
            dagger (bool): Flag to indicate whether the operator is daggered.
                If True, :attr:`dagger_sym` will be included in the
                `superscript` (or  `subscript`, depending on the settings)
            args (list or None): List of arguments (expressions). Each element
                will be rendered with :meth:`doprint`. The total list of args
                will then be joined with commas, enclosed
                with :attr:`_parenth_left` and :attr:`parenth_right`, and
                returnd as the `arguments` string
        """
        if self._isinstance(identifier, 'SymbolicLabelBase'):
            identifier = QalgebraAsciiDefaultPrinter()._print_SCALAR_TYPES(
                identifier.expr
            )
        name, total_subscript = self._split_identifier(identifier)
        total_superscript = ''
        if hs_label not in [None, '']:
            if self._settings['show_hs_label'] == 'subscript':
                if len(total_subscript) == 0:
                    total_subscript = '(' + hs_label + ')'
                else:
                    total_subscript += ',(' + hs_label + ')'
            else:
                total_superscript += '(' + hs_label + ')'
        if dagger:
            total_superscript += self._dagger_sym
        args_str = ''
        if (args is not None) and (len(args) > 0):
            args_str = (
                self._parenth_left
                + ",".join([self.doprint(arg) for arg in args])
                + self._parenth_right
            )
        return name, total_subscript, total_superscript, args_str

    @classmethod
    def _is_single_letter(cls, label):
        return len(label) == 1

    def _render_hs_label(self, hs):
        """Return the label of the given Hilbert space as a string"""
        if isinstance(hs.__class__, Singleton):
            return self._render_str(hs.label)
        else:
            return self._tensor_sym.join(
                [self._render_str(ls.label) for ls in hs.local_factors]
            )

    def _render_state_label(self, label):
        if self._isinstance(label, 'SymbolicLabelBase'):
            return self._print_SCALAR_TYPES(label.expr)
        else:
            return self._render_str(label)

    def _braket_fmt(self, expr_type):
        """Return a format string for printing an `expr_type`
        ket/bra/ketbra/braket"""
        mapping = {
            'bra': {
                True: '<{label}|^({space})',
                'subscript': '<{label}|_({space})',
                False: '<{label}|',
            },
            'ket': {
                True: '|{label}>^({space})',
                'subscript': '|{label}>_({space})',
                False: '|{label}>',
            },
            'ketbra': {
                True: '|{label_i}><{label_j}|^({space})',
                'subscript': '|{label_i}><{label_j}|_({space})',
                False: '|{label_i}><{label_j}|',
            },
            'braket': {
                True: '<{label_i}|{label_j}>^({space})',
                'subscript': '<{label_i}|{label_j}>_({space})',
                False: '<{label_i}|{label_j}>',
            },
        }
        hs_setting = bool(self._settings['show_hs_label'])
        if self._settings['show_hs_label'] == 'subscript':
            hs_setting = 'subscript'
        return mapping[expr_type][hs_setting]

    def _render_op(
        self, identifier, hs=None, dagger=False, args=None, superop=False
    ):
        """Render an operator

        Args:
            identifier (str or SymbolicLabelBase): The identifier (name/symbol)
                of the operator. May include a subscript, denoted by '_'.
            hs (qalgebra.hilbert_space_algebra.HilbertSpace): The Hilbert
                space in which the operator is defined
            dagger (bool): Whether the operator should be daggered
            args (list): A list of expressions that will be rendered with
                :meth:`doprint`, joined with commas, enclosed in parenthesis
            superop (bool): Whether the operator is a super-operator
        """
        hs_label = None
        if hs is not None and self._settings['show_hs_label']:
            hs_label = self._render_hs_label(hs)
        name, total_subscript, total_superscript, args_str = self._split_op(
            identifier, hs_label, dagger, args
        )
        res = name
        if len(total_subscript) > 0:
            res += "_" + total_subscript
        if len(total_superscript) > 0:
            res += "^" + total_superscript
        if len(args_str) > 0:
            res += args_str
        return res

    def parenthesize(self, expr, level, *args, strict=False, **kwargs):
        """Render `expr` and wrap the result in parentheses if the precedence
        of `expr` is below the given `level` (or at the given `level` if
        `strict` is True. Extra `args` and `kwargs` are passed to the internal
        `doit` renderer"""
        needs_parenths = (precedence(expr) < level) or (
            strict and precedence(expr) == level
        )
        if needs_parenths:
            return (
                self._parenth_left
                + self.doprint(expr, *args, **kwargs)
                + self._parenth_right
            )
        else:
            return self.doprint(expr, *args, **kwargs)

    def _print_tuple(self, expr):
        return (
            self._parenth_left
            + ", ".join([self.doprint(c) for c in expr])
            + self._parenth_right
        )

    def _print_list(self, expr):
        return (
            self._bracket_left
            + ", ".join([self.doprint(c) for c in expr])
            + self._bracket_right
        )

    def _print_HilbertSpace(self, expr):
        return r'H_{label}'.format(label=self._render_hs_label(expr))

    def _print_ProductSpace(self, expr):
        tensor_sym = " " + self._tensor_sym + " "
        return tensor_sym.join([self.doprint(op) for op in expr.operands])

    def _print_OperatorSymbol(self, expr, adjoint=False):
        res = self._render_op(expr.label, expr._hs, dagger=adjoint)
        if len(expr.sym_args) > 0:
            res += (
                self._parenth_left
                + ", ".join([self.doprint(arg) for arg in expr.sym_args])
                + self._parenth_right
            )
        return res

    def _print_LocalOperator(self, expr, adjoint=False):
        if adjoint:
            dagger = not expr._dagger
        else:
            dagger = expr._dagger
        return self._render_op(
            expr.identifier, expr._hs, dagger=dagger, args=expr.args
        )

    def _print_LocalSigma(self, expr, adjoint=False):
        if self._settings['sig_as_ketbra']:
            fmt = self._braket_fmt('ketbra')
            if adjoint:
                return fmt.format(
                    label_i=self._render_state_label(expr.k),
                    label_j=self._render_state_label(expr.j),
                    space=self._render_hs_label(expr.space),
                )
            else:
                return fmt.format(
                    label_i=self._render_state_label(expr.j),
                    label_j=self._render_state_label(expr.k),
                    space=self._render_hs_label(expr.space),
                )
        else:
            if expr.j == expr.k:
                identifier = "%s_%s" % (expr._identifier_projector, expr.j)
            else:
                if adjoint:
                    identifier = "%s_%s,%s" % (expr.identifier, expr.k, expr.j)
                else:
                    identifier = "%s_%s,%s" % (expr.identifier, expr.j, expr.k)
            return self._render_op(identifier, expr._hs, dagger=adjoint)

    def _print_IdentityOperator(self, expr):
        return "1"

    def _print_ZeroOperator(self, expr):
        return "0"

    def _print_ScalarValue(self, expr, **kwargs):
        return self.doprint(expr.val, **kwargs)

    def _print_Zero(self, expr, **kwargs):
        return "0"

    def _print_One(self, expr, **kwargs):
        return "1"

    def _print_ScalarTimesQuantumExpression(self, expr, **kwargs):
        prec = PRECEDENCE['Mul']
        coeff, term = expr.coeff, expr.term
        term_str = self.doprint(term, **kwargs)
        if precedence(term) < prec:
            term_str = self._parenth_left + term_str + self._parenth_right

        if coeff == -1:
            if term_str.startswith(self._parenth_left):
                return "- " + term_str
            else:
                return "-" + term_str
        if 'adjoint' in kwargs:
            coeff_str = self.doprint(coeff, adjoint=kwargs['adjoint'])
        else:
            coeff_str = self.doprint(coeff)

        if term_str in [
            '1',
            self._print_IdentityOperator(expr),
            self._print_IdentitySuperOperator,
        ]:
            return coeff_str
        else:
            coeff_str = coeff_str.strip()
            if precedence(coeff) < prec and precedence(-coeff) < prec:
                # the above precedence check catches on only for true sums
                coeff_str = (
                    self._parenth_left + coeff_str + self._parenth_right
                )
            return coeff_str + self._spaced_product_sym + term_str.strip()

    def _print_QuantumPlus(self, expr, adjoint=False, superop=False):
        prec = precedence(expr)
        l = []
        kwargs = {}
        if adjoint:
            kwargs['adjoint'] = adjoint
        if superop:
            kwargs['superop'] = superop
        for term in expr.args:
            t = self.doprint(term, **kwargs)
            if t.startswith('-'):
                sign = "-"
                t = t[1:].strip()
            else:
                sign = "+"
            if precedence(term) < prec:
                l.extend([sign, self._parenth_left + t + self._parenth_right])
            else:
                l.extend([sign, t])
        try:
            sign = l.pop(0)
            if sign == '+':
                sign = ""
        except IndexError:
            sign = ""
        return sign + ' '.join(l)

    def _print_QuantumTimes(self, expr, **kwargs):
        prec = precedence(expr)
        return self._spaced_product_sym.join(
            [self.parenthesize(op, prec, **kwargs) for op in expr.operands]
        )

    def _print_Commutator(self, expr, adjoint=False):
        res = "[" + self.doprint(expr.A) + ", " + self.doprint(expr.B) + "]"
        if adjoint:
            res += "^" + self._dagger_sym
        return res

    def _print_OperatorTrace(self, expr, adjoint=False):
        s = self._render_hs_label(expr._over_space)
        kwargs = {}
        if adjoint:
            kwargs['adjoint'] = adjoint
        o = self.doprint(expr.operand, **kwargs)
        return r'tr_({space})[{operand}]'.format(space=s, operand=o)

    def _print_Adjoint(self, expr, adjoint=False):
        o = expr.operand
        if self._isinstance(o, 'LocalOperator'):
            if adjoint:
                dagger = o._dagger
            else:
                dagger = not o._dagger
            return self._render_op(
                o.identifier, hs=o.space, dagger=dagger, args=o.args[1:]
            )
        elif self._isinstance(o, 'OperatorSymbol'):
            return self._render_op(o.label, hs=o.space, dagger=(not adjoint))
        else:
            if adjoint:
                return self.doprint(o)
            else:
                return (
                    self._parenth_left
                    + self.doprint(o)
                    + self._parenth_right
                    + "^"
                    + self._dagger_sym
                )

    def _print_OperatorPlusMinusCC(self, expr):
        prec = precedence(expr)
        o = expr.operand
        sign_str = ' + '
        if expr._sign < 0:
            sign_str = ' - '
        return self.parenthesize(o, prec) + sign_str + "c.c."

    def _print_PseudoInverse(self, expr):
        prec = precedence(expr)
        return self.parenthesize(expr.operand, prec) + "^+"

    def _print_NullSpaceProjector(self, expr, adjoint=False):
        null_space_proj_sym = 'P_Ker'
        return self._render_op(
            null_space_proj_sym, hs=None, args=expr.operands, dagger=adjoint
        )

    def _print_KetSymbol(self, expr, adjoint=False):
        if adjoint:
            fmt = self._braket_fmt('bra')
        else:
            fmt = self._braket_fmt('ket')
        label = self._render_state_label(expr.label)
        if len(expr.sym_args) > 0:
            label += (
                self._parenth_left
                + ", ".join([self.doprint(arg) for arg in expr.sym_args])
                + self._parenth_right
            )
        return fmt.format(label=label, space=self._render_hs_label(expr.space))

    def _print_ZeroKet(self, expr, adjoint=False):
        return "0"

    def _print_TrivialKet(self, expr, adjoint=False):
        return "1"

    def _print_CoherentStateKet(self, expr, adjoint=False):
        if adjoint:
            fmt = self._braket_fmt('bra')
        else:
            fmt = self._braket_fmt('ket')
        label = self._render_state_label('alpha=') + self.doprint(expr._ampl)
        space = self._render_hs_label(expr.space)
        return fmt.format(label=label, space=space)

    def _print_TensorKet(self, expr, adjoint=False):
        if all(self._isinstance(o, 'BasisKet') for o in expr.operands):
            labels = [self._render_state_label(o.label) for o in expr.operands]
            single_letters = all([self._is_single_letter(l) for l in labels])
            try:
                small_hs = all(
                    [(o.space.dimension < 10) for o in expr.operands]
                )
            except BasisNotSetError:
                small_hs = False
            if small_hs and single_letters:
                joiner = ""
            else:
                joiner = ","
            label = joiner.join(labels)
            fmt = self._braket_fmt('ket')
            if adjoint:
                fmt = self._braket_fmt('bra')
            space = self._render_hs_label(expr.space)
            return fmt.format(label=label, space=space)
        else:
            prec = precedence(expr)
            kwargs = {}
            if adjoint:
                kwargs['adjoint'] = adjoint
            tensor_sym = " %s " % self._tensor_sym
            return tensor_sym.join(
                [self.parenthesize(op, prec, **kwargs) for op in expr.operands]
            )

    def _print_OperatorTimesKet(self, expr, adjoint=False):
        prec = precedence(expr)
        op, ket = expr.operator, expr.ket
        kwargs = {}
        if adjoint:
            kwargs['adjoint'] = adjoint
        rendered_op = self.parenthesize(op, prec, **kwargs)
        rendered_ket = self.parenthesize(ket, prec, **kwargs)
        if adjoint:
            return rendered_ket + " " + rendered_op
        else:
            return rendered_op + " " + rendered_ket

    def _print_IndexedSum(self, expr, adjoint=False):
        prec = precedence(expr)
        kwargs = {}
        if adjoint:
            kwargs['adjoint'] = adjoint
        indices = []
        bottom_rhs = None
        top = None
        res = ''
        # ranges with the same limits are grouped into the same sum symbol
        for index_range in expr.ranges:
            current_index = self.doprint(index_range, which='bottom_index')
            current_bottom_rhs = self.doprint(index_range, which='bottom_rhs')
            current_top = self.doprint(index_range, which='top')
            if top is not None:  # index_ranges after the first one
                if current_top != top or current_bottom_rhs != bottom_rhs:
                    res += self._sum_sym
                    bottom = ",".join(indices) + bottom_rhs
                    if len(bottom) > 0:
                        res += '_{%s}' % bottom
                    if len(top) > 0:
                        res += '^{%s}' % top
                    res += " "
                    indices = [
                        current_index,
                    ]
                    top = current_top
                    bottom_rhs = current_bottom_rhs
                else:
                    indices.append(current_index)
            else:  # first range
                indices.append(current_index)
                top = current_top
                bottom_rhs = current_bottom_rhs
        # add the final accumulated sum symbol
        res += self._sum_sym
        bottom = ",".join(indices) + bottom_rhs
        if len(bottom) > 0:
            res += '_{%s}' % bottom
        if len(top) > 0:
            res += '^{%s}' % top
        res += " " + self.parenthesize(expr.term, prec, strict=True, **kwargs)
        return res

    def _print_IndexRangeBase(self, expr, which='bottom'):
        assert which in ['bottom', 'bottom_index', 'bottom_rhs', 'top']
        if which in ['bottom', 'bottom_index']:
            return self.doprint(expr.index_symbol)
        else:
            return ''

    def _print_IndexOverFockSpace(self, expr, which='bottom'):
        assert which in ['bottom', 'bottom_index', 'bottom_rhs', 'top']
        if 'bottom' in which:
            bottom_index = self.doprint(expr.index_symbol)
            bottom_rhs = " " + self._element_sym + " " + self.doprint(expr.hs)
            if which == 'bottom_index':
                return bottom_index
            elif which == 'bottom_rhs':
                return bottom_rhs
            else:
                return bottom_index + bottom_rhs
        elif which == 'top':
            return ''
        else:
            raise ValueError("invalid `which`: %s" % which)

    def _print_IndexOverList(self, expr, which='bottom'):
        assert which in ['bottom', 'bottom_index', 'bottom_rhs', 'top']
        if 'bottom' in which:
            bottom_index = self.doprint(expr.index_symbol)
            bottom_rhs = (
                " "
                + self._element_sym
                + " "
                + self._set_delim_left
                + ",".join([self.doprint(val) for val in expr.values])
                + self._set_delim_right
            )
            if which == 'bottom_index':
                return bottom_index
            elif which == 'bottom_rhs':
                return bottom_rhs
            else:
                return bottom_index + bottom_rhs
        elif which == 'top':
            return ''
        else:
            raise ValueError("invalid `which`: %s" % which)

    def _print_IndexOverRange(self, expr, which='bottom'):
        assert which in ['bottom', 'bottom_index', 'bottom_rhs', 'top']
        if 'bottom' in which:
            bottom_index = self.doprint(expr.index_symbol)
            bottom_rhs = "=%s" % expr.start_from
            if abs(expr.step) > 1:
                bottom_rhs += ", %s" % expr.start_from + expr.step
                bottom_rhs += ", " + self._ellipsis
            if which == 'bottom_index':
                return bottom_index
            elif which == 'bottom_rhs':
                return bottom_rhs
            else:
                return bottom_index + bottom_rhs
        elif which == 'top':
            return str(expr.to)
        else:
            raise ValueError("invalid `which`: %s" % which)

    def _print_BaseLabel(self, expr):
        return self.doprint(expr.expr)

    def _print_Bra(self, expr, adjoint=False):
        return self.doprint(expr.ket, adjoint=(not adjoint))

    def _print_BraKet(self, expr, adjoint=False):
        trivial = True
        try:
            bra_label = self._render_state_label(expr.bra.label)
            bra = expr.bra.ket
            if hasattr(bra, 'sym_args') and len(bra.sym_args) > 0:
                bra_label += (
                    self._parenth_left
                    + ", ".join([self.doprint(arg) for arg in bra.sym_args])
                    + self._parenth_right
                )
        except AttributeError:
            trivial = False
        try:
            ket_label = self._render_state_label(expr.ket.label)
            if hasattr(expr.ket, 'sym_args') and len(expr.ket.sym_args) > 0:
                ket_label += (
                    self._parenth_left
                    + ", ".join(
                        [self.doprint(arg) for arg in expr.ket.sym_args]
                    )
                    + self._parenth_right
                )
        except AttributeError:
            trivial = False
        if trivial:
            fmt = self._braket_fmt('braket')
            if adjoint:
                return fmt.format(
                    label_i=ket_label,
                    label_j=bra_label,
                    space=self._render_hs_label(expr.ket.space),
                )
            else:
                return fmt.format(
                    label_i=bra_label,
                    label_j=ket_label,
                    space=self._render_hs_label(expr.ket.space),
                )
        else:
            prec = precedence(expr)
            rendered_bra = self.parenthesize(expr.bra, prec, adjoint=adjoint)
            rendered_ket = self.parenthesize(expr.ket, prec, adjoint=adjoint)
            if adjoint:
                return rendered_ket + self._spaced_product_sym + rendered_bra
            else:
                return rendered_bra + self._spaced_product_sym + rendered_ket

    def _print_KetBra(self, expr, adjoint=False):
        trivial = True
        try:
            bra_label = self._render_state_label(expr.bra.label)
            bra = expr.bra.ket
            if hasattr(bra, 'sym_args') and len(bra.sym_args) > 0:
                bra_label += (
                    self._parenth_left
                    + ", ".join([self.doprint(arg) for arg in bra.sym_args])
                    + self._parenth_right
                )
        except AttributeError:
            trivial = False
        try:
            ket_label = self._render_state_label(expr.ket.label)
            if hasattr(expr.ket, 'sym_args') and len(expr.ket.sym_args) > 0:
                ket_label += (
                    self._parenth_left
                    + ", ".join(
                        [self.doprint(arg) for arg in expr.ket.sym_args]
                    )
                    + self._parenth_right
                )
        except AttributeError:
            trivial = False
        if trivial:
            fmt = self._braket_fmt('ketbra')
            if adjoint:
                return fmt.format(
                    label_i=bra_label,
                    label_j=ket_label,
                    space=self._render_hs_label(expr.ket.space),
                )
            else:
                return fmt.format(
                    label_i=ket_label,
                    label_j=bra_label,
                    space=self._render_hs_label(expr.ket.space),
                )
        else:
            prec = precedence(expr)
            rendered_bra = self.parenthesize(expr.bra, prec, adjoint=adjoint)
            rendered_ket = self.parenthesize(expr.ket, prec, adjoint=adjoint)
            if adjoint:
                return rendered_bra + rendered_ket
            else:
                return rendered_ket + rendered_bra

    def _print_SuperOperatorSymbol(self, expr, adjoint=False, superop=True):
        res = self._render_op(
            expr.label, expr._hs, dagger=adjoint, superop=True
        )
        if len(expr.sym_args) > 0:
            res += (
                self._parenth_left
                + ", ".join([self.doprint(arg) for arg in expr.sym_args])
                + self._parenth_right
            )
        return res

    def _print_IdentitySuperOperator(self, expr, superop=True):
        return "1"

    def _print_ZeroSuperOperator(self, expr, superop=True):
        return "0"

    def _print_SuperOperatorPlus(self, expr, adjoint=False, superop=True):
        return self._print_QuantumPlus(expr, adjoint=adjoint, superop=True)

    def _print_SuperOperatorTimes(self, expr, adjoint=False, superop=True):
        kwargs = {}
        if adjoint:
            kwargs['adjoint'] = True
        return self._print_QuantumTimes(expr, superop=True, **kwargs)

    def _print_SuperAdjoint(self, expr, adjoint=False, superop=True):
        o = expr.operand
        if self._isinstance(o, 'SuperOperatorSymbol'):
            return self._render_op(
                o.label, hs=o.space, dagger=(not adjoint), superop=True
            )
        else:
            if adjoint:
                return self.doprint(o)
            else:
                return (
                    self._parenth_left
                    + self.doprint(o)
                    + self._parenth_right
                    + "^"
                    + self._dagger_sym
                )

    def _print_SPre(self, expr, superop=True):
        return (
            "SPre"
            + self._parenth_left
            + self.doprint(expr.operands[0])
            + self._parenth_right
        )

    def _print_SPost(self, expr, superop=True):
        return (
            "SPost"
            + self._parenth_left
            + self.doprint(expr.operands[0])
            + self._parenth_right
        )

    def _print_SuperOperatorTimesOperator(self, expr):
        prec = precedence(expr)
        sop, op = expr.sop, expr.op
        cs = self.parenthesize(sop, prec)
        ct = self.doprint(op)
        return "%s[%s]" % (cs, ct)

    def _print_QuantumDerivative(self, expr, adjoint=False):
        res = ""
        for sym, n in expr.derivs.items():
            sym_str = self.doprint(sym)
            if " " in sym_str:
                sym_str = "(%s)" % sym_str
            if n == 1:
                res += "D_%s " % sym_str
            else:
                res += "D_%s^%s " % (sym_str, n)
        res += self.parenthesize(expr.operand, PRECEDENCE['Mul'], strict=True)
        if expr.vals:
            evaluation_strs = []
            for sym, val in expr.vals.items():
                evaluation_strs.append(
                    "%s=%s" % (self.doprint(sym), self.doprint(val))
                )
            res += " |_(%s)" % ", ".join(evaluation_strs)
        if adjoint:
            res = r"(%s)^%s" % (res, self._dagger_sym)
        return res

    def _print_Matrix(self, expr):
        matrix_left_sym = '['
        matrix_right_sym = ']'
        matrix_row_left_sym = '['
        matrix_row_right_sym = ']'
        matrix_col_sep_sym = ', '
        matrix_row_sep_sym = ', '
        row_strs = []
        if len(expr.matrix) == 0:
            row_strs.append(matrix_row_left_sym + matrix_row_right_sym)
            row_strs.append(matrix_row_left_sym + matrix_row_right_sym)
        else:
            for row in expr.matrix:
                row_strs.append(
                    matrix_row_left_sym
                    + matrix_col_sep_sym.join(
                        [self.doprint(entry) for entry in row]
                    )
                    + matrix_row_right_sym
                )
        return (
            matrix_left_sym
            + matrix_row_sep_sym.join(row_strs)
            + matrix_right_sym
        )

    def _print_Eq(self, expr):
        # print for symbolic_equation.Eq, but also works for any
        # Eq class that has the minimum requirement to have an `lhs` and `rhs`
        # attribute
        try:
            return expr._render_str(renderer=self.doprint)
        except AttributeError:
            return self.doprint(expr.lhs) + ' = ' + self.doprint(expr.rhs)


class QalgebraAsciiDefaultPrinter(QalgebraAsciiPrinter):
    """Printer for an ASCII representation that accepts no settings. This can
    be used internally when a well-defined, static representation is needed
    (e.g. as a sort key)"""

    _default_settings = {}

    def __init__(self):
        super().__init__(cache=None, settings=None)
        self._settings = {'show_hs_label': True, 'sig_as_ketbra': True}
