"""Transformation functions for expressions."""

from tt.errors import InvalidArgumentTypeError
from tt.expressions import BooleanExpression


def _ensure_bexpr(expr):
    """Return a BooleanExpression object or raise an error."""
    if isinstance(expr, str):
        return BooleanExpression(expr)
    elif isinstance(expr, BooleanExpression):
        return expr
    else:
        raise InvalidArgumentTypeError(
            'Transformations accept either a string or BooleanExpression '
            'argument')


def apply_de_morgans(expr):
    """Convert an expression to a form with De Morgan's Law applied.

    :returns: A new expression object, transformed so that De Morgan's Law has
        been applied to negated *ANDs* and *ORs*.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a couple of simple examples showing De Morgan's Law being applied
    to a negated AND and a negated OR::

        >>> from tt import apply_de_morgans
        >>> apply_de_morgans('not (A or B)')
        <BooleanExpression "not A and not B">
        >>> apply_de_morgans('not (A and B)')
        <BooleanExpression "not A or not B">

    """
    bexpr = _ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.root.apply_de_morgans())


def coalesce_negations(expr):
    """Convert an expression to a form with all negations condensed.

    :returns: A new expression object, transformed so that all "runs" of
        logical *NOTs* are condensed into the minimal equivalent number.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple example showing the basic premise of this transformation::

        >>> from tt import coalesce_negations
        >>> coalesce_negations('~~A or ~B or ~~~C or ~~~~D')
        <BooleanExpression "A or ~B or ~C or D">

    This transformation works on expressions, too::

        >>> coalesce_negations('!!(A -> not not B) or ~(~(A xor B))')
        <BooleanExpression "(A -> B) or (A xor B)">

    """
    bexpr = _ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.root.coalesce_negations())


def distribute_ands(expr):
    """Convert an expression to distribute ANDs over ORed clauses.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to distribute ANDs over ORed
        clauses.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple example::

        >>> from tt import distribute_ands
        >>> distribute_ands('A and (B or C or D)')
        <BooleanExpression "(A and B) or (A and C) or (A and D)">

    And an example involving distributing a sub-expression::

        >>> distribute_ands('(A and B) and (C or D or E)')
        <BooleanExpression "(A and B and C) or (A and B and D) or \
(A and B and E)">

    """
    bexpr = _ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.root.distribute_ands())


def distribute_ors(expr):
    """Convert an expression to distribute ORs over ANDed clauses.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to distribute ORs over ANDed
        clauses.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple example::

        >>> from tt import distribute_ors
        >>> distribute_ors('A or (B and C and D and E)')
        <BooleanExpression "(A or B) and (A or C) and (A or D) and (A or E)">

    And an example involving distributing a sub-expression::

        >>> distribute_ors('(A or B) or (C and D)')
        <BooleanExpression "(A or B or C) and (A or B or D)">

    """
    bexpr = _ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.root.distribute_ors())


def to_primitives(expr):
    """Convert an expression to a form with only primitive operators.

    All operators will be transformed equivalent form composed only of the
    logical AND, OR,and NOT operators. Symbolic operators in the passed
    expression will remain symbolic in the transformed expression and the same
    applies for plain English operators.

    :param expr: The expression to transform.
    :type expr: :class:`str <python:str>` or :class:`BooleanExpression \
    <tt.expressions.bexpr.BooleanExpression>`

    :returns: A new expression object, transformed to contain only primitive
        operators.
    :rtype: :class:`BooleanExpression <tt.expressions.bexpr.BooleanExpression>`

    :raises InvalidArgumentTypeError: If ``expr`` is not a valid type.

    Here's a simple transformation of exclusive-or::

        >>> from tt import to_primitives
        >>> to_primitives('A xor B')
        <BooleanExpression "(A and not B) or (not A and B)">

    And another example of if-and-only-if (using symbolic operators)::

        >>> to_primitives('A <-> B')
        <BooleanExpression "(A /\ B) \/ (~A /\ ~B)">

    """
    bexpr = _ensure_bexpr(expr)
    return BooleanExpression(bexpr.tree.root.to_primitives())

@@ -1085,18 +1085,16 @@
 ans(
-'not (A or
+r'~(A /%5C
  B)'
@@ -1127,22 +1127,15 @@
 on %22
-not A and not 
+~A %5C/ ~
 B%22%3E%0A
@@ -1167,19 +1167,16 @@
 ans(
-'not (A and
+r'~(A %5C/
  B)'
@@ -1209,21 +1209,15 @@
 on %22
-not A or not 
+~A /%5C ~
 B%22%3E%0A
@@ -2761,32 +2761,42 @@
 .%0A%0A    Here's a 
+couple of 
 simple example::
@@ -2785,32 +2785,33 @@
 f simple example
+s
 ::%0A%0A        %3E%3E%3E 
@@ -2953,24 +2953,123 @@
  (A and D)%22%3E
+%0A        %3E%3E%3E distribute_ands('(A or B) and C')%0A        %3CBooleanExpression %22(A and C) or (B and C)%22%3E
 %0A%0A    And an
@@ -3887,32 +3887,42 @@
 .%0A%0A    Here's a 
+couple of 
 simple example::
@@ -3919,16 +3919,17 @@
  example
+s
 ::%0A%0A    
@@ -4096,24 +4096,121 @@
 d (A or E)%22%3E
+%0A        %3E%3E%3E distribute_ors('(A and B) or C')%0A        %3CBooleanExpression %22(A or C) and (B or C)%22%3E
 %0A%0A    And an

Update expression transformation function docstrings