""" Generic SymPy-Independent Strategies """
from __future__ import print_function, division

from sympy.core.compatibility import get_function_name

def identity(x):
    yield x

def exhaust(brule):
    """ Apply a branching rule repeatedly until it has no effect """
    def exhaust_brl(expr):
        seen = set([expr])
        for nexpr in brule(expr):
            if nexpr not in seen:
                seen.add(nexpr)
                for nnexpr in exhaust_brl(nexpr):
                    yield nnexpr
        if seen == set([expr]):
            yield expr
    return exhaust_brl

def onaction(brule, fn):
    def onaction_brl(expr):
        for result in brule(expr):
            if result != expr:
                fn(brule, expr, result)
            yield result
    return onaction_brl

def debug(brule, file=None):
    """ Print the input and output expressions at each rule application """
    if not file:
        from sys import stdout
        file = stdout

    def write(brl, expr, result):
        file.write("Rule: %s\n" % get_function_name(brl))
        file.write("In: %s\nOut: %s\n\n" % (expr, result))

    return onaction(brule, write)

def multiplex(*brules):
    """ Multiplex many branching rules into one """
    def multiplex_brl(expr):
        seen = set([])
        for brl in brules:
            for nexpr in brl(expr):
                if nexpr not in seen:
                    seen.add(nexpr)
                    yield nexpr
    return multiplex_brl

def condition(cond, brule):
    """ Only apply branching rule if condition is true """
    def conditioned_brl(expr):
        if cond(expr):
            for x in brule(expr): yield x
        else:
            pass
    return conditioned_brl

def sfilter(pred, brule):
    """ Yield only those results which satisfy the predicate """
    def filtered_brl(expr):
        for x in filter(pred, brule(expr)):
            yield x
    return filtered_brl

def notempty(brule):
    def notempty_brl(expr):
        yielded = False
        for nexpr in brule(expr):
            yielded = True
            yield nexpr
        if not yielded:
            yield expr
    return notempty_brl

def do_one(*brules):
    """ Execute one of the branching rules """
    def do_one_brl(expr):
        yielded = False
        for brl in brules:
            for nexpr in brl(expr):
                yielded = True
                yield nexpr
            if yielded:
                raise StopIteration()
    return do_one_brl

def chain(*brules):
    """
    Compose a sequence of brules so that they apply to the expr sequentially
    """
    def chain_brl(expr):
        if not brules:
            yield expr
            raise StopIteration()

        head, tail = brules[0], brules[1:]
        for nexpr in head(expr):
            for nnexpr in chain(*tail)(nexpr):
                yield nnexpr

    return chain_brl

def yieldify(rl):
    """ Turn a rule into a branching rule """
    def brl(expr):
        yield rl(expr)
    return brl
