from time import time, localtime, strftime


class __Registry(object):
    """
    Singleton module registry. This class manages the information about which
    module extends which other modules.
    """

    modules = {}
    extensions = {}

    def get_extensions(self, name):
        return self.extensions.get(name, [])

    def register(self, name, cls):
        self.modules[name] = cls
        for extension in cls._extends:
            if not self.extensions.has_key(extension):
                self.extensions[extension] = []
            self.extensions[extension].append(name)

Registry = __Registry()


class Environment:
    """
    Environment stub that does nothing else but keep track of which modules
    are active, and instantiate modules when necessary.
    """

    active_modules = None

    def __init__(self):
        self.active_modules = {}

    def module(self, name):
        module = self.active_modules.get(name)
        if not module:
            module = Registry.modules[name](self)
            self.active_modules[name] = module
        return module


class ExtensionPoint(object):
    """
    An extension point is basically a method name that is declared by a module
    which offers functionality to which other modules can contribute.
    """

    module = None
    name = None
    env = None

    def __init__(self, flatten_results=True):
        self.flatten_results = flatten_results

    def __call__(self, *kw):
        extensions = Registry.get_extensions(self.module)
        retvals = []
        for extension in [e for e in extensions]:
            module = self.env.module(extension)
            if hasattr(module, self.name):
                fn = getattr(module, self.name)
                retval = fn.__call__(module, *kw)
                if self.flatten_results:
                    if not retval:
                        continue
                    if type(retval) is list:
                        retvals.extend(retval)
                    else:
                        retvals.append(retval)
                else:
                    retvals.append(retval)
        return retvals


class MetaModule(type):
    """
    Meta class for modules. It registers the module with the Registry, collects
    extension points and extensions declared by the module class, and adds 
    convenient ways to access the extensions.
    """

    class __Extensions(object): pass

    def __new__(cls, name, bases, d):

        extensions = cls.__Extensions()

        module_name = d.get('_name')
        if not module_name:
            if name.endswith('Module'):
                module_name = name[:-6].lower()
            else:
                module_name = name.lower()
        else:
            del d['_name']

        # collect extension points
        extension_points = []
        for attr, value in d.items():
            if isinstance(value, ExtensionPoint):
                value.module = module_name
                value.name = attr
                extension_points.append(value)
                setattr(extensions, attr, value)
                del d[attr]

        newClass = type.__new__(cls, name, bases, d)
        newClass.name = name
        newClass._extension_points = extension_points
        newClass._extends = d.get('_extends', [])
        newClass.extensions = extensions

        if module_name:
            Registry.register(module_name, newClass)

        return newClass


class Module(object):
    """
    Base class for modules.
    """

    __metaclass__ = MetaModule
    _registry = None
    env = None

    def __init__(self, env):
        self.env = env
        for extension_point in self._extension_points:
            extension_point.env = env


# ----------------------------------------------------------------------------
# Stuff below this point is only for demonstration purposes
# ----------------------------------------------------------------------------


class TimedEvent(object):

    time = None
    title = None
    description = None

    def __init__(self, time, title, description=''):
        self.time = time
        self.title = title or ''
        self.description = description or ''

    def __repr__(self):
        retval = '%s -- %s' % (strftime('%x %X', localtime(self.time)), self.title)
        if self.description:
            retval += '\n\t%s' % self.description
        return retval


class TimelineModule(Module):

    _name = 'timeline'

    get_timeline_events = ExtensionPoint()
    get_timeline_filters = ExtensionPoint()

    def get_events(self, start, stop, filters=None):
        if not filters:
            filters = self.extensions.get_timeline_filters()
        return self.extensions.get_timeline_events(start, stop, filters)


class TicketModule(Module):

    _name = 'ticket'
    _extends = [ 'timeline' ]

    def get_timeline_events(self, start, stop, filters):
        if 'tickets' in filters:
            return [TimedEvent(time(), 'Ticket #1'),
                    TimedEvent(time(), 'Ticket #2')]

    def get_timeline_filters(self):
        return ['tickets']


class ChangesetModule(Module):

    _name = 'changeset'
    _extends = [ 'timeline' ]

    def get_timeline_events(self, start, stop, filters):
        if 'changesets' in filters:
            return [TimedEvent(time(), 'Changeset [1]'),
                    TimedEvent(time(), 'Changeset [2]')]

    def get_timeline_filters(self):
        return ['changesets']


if __name__ == '__main__':
    env = Environment()
    timeline = env.module('timeline')
    
    print '\nAll events:'
    all_events = timeline.get_events(0, 0)
    print all_events

    print '\nChangeset events:'
    changeset_events = timeline.get_events(0, 0, ['changesets'])
    print changeset_events

    print '\nTicket events:'
    ticket_events = timeline.get_events(0, 0, ['tickets'])
    print ticket_events

