'''
Asterisk/Util.py: utility classes.
'''

__author__ = 'David Wilson'
__Id__ = '$Id$'

import sys, copy
import Asterisk
from Asterisk import Logging




class SubscriptionError(Asterisk.BaseException):
    '''
    This exception is raised when an attempt to register the same (event,
    handler) tuple twice is detected.
    '''



# This special unique object is used to indicate that an argument has not been
# specified. It is used where None may be a valid argument value.

class Unspecified(object):
    'A class to represent an unspecified value that cannot be None.'

    def __repr__(self):
        return '<Asterisk.Util.Unspecified>'

Unspecified = Unspecified()




class AttributeDict(dict):
    def __getattr__(self, key):
        return self[key]

    def __setattr__(self, key, value):
        self[key] = value

    def copy(self):
        return AttributeDict(self.iteritems())



class EventCollection(Logging.InstanceLogger):
    '''
    Utility class to allow grouping and automatic registration of event.
    '''

    def __init__(self, initial = None):
        '''
        If <initial> is not None, register functions from the list <initial>
        waiting for events with the same name as the function.
        '''

        self.subscriptions = {}
        self.log = self.getLogger()

        if initial is not None:
            for func in initial:
                self.subscribe(func.__name__, func)


    def subscribe(self, name, handler):
        '''
        Subscribe callable <handler> to event named <name>.
        '''

        if name not in self.subscriptions:
            subscriptions = self.subscriptions[name] = []
        else:
            subscriptions = self.subscriptions[name]

        if handler in subscriptions:
            raise SubscriptionError

        subscriptions.append(handler)


    def unsubscribe(self, name, handler):
        'Unsubscribe callable <handler> to event named <name>.'
        self.subscriptions[name].remove(handler)


    def clear(self):
        'Destroy all present subscriptions.'
        self.subscriptions.clear()


    def fire(self, name, *args, **kwargs):
        '''
        Fire event <name> passing *<args> and **<kwargs> to subscribers,
        returning the return value of the last called subscriber.
        '''

        if name not in self.subscriptions:
            return


        return_value = None

        for subscription in self.subscriptions[name]:
            self.log.debug('calling %r(*%r, **%r)', subscription, args, kwargs)
            return_value = subscription(*args, **kwargs)

        return return_value


    def copy(self):
        new = self.__class__()

        for name, subscriptions in self.subscriptions.iteritems():
            new.subscriptions[name] = []
            for subscription in subscriptions:
                new.subscriptions[name].append(subscription)


    def __iadd__(self, collection):
        'Add all the events in <collection> to our collection.'

        if not isinstance(collection, EventCollection):
            raise TypeError

        new = self.copy()

        try:
            for name, handlers in collection.subscriptions.iteritems():
                for handler in handlers:
                    self.subscribe(name, handler)
        except Exception, e:
            self.subscriptions = new.subscriptions
            raise

        return self


    def __isub__(self, collection):
        'Remove all the events in <collection> from our collection.'

        if not isinstance(collection, EventCollection):
            raise TypeError

        new = self.copy()

        try:
            for name, handlers in collection.subscriptions.iteritems():
                for handler in handlers:
                    self.unsubscribe(name, handler)
        except Exception, e:
            self.subscriptions = new.subscriptions
            raise

        return self
    



def dump_packet(packet, file = sys.stdout):
    '''
    Dump a packet in human readable form to file-like object <file>.
    '''

    packet = dict(packet)

    if 'Event' in packet:
        file.write('-- %s\n' % packet.pop('Event'))
    else:
        file.write('-- Response: %s\n' % packet.pop('Response'))


    packet = packet.items()
    packet.sort()

    for tuple in packet:
        file.write('   %s: %s\n' % tuple)

    file.write('\n')






def dump_human(data, file = sys.stdout, _indent = 0):
    scalars = (str, int, float)
    recursive = (dict, list, tuple, AttributeDict)
    indent = lambda a = 0, i = _indent: ('   ' * (a + i))
    Type = type(data)


    if Type in (dict, AttributeDict):
        items = data.items()
        items.sort()

        for key, val in items:
            file.write(indent() + str(key) + ': ')
            if type(val) in recursive:
                file.write('\n')
                dump_human(val, file, _indent + 1)
            else:
                dump_human(val, file, 0)

    elif Type in (list, tuple):
        for val in data:
            dump_human(val, file, _indent + 1)

    elif Type in (int, float):
        file.write(indent() + '%r\n' % data)

    elif Type is str:
        file.write(indent() + data + '\n')
