# -*- coding: utf-8 -*-

"""
    Introducing delayed methods
    ===========================

    A delayed method is a smart method that detects in what environment is invoked
    and avoid duplicated and expensive calls.

    In transactional-free context, a delayed method behaves like normal a method:
    do his work and returns.

    Meanwhile, in the context of any open transaction or undo/redo operation, a
    delayed method enqueues the call, avoiding duplicates for the end of the
    transactional operation.

    That results in reduction of redundant calls done, for example while deleting
    a big set of elements at once.

    Creating a delayed method with a decorator is elegant and simple: nothing
    changes from the user point of view.

    The main target of delayed methods is obviously graphical updates, but surprisingly
    the origin of this idea was to minimice code regeneration.

"""


from tran import TransactionStack


def DelayedMethod(method):
    """Function decorator for delayed methods."""
    def wrapped_call(*args, **kwargs):
        """Code for delayed method"""
        # If we are not in transactional context, simply do the call
        if not TransactionStack.InTransaction() and not TransactionStack.InUndoRedo():
            # do inmmediate call
            return method(*args, **kwargs)
        # Skip explicit filtered calls
        if method in TransactionStack.delayedCallsFiltered:
            return
        # Ok, check the method inside the transaction
        entry = TransactionStack.delayedCalls.get(method, [])
        if (args, kwargs) in entry:
            return
        if entry:
            TransactionStack.delayedCalls[method].append((args, kwargs))
        else:
            TransactionStack.delayedCalls[method] = [(args, kwargs)]
    wrapped_call.inner = method
    return wrapped_call
