"""The anti-commutator: ``{A,B} = A*B + B*A``."""

from sympy.core.expr import Expr
from sympy.core.kind import KindDispatcher
from sympy.core.mul import Mul
from sympy.core.numbers import Integer
from sympy.core.singleton import S
from sympy.printing.pretty.stringpict import prettyForm

from sympy.physics.quantum.dagger import Dagger
from sympy.physics.quantum.kind import _OperatorKind, OperatorKind

__all__ = [
    'AntiCommutator'
]

#-----------------------------------------------------------------------------
# Anti-commutator
#-----------------------------------------------------------------------------


class AntiCommutator(Expr):
    """The standard anticommutator, in an unevaluated state.

    Explanation
    ===========

    Evaluating an anticommutator is defined [1]_ as: ``{A, B} = A*B + B*A``.
    This class returns the anticommutator in an unevaluated form.  To evaluate
    the anticommutator, use the ``.doit()`` method.

    Canonical ordering of an anticommutator is ``{A, B}`` for ``A < B``. The
    arguments of the anticommutator are put into canonical order using
    comparison operators. If ``B < A``, then ``{A, B}`` is returned as ``{B, A}``.

    Parameters
    ==========

    A : Expr
        The first argument of the anticommutator {A,B}.
    B : Expr
        The second argument of the anticommutator {A,B}.

    Examples
    ========

    >>> from sympy import symbols
    >>> from sympy.physics.quantum import AntiCommutator
    >>> from sympy.physics.quantum import Operator, Dagger
    >>> x, y = symbols('x,y')
    >>> A = Operator('A')
    >>> B = Operator('B')

    Create an anticommutator and use ``doit()`` to multiply them out.

    >>> ac = AntiCommutator(A,B); ac
    {A,B}
    >>> ac.doit()
    A*B + B*A

    The commutator orders it arguments in canonical order:

    >>> ac = AntiCommutator(B,A); ac
    {A,B}

    Commutative constants are factored out:

    >>> AntiCommutator(3*x*A,x*y*B)
    3*x**2*y*{A,B}

    Adjoint operations applied to the anticommutator are properly applied to
    the arguments:

    >>> Dagger(AntiCommutator(A,B))
    {Dagger(A),Dagger(B)}

    References
    ==========

    .. [1] https://en.wikipedia.org/wiki/Commutator
    """
    is_commutative = False

    _kind_dispatcher = KindDispatcher("AntiCommutator_kind_dispatcher", commutative=True)

    @property
    def kind(self):
        arg_kinds = (a.kind for a in self.args)
        return self._kind_dispatcher(*arg_kinds)

    def __new__(cls, A, B):
        r = cls.eval(A, B)
        if r is not None:
            return r
        obj = Expr.__new__(cls, A, B)
        return obj

    @classmethod
    def eval(cls, a, b):
        if not (a and b):
            return S.Zero
        if a == b:
            return Integer(2)*a**2
        if a.is_commutative or b.is_commutative:
            return Integer(2)*a*b

        # [xA,yB]  ->  xy*[A,B]
        ca, nca = a.args_cnc()
        cb, ncb = b.args_cnc()
        c_part = ca + cb
        if c_part:
            return Mul(Mul(*c_part), cls(Mul._from_args(nca), Mul._from_args(ncb)))

        # Canonical ordering of arguments
        #The Commutator [A,B] is on canonical form if A < B.
        if a.compare(b) == 1:
            return cls(b, a)

    def doit(self, **hints):
        """ Evaluate anticommutator """
        # Keep the import of Operator here to avoid problems with
        # circular imports.
        from sympy.physics.quantum.operator import Operator
        A = self.args[0]
        B = self.args[1]
        if isinstance(A, Operator) and isinstance(B, Operator):
            try:
                comm = A._eval_anticommutator(B, **hints)
            except NotImplementedError:
                try:
                    comm = B._eval_anticommutator(A, **hints)
                except NotImplementedError:
                    comm = None
            if comm is not None:
                return comm.doit(**hints)
        return (A*B + B*A).doit(**hints)

    def _eval_adjoint(self):
        return AntiCommutator(Dagger(self.args[0]), Dagger(self.args[1]))

    def _sympyrepr(self, printer, *args):
        return "%s(%s,%s)" % (
            self.__class__.__name__, printer._print(
                self.args[0]), printer._print(self.args[1])
        )

    def _sympystr(self, printer, *args):
        return "{%s,%s}" % (
            printer._print(self.args[0]), printer._print(self.args[1]))

    def _pretty(self, printer, *args):
        pform = printer._print(self.args[0], *args)
        pform = prettyForm(*pform.right(prettyForm(',')))
        pform = prettyForm(*pform.right(printer._print(self.args[1], *args)))
        pform = prettyForm(*pform.parens(left='{', right='}'))
        return pform

    def _latex(self, printer, *args):
        return "\\left\\{%s,%s\\right\\}" % tuple([
            printer._print(arg, *args) for arg in self.args])


@AntiCommutator._kind_dispatcher.register(_OperatorKind, _OperatorKind)
def find_op_kind(e1, e2):
    """Find the kind of an anticommutator of two OperatorKinds."""
    return OperatorKind
