#!/usr/bin/env python
# encoding: utf-8
"""
The IPython Core Notification Center.

See docs/source/development/notification_blueprint.txt for an overview of the
notification module.

Authors:

* Barry Wark
* Brian Granger
"""

#-----------------------------------------------------------------------------
#  Copyright (C) 2008-2009  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Code
#-----------------------------------------------------------------------------


class NotificationError(Exception):
    pass


class NotificationCenter(object):
    """Synchronous notification center.
    
    Examples
    --------
    Here is a simple example of how to use this::

        import IPython.util.notification as notification
        def callback(ntype, theSender, args={}):
            print ntype,theSender,args

        notification.sharedCenter.add_observer(callback, 'NOTIFICATION_TYPE', None)
        notification.sharedCenter.post_notification('NOTIFICATION_TYPE', object()) # doctest:+ELLIPSIS
        NOTIFICATION_TYPE ...
    """
    def __init__(self):
        super(NotificationCenter, self).__init__()
        self._init_observers()

    def _init_observers(self):
        """Initialize observer storage"""

        self.registered_types = set() #set of types that are observed
        self.registered_senders = set() #set of senders that are observed
        self.observers = {} #map (type,sender) => callback (callable)

    def post_notification(self, ntype, sender, *args, **kwargs):
        """Post notification to all registered observers.

        The registered callback will be called as::

            callback(ntype, sender, *args, **kwargs)

        Parameters
        ----------
        ntype : hashable
            The notification type.
        sender : hashable
            The object sending the notification.
        *args : tuple
            The positional arguments to be passed to the callback.
        **kwargs : dict
            The keyword argument to be passed to the callback.

        Notes
        -----
        * If no registered observers, performance is O(1).
        * Notificaiton order is undefined.
        * Notifications are posted synchronously.
        """

        if(ntype==None or sender==None):
            raise NotificationError(
                "Notification type and sender are required.")

        # If there are no registered observers for the type/sender pair
        if((ntype not in self.registered_types and 
                None not in self.registered_types) or
            (sender not in self.registered_senders and 
                None not in self.registered_senders)):
            return

        for o in self._observers_for_notification(ntype, sender):
            o(ntype, sender, *args, **kwargs)

    def _observers_for_notification(self, ntype, sender):
        """Find all registered observers that should recieve notification"""

        keys = (
                   (ntype,sender),
                   (ntype, None),
                   (None, sender),
                   (None,None)
               )

        obs = set()
        for k in keys:
            obs.update(self.observers.get(k, set()))

        return obs

    def add_observer(self, callback, ntype, sender):
        """Add an observer callback to this notification center.
        
        The given callback will be called upon posting of notifications of
        the given type/sender and will receive any additional arguments passed
        to post_notification.
        
        Parameters
        ----------
        callback : callable
            The callable that will be called by :meth:`post_notification`
            as ``callback(ntype, sender, *args, **kwargs)
        ntype : hashable
            The notification type. If None, all notifications from sender
            will be posted.
        sender : hashable
            The notification sender. If None, all notifications of ntype
            will be posted.
        """
        assert(callback != None)
        self.registered_types.add(ntype)
        self.registered_senders.add(sender)
        self.observers.setdefault((ntype,sender), set()).add(callback)
    
    def remove_all_observers(self):
        """Removes all observers from this notification center"""
        
        self._init_observers()
    


shared_center = NotificationCenter()
