#! /usr/bin/env python
# -*- coding: utf-8 -*-

# Copyright (C) 2011 ~ 2012 Deepin, Inc.
#               2011 ~ 2012 Hou Shaohui
# 
# Author:     Hou Shaohui <houshao55@gmail.com>
# Maintainer: Hou Shaohui <houshao55@gmail.com>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

"""
    A generic framework for service providers, recommended to be used
    whenever there are multiple ways of accomplishing a task or multiple
    sources can offer the required data.
"""

from .events import event_manager

class ProviderManager(object):
    """
        The overall manager for services and providers for them
    """
    def __init__(self):
        self.services = {}

    def register(self, servicename, provider, target=None):
        """
            Registers a provider for a service. The provider object is used 
            by consumers of the service. 

            Services can be targeted for a specific use. For example, if you
            have a widget that uses a service 'foo', if your object can perform
            a service only for a specific type of widget, then target would be 
            set to the widget type. 
            
            If you had a service that could perform 'foo' for all widgets, then
            target would be set to None, and all widgets could use your service.
            
            It is intended that most services should set target to None, with 
            some narrow exceptions. 
            
            :param servicename: the name of the service [string]
            :type servicename: string
            :param provider: the object that is the provider [object]
            :type provider: object
            :param target: a specific target for the service [object]
            :type target: object
        """
        service = self.services.setdefault(servicename, {})
        providers = service.setdefault(target, [])
        if provider not in providers:
            providers.append(provider)
            event_manager.emit("%s_provider_added" % servicename, (provider, target), self)

    def unregister(self, servicename, provider, target=None):
        """
            Unregisters a provider.

            :param servicename: the name of the service
            :type servicename: string
            :param provider: the provider to be removed
            :type provider: object
            :param target: a specific target for the service [object]
            :type target: object
        """
        if servicename not in self.services:
            return
        try:
            service = self.services[servicename]
            if provider in service[target]:
                service[target].remove(provider)
                event_manager.emit("%s_provider_removed" % servicename, (provider, target), self)
                if not service[target]: #no values for target key then del it
                    del service[target]
        except KeyError:
            return

    def get(self, servicename, target=None):
        """
            Returns a list of providers for the specified servicename.

            This will return providers targeted for a specific target AND
            providers not targeted towards any particular target.
            
            :param servicename: the service name to get providers for
            :type servicename: string
            :param target: the target of the service
            :type target: object
            :returns: list of providers
            :rtype: list of objects
        """
        try:
            service = self.services[servicename]
        except KeyError:
            return []
        
        try:
            any = service[None]
        except KeyError:
            any = []
            
        if target is None:
            return any[:]
        
        try:
            specific = service[target]
        except KeyError:
            specific = []
        
        return specific + any

    def get_provider(self, servicename, providername, target=None):
        """
            Returns a single identified provider
            
            This will return a provider either targeted for the specific 
            target or a provider not targeted towards any particular target.

            :param servicename: The service name to get the provider for
            :type servicename: string
            :param providername: The provider name to identify the provider
            :type providername: string
            :param target: the target of the service
            :type target: object
            :returns: a provider or None
            :rtype: object
        """
        for provider in self.get_providers(servicename, target):
            if provider.name == providername:
                return provider

        return None

provider_manager = ProviderManager()

class ProviderHandler(object):
    """
        Base class to handle providers
        for one specific service including
        notification about (un)registration
    """
    def __init__(self, servicename, target=None, simple_init=False):
        """
            Target is the object that the service is being performed for.
            Often, if the service is truly global and it doesn't make sense
            to target a service at a particular consumer, it can be None. 
        
            :param servicename: the name of the service to handle
            :type servicename: string
            :param target: the target for a provided service. Generally, 
            this will be the object that uses the service
            :type target: string
            :param simple_init: call on_provider_added for every element
                already registered on instantiation.
        """
        self.servicename = servicename
        self.target = target
        if simple_init:
            for provider in provider_manager.get(servicename, target):
                self.on_provider_added(provider)
        event_manager.connect("%s_provider_added" % servicename, self._add_callback)
        event_manager.connect("%s_provider_removed" % servicename, self._remove_callback)

    def _add_callback(self, obj, ptuple):
        """
            Mediator to call actual callback
            for added providers
        """
        provider, target = ptuple
        if target is None or target is self.target:
            self.on_provider_added(provider)

    def on_provider_added(self, provider):
        """
            Called when a new provider is added

            :param provider: the new provider
            :type provider: object
        """
        pass # for overriding

    def _remove_callback(self, obj, ptuple):
        """
            Mediator to call actual callback
            for removed providers
        """
        provider, target = ptuple
        if target is None or target is self.target:
            self.on_provider_removed(provider)

    def on_provider_removed(self, provider):
        """
            Called when a provider is removed

            :param provider: the removed provider
            :type provider: object
        """
        pass # for overriding

    def get_providers(self):
        """
            Returns a list of providers for this service

            :returns: list of providers
            :rtype: list of objects
        """
        return provider_manager.get(self.servicename, self.target)

    def get_provider(self, providername):
        """
            Returns a provider for this service.

            :param providername: The provider name to
                identify the provider
            :type providername: string
            :returns: A provider or None
            :rtype: object
        """
        return provider_manager.get_provider(self.servicename, providername, self.target)
