"""Symbolic inner product."""

from sympy.core.expr import Expr
from sympy.core.kind import NumberKind
from sympy.functions.elementary.complexes import conjugate
from sympy.printing.pretty.stringpict import prettyForm
from sympy.physics.quantum.dagger import Dagger


__all__ = [
    'InnerProduct'
]


# InnerProduct is not an QExpr because it is really just a regular commutative
# number. We have gone back and forth about this, but we gain a lot by having
# it subclass Expr. The main challenges were getting Dagger to work
# (we use _eval_conjugate) and represent (we can use atoms and subs). Having
# it be an Expr, mean that there are no commutative QExpr subclasses,
# which simplifies the design of everything.

class InnerProduct(Expr):
    """An unevaluated inner product between a Bra and a Ket [1].

    Parameters
    ==========

    bra : BraBase or subclass
        The bra on the left side of the inner product.
    ket : KetBase or subclass
        The ket on the right side of the inner product.

    Examples
    ========

    Create an InnerProduct and check its properties:

        >>> from sympy.physics.quantum import Bra, Ket
        >>> b = Bra('b')
        >>> k = Ket('k')
        >>> ip = b*k
        >>> ip
        <b|k>
        >>> ip.bra
        <b|
        >>> ip.ket
        |k>

    In quantum expressions, inner products will be automatically
    identified and created::

        >>> b*k
        <b|k>

    In more complex expressions, where there is ambiguity in whether inner or
    outer products should be created, inner products have high priority::

        >>> k*b*k*b
        <b|k>*|k><b|

    Notice how the inner product <b|k> moved to the left of the expression
    because inner products are commutative complex numbers.

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Inner_product
    """

    kind = NumberKind

    is_complex = True

    def __new__(cls, bra, ket):
        # Keep the import of BraBase and KetBase here to avoid problems
        # with circular imports.
        from sympy.physics.quantum.state import KetBase, BraBase
        if not isinstance(ket, KetBase):
            raise TypeError('KetBase subclass expected, got: %r' % ket)
        if not isinstance(bra, BraBase):
            raise TypeError('BraBase subclass expected, got: %r' % ket)
        obj = Expr.__new__(cls, bra, ket)
        return obj

    @property
    def bra(self):
        return self.args[0]

    @property
    def ket(self):
        return self.args[1]

    def _eval_conjugate(self):
        return InnerProduct(Dagger(self.ket), Dagger(self.bra))

    def _sympyrepr(self, printer, *args):
        return '%s(%s,%s)' % (self.__class__.__name__,
            printer._print(self.bra, *args), printer._print(self.ket, *args))

    def _sympystr(self, printer, *args):
        sbra = printer._print(self.bra)
        sket = printer._print(self.ket)
        return '%s|%s' % (sbra[:-1], sket[1:])

    def _pretty(self, printer, *args):
        # Print state contents
        bra = self.bra._print_contents_pretty(printer, *args)
        ket = self.ket._print_contents_pretty(printer, *args)
        # Print brackets
        height = max(bra.height(), ket.height())
        use_unicode = printer._use_unicode
        lbracket, _ = self.bra._pretty_brackets(height, use_unicode)
        cbracket, rbracket = self.ket._pretty_brackets(height, use_unicode)
        # Build innerproduct
        pform = prettyForm(*bra.left(lbracket))
        pform = prettyForm(*pform.right(cbracket))
        pform = prettyForm(*pform.right(ket))
        pform = prettyForm(*pform.right(rbracket))
        return pform

    def _latex(self, printer, *args):
        bra_label = self.bra._print_contents_latex(printer, *args)
        ket = printer._print(self.ket, *args)
        return r'\left\langle %s \right. %s' % (bra_label, ket)

    def doit(self, **hints):
        try:
            r = self.ket._eval_innerproduct(self.bra, **hints)
        except NotImplementedError:
            try:
                r = conjugate(
                    self.bra.dual._eval_innerproduct(self.ket.dual, **hints)
                )
            except NotImplementedError:
                r = None
        if r is not None:
            return r
        return self
