from zope.component import queryAdapter
from zope.annotation.interfaces import IAnnotations

from Products.CMFCore.utils import getToolByName

from plone.portlets.interfaces import ILocalPortletAssignable
from plone.portlets.interfaces import IPortletContext
from plone.portlets.constants import CONTEXT_ASSIGNMENT_KEY
from plone.portlets.constants import CONTEXT_BLACKLIST_STATUS_KEY
from plone.portlets.constants import CONTEXT_CATEGORY
from plone.portlets.retriever import PortletRetriever as basePortletRetriever


class VSPortletRetriever(basePortletRetriever):
    """The vs.dashboardmanager retriever.
    It does the same job as the standard retriever.
    """

    def getPortlets(self):
        """Work out which portlets to display, returning a list of dicts
        describing assignments to render.
        """

        if IPortletContext.providedBy(self.context):
            pcontext = self.context
        else:
            pcontext = queryAdapter(self.context, IPortletContext)

        if pcontext is None:
            return []
            
        # Holds a list of (category, key, assignment).
        categories = [] 
        
        # Keeps track of the blacklisting status for global categores 
        # (user, group, content type). The status is either True (blocked)
        # or False (not blocked).
        blacklisted = {}
                
        # This is the name of the manager (column) we're rendering
        manager = self.storage.__name__
        
        # 1. Fetch blacklisting status for each global category

        # First, find out which categories we will need to determine
        # blacklist status for
        
        for category, key in pcontext.globalPortletCategories(False):
            blacklisted[category] = None
        
        # Then walk the content hierarchy to find out what blacklist status
        # was assigned. Note that the blacklist is tri-state; if it's None it
        # means no assertion has been made (i.e. the category has neither been
        # whitelisted or blacklisted by this object or any parent). The first
        # item to give either a blacklisted (True) or whitelisted (False)
        # value for a given item will set the appropriate value. Parents of
        # this item that also set a black- or white-list value will then be
        # ignored.

        # Whilst walking the hierarchy, we also collect parent portlets, 
        # until we hit the first block.

        current = self.context
        currentpc = pcontext
        blacklistFetched = set()
        parentsBlocked = False
        
        while current is not None and currentpc is not None:
            if ILocalPortletAssignable.providedBy(current):
                assignable = current
            else:
                assignable = queryAdapter(current, ILocalPortletAssignable)

            if assignable is not None:
                if IAnnotations.providedBy(assignable):
                    annotations = assignable
                else:
                    annotations = queryAdapter(assignable, IAnnotations)

                if not parentsBlocked:
                    local = annotations.get(CONTEXT_ASSIGNMENT_KEY, None)
                    if local is not None:
                        localManager = local.get(manager, None)
                        if localManager is not None:
                            categories.extend([(CONTEXT_CATEGORY, currentpc.uid, a) for a in localManager.values()])

                blacklistStatus = annotations.get(CONTEXT_BLACKLIST_STATUS_KEY, {}).get(manager, None)
                if blacklistStatus is not None:
                    for cat, status in blacklistStatus.items():
                        if cat == CONTEXT_CATEGORY:
                            if not parentsBlocked and status == True:
                                parentsBlocked = True
                        else: # global portlet categories
                            if blacklisted.get(cat, False) is None:
                                blacklisted[cat] = status
                            if status is not None:
                                blacklistFetched.add(cat)
                                
            # We can abort if parents are blocked and we've fetched all
            # blacklist statuses
            
            if parentsBlocked and len(blacklistFetched) == len(blacklisted):
                break
                    
            # Check the parent - if there is no parent, we will stop
            current = currentpc.getParent()
            if current is not None:
                if IPortletContext.providedBy(current):
                    currentpc = current
                else:
                    currentpc = queryAdapter(current, IPortletContext)
        
        # Get all global mappings for non-blacklisted categories
        
        for category, key in pcontext.globalPortletCategories(False):
            if not blacklisted[category]:
                mapping = self.storage.get(category, None)
                if mapping is not None:
                    for a in mapping.get(key, {}).values():
                        categories.append((category, key, a,))
        
        assignments = []
        for category, key, assignment in categories:
            assignments.append({'category'    : category,
                                'key'         : key,
                                'name'        : assignment.__name__,
                                'assignment'  : assignment
                                })
        return assignments