from __future__ import print_function


class EmptyCommandStackError(Exception):
    pass



class ElementAdder(object):

    def __init__(self, set_, element):
        self.set_ = set_
        self.element = element
            
    def do(self):
        self.set_.add(self.element)
    def undo(self):
        self.set_.remove(self.element)


class ElementRemover(object):

    def __init__(self, set_, element):
        self.set_ = set_
        self.element = element
            
    def do(self):
        self.set_.remove(self.element)
        
        return 232
    def undo(self):
        self.set_.add(self.element)



class CommandManager(object):
    def __init__(self):
        self.undo_commands = []
        self.redo_commands = []

    def push_undo_command(self, command):
        """Push the given command to the undo command stack."""
        self.undo_commands.append(command)

    def pop_undo_command(self):
        """Remove the last command from the undo command stack and return it.
        If the command stack is empty, EmptyCommandStackError is raised.
        """
        try:
            last_undo_command = self.undo_commands.pop()
        except IndexError:
            raise EmptyCommandStackError()
        return last_undo_command

    def push_redo_command(self, command):
        """Push the given command to the redo command stack."""
        self.redo_commands.append(command)

    def pop_redo_command(self):
        """Remove the last command from the redo command stack and return it.
        If the command stack is empty, EmptyCommandStackError is raised.
        """
        try:
            last_redo_command = self.redo_commands.pop()
        except IndexError:
            raise EmptyCommandStackError()
        return last_redo_command
    
    def canundo(self):
        
        return bool(self.undo_commands)

    def canredo(self):
        
        return bool(self.redo_commands)
    
    def do(self, command):
        """Execute the given command. Exceptions raised from the command are
        not caught.
        """
        f  = command.do()
        self.push_undo_command(command)
        # clear the redo stack when a new command was executed
        self.redo_commands[:] = []
        
        return f

    def undo(self, n=1):
        """Undo the last n commands. The default is to undo only the last
        command. If there is no command that can be undone because n is too big
        or because no command has been emitted yet, EmptyCommandStackError is
        raised.
        """
     
        for _ in xrange(n):
            command = self.pop_undo_command()
            command.undo()
            self.push_redo_command(command)

    def redo(self, n=1):
        """Redo the last n commands which have been undone using the undo
        method. The default is to redo only the last command which has been
        undone using the undo method. If there is no command that can be redone
        because n is too big or because no command has been undone yet,
        EmptyCommandStackError is raised.
        """
        for _ in xrange(n):
            command = self.pop_redo_command()
            command.do()
            self.push_undo_command(command)


if __name__ == "__main__":
    my_set = {3, 5, 13}
    print("initial set: {}".format(my_set))
    manager = CommandManager()
    # remove element 5 from the set
    print (manager.do(ElementRemover(my_set, 5)))
    print("set after removing 5: {}".format(my_set))
    
    print ("canundo: ", manager.canundo())
    print ("canredo: ", manager.canredo())
    # add element -7 to the set
    manager.do(ElementAdder(my_set, -7))
    print("set after adding -7 to the set: {}".format(my_set))
    print ("canundo: ", manager.canundo())
    print ("canredo: ", manager.canredo())    
    # undo adding the element -7
    manager.undo()
    print("set after undoing adding -7 to the set: {}".format(my_set))
    print ("canundo: ", manager.canundo())
    print ("canredo: ", manager.canredo())    
    # undo removing the element 5 from the set
    manager.undo()
    print("set after undoing removing 5 from the set: {}".format(my_set))
    print ("canundo: ", manager.canundo())
    print ("canredo: ", manager.canredo())    
    # redo both undone operations
    manager.redo(2)
    print("set after redoing the just undone operations: {}".format(my_set))
    