#
#  Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
#  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 2 of the License, or
#  (at your option) 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, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

"""
Definition of the concept view classes.
"""

from itertools import groupby
from zope import interface, component, schema
from zope.app.catalog.interfaces import ICatalog
from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.container.contained import ObjectRemovedEvent
from zope.app.form.browser.interfaces import ITerms
from zope.app.form.interfaces import IDisplayWidget
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.formlib.form import EditForm, FormFields, setUpEditWidgets
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.schema.interfaces import IIterableSource
from zope.security.proxy import removeSecurityProxy
from zope.traversing.api import getName
from zope.traversing.browser import absoluteURL

from cybertools.browser.action import actions
from cybertools.composer.interfaces import IInstance
from cybertools.composer.schema.grid.interfaces import Grid
from cybertools.composer.schema.interfaces import ISchemaFactory
from cybertools.meta.interfaces import IOptions
from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.jeep import Jeep
from loops.browser.common import EditForm, BaseView, LoopsTerms, concept_macros
from loops.browser.common import ViewMode
from loops.common import adapted
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
from loops.i18n.browser import I18NView
from loops.interfaces import IConcept, IConceptSchema, ITypeConcept, IResource
from loops.organize.util import getRolesForPrincipal
from loops.schema.base import RelationSet, Relation
from loops import util
from loops.util import _
from loops.versioning.util import getVersion


class ConceptEditForm(EditForm, I18NView):
    """ Classic-style (zope.formlib-based) form for editing concepts.
    """

    ignoredFieldTypes = (Grid, Relation, RelationSet,)

    #@Lazy  # zope.formlib does not issue a redirect after changes, so that
            # it tries to redisplay the old form even after a type change that
            # changes the set of available attributes. So the typeInterface
            # must be recalculated e.g. after an update of the context object.
    @property
    def typeInterface(self):
        return IType(self.context).typeInterface

    @Lazy
    def title(self):
        return adapted(self.context, self.languageInfo).title

    @property
    def form_fields(self):
        typeInterface = self.typeInterface
        if typeInterface is None:
            fields = FormFields(IConcept)
        elif 'title' in typeInterface:  # new type interface based on ConceptSchema
            f1 = FormFields(IConcept).omit('title', 'description')
            fields = FormFields(typeInterface, f1)
        else:
            fields = FormFields(IConcept, typeInterface)
        return [f for f in fields
                  if not isinstance(f.field, self.ignoredFieldTypes)]

    def setUpWidgets(self, ignore_request=False):
        # TODO: get rid of removeSecurityProxy(): use ConceptSchema in interfaces
        #adapter = removeSecurityProxy(adapted(self.context, self.languageInfo))
        adapter = adapted(self.context, self.languageInfo)
        self.adapters = {self.typeInterface: adapter,
                         IConceptSchema: adapter}
        self.widgets = setUpEditWidgets(
            self.form_fields, self.prefix, self.context, self.request,
            adapters=self.adapters, ignore_request=ignore_request)
        desc = self.widgets.get('description')
        if desc:
            desc.height = 2


class BaseRelationView(BaseView):
    """ For displaying children and resources lists.
    """

    def __init__(self, relation, request, contextIsSecond=False):
        if contextIsSecond:
            self.context = relation.second
            self.other = relation.first
        else:
            self.context = relation.first
            self.other = relation.second
        self.context = getVersion(self.context, request)
        self.predicate = relation.predicate
        self.predicates = [self.predicate]  # allow for more than one relation
        self.relation = relation
        self.relations = [relation]
        self.request = request

    @Lazy
    def hash(self):
        return '%s:%s' % (id(self.relation.first), id(self.relation.second))

    @Lazy
    def adapted(self):
        return adapted(self.context, self.languageInfo)

    @Lazy
    def data(self):
        return self.getData()

    def getData(self):
        return self.instance.applyTemplate(context=self.context, request=self.request)

    @Lazy
    def instance(self):
        instance = IInstance(self.adapted)
        instance.template = self.schema
        instance.view = self
        return instance

    @Lazy
    def schema(self):
        ti = self.typeInterface or IConceptSchema
        schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
        return schemaFactory(ti, manager=self, request=self.request)

    @Lazy
    def title(self):
        return self.adapted.title or getName(self.context)

    @Lazy
    def description(self):
        return self.adapted.description

    @Lazy
    def token(self):
        return ':'.join((self.loopsRoot.getLoopsUri(self.context),
                         self.loopsRoot.getLoopsUri(self.predicate)))

    @Lazy
    def uidToken(self):
        return ':'.join((util.getUidForObject(self.context),
                         util.getUidForObject(self.predicate)))

    @Lazy
    def isProtected(self):
        return getName(self.predicate) == 'hasType'

    @Lazy
    def predicateTitle(self):
        return ', ' .join(p.title for p in self.predicates)

    @Lazy
    def predicateUrl(self):
        return absoluteURL(self.predicate, self.request)

    @Lazy
    def relevance(self):
        return self.relation.relevance

    @Lazy
    def order(self):
        return self.relation.order

    @Lazy
    def relationInfo(self):
        if self.predicate == self.defaultPredicate:
            return u''
        return self.predicateTitle

    @Lazy
    def relationInfo(self):
        predInfo = ', ' .join(p.title for p in self.predicates
                                if p != self.defaultPredicate)
        return ' | '.join(t for t in (self.description, predInfo) if t)


class ConceptView(BaseView):

    template = concept_macros
    templateName = 'concept.standard'
    macroName = 'conceptdata'
    partPrefix = 'part_'
    defaultParts = ('title', 'fields',
                    'children', 'resources', 'workitems', 'comments',)

    def childViewFactory(self, *args, **kw):
        return ConceptRelationView(*args, **kw)

    @Lazy
    def macros(self):
        return self.controller.getTemplateMacros(self.templateName, self.template)

    @property
    def macro(self):
        return self.macros[self.macroName]

    def setupController(self):
        cont = self.controller
        if cont is None:
            return
        if self.parentsForPortlet and (
                self.globalOptions('showParentsForAnonymous') or
                not IUnauthenticatedPrincipal.providedBy(self.request.principal)):
            cont.macros.register('portlet_right', 'parents', title=_(u'Parents'),
                        subMacro=concept_macros.macros['parents'],
                        priority=20, info=self)

    # the part-based layout is now implemented in loops.browser.compound
    def getParts(self):
        parts = (self.params.get('parts') or [])   # deprecated!
        if not parts:
            parts = (self.options('parts') or self.typeOptions('parts') or
                     self.defaultParts)
        return self.getPartViews(parts)

    def getPartViews(self, parts):
        result = []
        for p in parts:
            viewName = self.partPrefix + p
            view = component.queryMultiAdapter((self.adapted, self.request),
                                               name=viewName)
            if view is not None:
                view.parent = self
                result.append(view)
        return result

    def viewModes(self):
        modes = Jeep()
        current = self.request.form.get('loops.viewName')
        parts = (self.options('view_tabs') or 
                    self.typeOptions('view_tabs') or [])
        if not parts:
            return modes
        activeMode = None
        for p in parts:
            view = component.queryMultiAdapter(
                        (self.adapted, self.request), name=p)
            if view is None:
                view = component.queryMultiAdapter(
                            (self.context, self.request), name=p)
            if view is None:
                continue
            active = (activeMode is None and p == current)
            if active:
                activeMode = p
            url = '%s?loops.viewName=%s' % (self.targetUrl, p)
            modes.append(ViewMode(p, view.tabTitle, url, active))
        if activeMode is None:
            modes[0].active = True
        return modes

    @Lazy
    def adapted(self):
        return adapted(self.context, self.languageInfo)

    @Lazy
    def targetUrl(self):
        return self.nodeView.getUrlForTarget(self.context)

    def breadcrumbs(self):
        data = []
        if self.breadcrumbsParent is not None:
            data.extend(self.breadcrumbsParent.breadcrumbs())
        if self.context != self.nodeView.targetObject:
            data.append(dict(label=self.breadcrumbsTitle, url=self.targetUrl))
        return data

    @Lazy
    def breadcrumbsTitle(self):
        return self.title

    @Lazy
    def showInBreadcrumbs(self):
        return (self.options('show_in_breadcrumbs') or 
                self.typeOptions('show_in_breadcrumbs'))

    @Lazy
    def breadcrumbsParent(self):
        for p in self.context.getParents([self.defaultPredicate]):
            view = self.nodeView.getViewForTarget(p)
            if view.showInBreadcrumbs:
                return view
        return None

    def getData(self, omit=('title', 'description')):
        data = self.instance.applyTemplate(context=self.context, request=self.request)
        for k in omit:
            if k in data:
                del data[k]
        return data

    @Lazy
    def data(self):
        return self.getData()

    def getFields(self, omit=('title', 'description')):
        fields = Jeep(self.schema.fields)
        fields.remove(*omit)
        return fields

    @Lazy
    def fields(self):
        return self.getFields()

    @Lazy
    def schema(self):
        ti = self.typeInterface or IConceptSchema
        schemaFactory = component.getAdapter(self.adapted, ISchemaFactory)
        return schemaFactory(ti, manager=self, request=self.request)

    @Lazy
    def instance(self):
        instance = IInstance(self.adapted)
        instance.template = self.schema
        instance.view = self
        return instance

    def reorderChildren(self, tokens):
        for r in self.context.getChildRelations(sort=None):
            token = ':'.join((util.getUidForObject(r.second),
                              util.getUidForObject(r.predicate)))
            if token in tokens:
                pos = tokens.index(token) + 1
                if r.order != pos:
                    r.order = pos

    @Lazy
    def filterOptions(self):
        return self.getOptions('filter.states')

    def getChildren(self, topLevelOnly=True, sort=True, noDuplicates=True,
                    useFilter=True, predicates=None):
        form = self.request.form
        #if form.get('loops.viewName') == 'index.html' and self.editable:
        if self.editable:
            self.registerDojoDnd()
            if 'children_change_order' in form:
                tokens = form.get('child_tokens')
                if tokens:
                    self.reorderChildren(tokens)
        cm = self.loopsRoot.getConceptManager()
        hasType = cm.getTypePredicate()
        params = self.params
        criteria = {}
        if params.get('types'):
            criteria['types'] = [cm.get(name) for name in params['types']]
        standard = cm.getDefaultPredicate()
        rels = (self.childViewFactory(r, self.request, contextIsSecond=True)
                for r in self.context.getChildRelations(
                                            predicates=predicates, sort=None))
        if sort:
            rels = sorted(rels, key=lambda r: (r.order, r.title.lower()))
        from loops.organize.personal.browser.filter import FilterView
        fv = FilterView(self.context, self.request)
        for r in rels:
            if criteria:
                if not self.checkCriteria(r, criteria):
                    continue
            if topLevelOnly and r.predicate == hasType:
                # only show top-level entries for type instances:
                skip = False
                for parent in r.context.getParents((standard,)):
                    if parent.conceptType == self.context:
                        skip = True
                        break
                if skip:
                    continue
            if useFilter:
                options = IOptions(adapted(r.predicate), None)
                if options is not None and options('hide_children'):
                    continue
                if not fv.check(r.context, self.filterOptions):
                    continue
            yield r

    def checkCriteria(self, relation, criteria):
        result = True
        for k, v in criteria.items():
            if k == 'types':
                v = [item for item in v if item is not None]
                result = result and (relation.context.conceptType in v)
        return result

    # Override in subclass to control what is displayd in listings:
    children = getChildren

    def childrenAlphaGroups(self, predicates=None):
        #result = Jeep()
        result = {}
        rels = self.getChildren(predicates=predicates or [self.defaultPredicate],
                                topLevelOnly=False, sort=False)
        rels = sorted(rels, key=lambda r: r.title.lower())
        for letter, group in groupby(rels, lambda r: r.title.lower()[0]):
            letter = letter.upper()
            result[letter] = list(group)
        return result

    def childrenByType(self):
        result = Jeep()
        rels = self.getChildren(topLevelOnly=False, sort=False)
        rels = sorted(rels, key=lambda r: (r.typeTitle.lower(), r.title.lower()))
        for type, group in groupby(rels, lambda r: r.type):
            typeName = getName(type.typeProvider)
            result[typeName] = list(group)
        return result

    def isHidden(self, pr):
        predOptions = IOptions(adapted(pr.predicate))
        if predOptions('hide_parents'):
            return True
        if (getName(pr.second.conceptType) in predOptions('hide_parents_for', [])):
            return True
        hideRoles = None
        options = component.queryAdapter(adapted(pr.first), IOptions)
        if options is not None:
            hideRoles = options('hide_for', None)
        if not hideRoles:
            hideRoles = IOptions(adapted(pr.first.conceptType))('hide_for', None)
        if hideRoles is not None:
            principal = self.request.principal
            if (IUnauthenticatedPrincipal.providedBy(principal) and
                'zope.Anonymous' in hideRoles):
                return True
            roles = getRolesForPrincipal(principal.id, self.context)
            for r in roles:
                if r in hideRoles:
                    return True
        return False

    @Lazy
    def parentsForPortlet(self):
        return [p for p in self.parents() if not self.isHidden(p.relation)]

    def parents(self):
        rels = sorted(self.context.getParentRelations(),
                      key=(lambda x: x.first.title and x.first.title.lower()))
        for r in rels:
            yield self.childViewFactory(r, self.request)

    def reorderResources(self, tokens):
        for r in self.context.getResourceRelations(sort=None):
            token = ':'.join((util.getUidForObject(r.second),
                              util.getUidForObject(r.predicate)))
            if token in tokens:
                pos = tokens.index(token) + 1
                if r.order != pos:
                    r.order = pos

    def getResources(self, relView=None, sort='default'):
        form = self.request.form
        #if form.get('loops.viewName') == 'index.html' and self.editable:
        if self.editable:
            self.registerDojoDnd()
            if 'resources_change_order' in form:
                tokens = form.get('resources_tokens')
                if tokens:
                    self.reorderResources(tokens)
        if relView is None:
            from loops.browser.resource import ResourceRelationView
            relView = ResourceRelationView
        from loops.organize.personal.browser.filter import FilterView
        fv = FilterView(self.context, self.request)
        rels = self.context.getResourceRelations(sort=sort)
        for r in rels:
            if fv.check(r.first):
                view = relView(r, self.request, contextIsSecond=True)
                if view.checkState():
                    yield view

    def resources(self):
        return self.getResources()

    @Lazy
    def resourcesList(self):
        from loops.browser.resource import ResourceRelationView
        return [ResourceRelationView(r, self.request, contextIsSecond=True)
                    for r in self.context.getResourceRelations()]

    @Lazy
    def resourcesByType(self):
        result = dict(texts=[], images=[], files=[])
        for rv in self.resourcesList:
            r = rv.context
            if r.contentType.startswith('text/'):
                result['texts'].append(r)
            if r.contentType.startswith('image/'):
                result['images'].append(r)
            else:
                result['files'].append(r)
        return result

    def unique(self, rels):
        result = Jeep()
        for r in rels:
            existing = result.get(r.hash)
            if existing is not None:
                existing.relations.append(r)
                existing.predicates.append(r.predicate)
            else:
                result[r.hash] = r
        return result.values()

    @Lazy
    def view(self):
        context = self.context
        name = self.request.get('loops.viewName') or getattr(self, '_viewName', None)
        if not name:
            ti = IType(context).typeInterface
            # TODO: check the interface (maybe for a base interface IViewProvider)
            #       instead of the viewName attribute:
            if ti and ti != ITypeConcept and 'viewName' in ti:
                typeAdapter = ti(self.context)
                name = typeAdapter.viewName
        if not name:
            tp = IType(context).typeProvider
            if tp:
               name = ITypeConcept(tp).viewName
        if name:
            if '?' in name:
                name, params = name.split('?', 1)
                ann = self.request.annotations.get('loops.view', {})
                ann['params'] = params
                self.request.annotations['loops.view'] = ann
            # ??? Would it make sense to use a somehow restricted interface
            #     that should be provided by the view like IQuery?
            #viewInterface = getattr(typeAdapter, 'viewInterface', None) or IQuery
            adapter = component.queryMultiAdapter((context, self.request),
                                                  name=name)
            if adapter is not None:
                return adapter
        #elif type provides view: use this
        return self

    def clients(self):
        from loops.browser.node import NodeView  # avoid circular import
        for node in self.context.getClients():
            yield NodeView(node, self.request)

    def getPortletActions(self, page=None, target=None):
        if self.portlet_actions:
            return actions.get('portlet', self.portlet_actions,
                                view=self, page=page, target=target)
        return []

    def getObjectActions(self, page=None, target=None):
        acts = ['info']
        if self.globalOptions('organize.allowSendEmail'):
            acts.append('send_email')
        acts.extend('state.' + st.statesDefinition for st in self.states)
        return actions.get('object', acts, view=self, page=page, target=target)

    actions = dict(object=getObjectActions, portlet=getPortletActions)

    def checkAction(self, name, category, target):
        if name in (self.typeOptions('hide_action.' + category) or []):
            return False
        return super(ConceptView, self).checkAction(name, category, target)

    @Lazy
    def workItems(self):
        return component.getMultiAdapter((self.context, self.request),
                                         name='taskworkitems.html')


class ConceptRelationView(ConceptView, BaseRelationView):

    __init__ = BaseRelationView.__init__

    getData = BaseRelationView.getData


class ConceptConfigureView(ConceptView):

    def update(self):
        request = self.request
        action = request.get('action')
        if action is None:
            return True
        if action == 'create':
            self.createAndAssign()
            return True
        tokens = request.get('tokens', [])
        for token in tokens:
            parts = token.split(':')
            cToken = parts[0]
            if len(parts) > 1:
                relToken = parts[1]
            concept = self.loopsRoot.loopsTraverse(cToken)
            if action == 'assign':
                assignAs = request.get('assignAs', 'child')
                predicate = request.get('predicate') or None
                order = int(request.get('order') or 0)
                relevance = float(request.get('relevance') or 1.0)
                if predicate:
                    predicate = removeSecurityProxy(
                                    self.loopsRoot.loopsTraverse(predicate))
                if assignAs == 'child':
                    self.context.assignChild(removeSecurityProxy(concept), predicate,
                                             order, relevance)
                elif assignAs == 'parent':
                    self.context.assignParent(removeSecurityProxy(concept), predicate,
                                             order, relevance)
                elif assignAs == 'resource':
                    self.context.assignResource(removeSecurityProxy(concept), predicate,
                                             order, relevance)
                else:
                    raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
            elif action in( 'remove',):
                predicate = self.loopsRoot.loopsTraverse(relToken)
                qualifier = request.get('qualifier')
                if 'form.button.submit' in request:
                    if predicate == self.typePredicate:
                        continue
                    if qualifier == 'parents':
                        self.context.deassignParent(concept, [predicate])
                    elif qualifier == 'children':
                        self.context.deassignChild(concept, [predicate])
                    elif qualifier == 'resources':
                        self.context.deassignResource(concept, [predicate])
                    elif qualifier == 'concepts':
                        self.context.deassignConcept(concept, [predicate])
                    else:
                        raise(BadRequest, 'Illegal qualifier: %s.' % qualifier)
                elif 'form.button.change_relations' in request:
                    self.changeRelation(concept, predicate, qualifier,
                                        request.get('order.' + token),
                                        request.get('relevance.' + token))
            else:
                    raise(BadRequest, 'Illegal action: %s.' % action)
        return True

    def changeRelation(self, target, predicate, qualifier, order, relevance):
        methodNames = dict(parents='getParentRelations',
                           children='getChildRelations',
                           resources='getResourceRelations',
                           concepts='getConceptRelations')
        method = getattr(self.context, methodNames[qualifier])
        for r in method([predicate], target):
            r.order = int(order or 0)
            r.relevance = float(relevance or 1.0)

    def createAndAssign(self):
        request = self.request
        name = request.get('create.name')
        if not name:
            raise(BadRequest, 'Empty name.')
        title = request.get('create.title', u'')
        token = self.request.get('create.type')
        type = ITypeManager(self.context).getType(token)
        factory = type.factory
        container = type.defaultContainer
        concept = removeSecurityProxy(factory(title))
        container[name] = concept
        if IConcept.providedBy(concept):
            concept.conceptType = type.typeProvider
        elif IResource.providedBy(concept):
            concept.resourceType = type.typeProvider
        notify(ObjectCreatedEvent(concept))
        notify(ObjectModifiedEvent(concept))
        assignAs = self.request.get('assignAs', 'child')
        predicate = request.get('create.predicate') or None
        if predicate:
            predicate = removeSecurityProxy(
                            self.loopsRoot.loopsTraverse(predicate))
        order = int(request.get('create.order') or 0)
        relevance = float(request.get('create.relevance') or 1.0)
        if assignAs == 'child':
            self.context.assignChild(concept, predicate, order, relevance)
        elif assignAs == 'parent':
            self.context.assignParent(concept, predicate, order, relevance)
        elif assignAs == 'resource':
            self.context.assignResource(concept, predicate, order, relevance)
        elif assignAs == 'concept':
            self.context.assignConcept(concept, predicate, order, relevance)
        else:
            raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)

    def search(self):
        request = self.request
        if request.get('action') != 'search':
            return []
        searchTerm = request.get('searchTerm', None)
        searchType = request.get('searchType', None)
        result = []
        if searchTerm or searchType != 'none':
            criteria = {}
            if searchTerm:
                criteria['loops_title'] = searchTerm
            if searchType:
                if searchType.endswith('*'):
                    start = searchType[:-1]
                    end = start + '\x7f'
                else:
                    start = end = searchType
                criteria['loops_type'] = (start, end)
            cat = component.getUtility(ICatalog)
            result = cat.searchResults(**criteria)
            # TODO: can this be done in a faster way?
            result = [r for r in result if r.getLoopsRoot() == self.loopsRoot]
        else:
            result = self.loopsRoot.getConceptManager().values()
        if searchType == 'none':
            result = [r for r in result if r.conceptType is None]
        result = sorted(result, key=lambda x: x.title)
        return self.viewIterator(result)

    def predicates(self):
        preds = PredicateSourceList(self.context)
        terms = component.getMultiAdapter((preds, self.request), ITerms)
        for pred in preds:
            yield terms.getTerm(pred)


# query views

class ListChildren(ConceptView):

    @Lazy
    def macro(self):
        return concept_macros.macros['list_children']


class ListTypeInstances(ListChildren):

    @Lazy
    def targets(self):
        targetPredicate = self.conceptManager['querytarget']
        for c in self.context.getChildren([targetPredicate]):
            # TODO: use type-specific view
            yield ConceptView(c, self.request)

    def children(self, topLevelOnly=True, sort=True, noDuplicates=True,
                    useFilter=True, predicates=None):
        # TODO: use filter options of query for selection of children
        for tv in self.targets:
            tv.filterOptions = self.filterOptions
            for c in tv.getChildren(topLevelOnly, sort,
                                    noDuplicates, useFilter, [self.typePredicate]):
                yield c


class TabbedPage(ConceptView):

    @Lazy
    def subpagePredicates(self):
        pred = self.conceptManager.get('issubpage')
        if pred is None:
            pred = self.isPartOfPredicate
        return [pred]

    def viewModes(self):
        modes = Jeep()
        for s in self.getSiblings(self.subpagePredicates):
            url = self.nodeView.getUrlForTarget(s)
            modes.append(ViewMode(getName(s), s.title, url))
        if not modes:
            return modes
        modes[getName(self.context)].active = True
        return modes

    def getSiblings(self, preds):
        for p in self.context.getParents(preds):
            parent = p
            break
        else:
            return []
        return p.getChildren(preds)
