# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


__doc__ = """Glasnost Groups Common Models"""

__version__ = '$Revision$'[11:-2]


import context
import faults
import ObjectsCommon as objects
import system
import tools_new as commonTools


class AdminGroups(objects.AdminCommon):
    serverRole = 'groups'


class GroupAbstract(objects.ObjectCommon):
    # FIXME: Replace by kind ServerRoles.
    acceptedRoles = None
    acceptedRoles_kindName = 'AcceptedRoles'

    name = None
    name_kind_balloonHelp = N_('A meaningful name for this group.')
    name_kind_isRequired = 1
    name_kindName = 'String'

    readersSet = [system.generalPublicId]
    readersSet_kindName = 'ReadersSet'

    serverRole = 'groups'

    writersSet = None
    writersSet_kindName = 'WritersSet'

    def canCache(self):
        return 1

    def contains(self, objectId, indirect = 1, resultWhenUnknown = 0):
        """Indicates whether the group contains an element."""

        result = self.contains1(objectId, indirect, {})
        if result is None:
            result = resultWhenUnknown
        return result

    def contains1(self, objectId, indirect, results):
        if results.has_key(self.id):
            return results[self.id]
        results[self.id] = None
        result = self.contains2(objectId, indirect, results)
        results[self.id] = result
        return result

    def contains2(self, objectId, indirect, results):
        raise NotImplementedError

    def getContainedIds(self, serverRoles = None, resultWhenUnknown = 0,
                        raiseWhenUncountable = 0):
        """Return the ids of the objects contained in the group."""

        small, large = self.getContainedIds1(serverRoles, {})
        if resultWhenUnknown:
            result = large
        else:
            result = small
        if result == 'uncountable':
            if raiseWhenUncountable:
                raise faults.UncountableGroup(self)
            else:
                result = []
        return result

    def getContainedIds1(self, serverRoles, results):
        if results.has_key(self.id):
            return results[self.id]
        results[self.id] = None
        smallAndLarge = self.getContainedIds2(serverRoles, results)
        results[self.id] = smallAndLarge
        return smallAndLarge

    def getContainedIds2(self, serverRoles, results):
        raise NotImplementedError

    def getGroup(self, groupId):
        raise NotImplementedError

    def getGroupContainedIds(self, groupId, serverRoles, results):
        if groupId == system.generalPublicId:
            if results.has_key(groupId):
                return results[groupId]
            if not serverRoles or 'identities' in serverRoles \
                    or 'people' in serverRoles:
                smallAndLarge = 'uncountable', 'uncountable'
            else:
                smallAndLarge = [], []
            results[groupId] = smallAndLarge
            return smallAndLarge
        else:
            return self.getGroup(groupId).getContainedIds1(
                    serverRoles, results)

    def getLabel(self):
        label = self.name
        if label is None:
            return ''
        return label

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = objects.ObjectCommon.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['name', 'className', 'acceptedRoles', 'writersSet']
        return slotNames

    def groupContains(self, groupId, objectId, indirect, results):
        if groupId == system.generalPublicId:
            if results.has_key(groupId):
                return results[groupId]
            result = not objectId \
                     or commonTools.extractRole(objectId) in [
                             'identities', 'people']
            results[groupId] = result
            return result
        else:
            return self.getGroup(groupId).contains1(
                    objectId, indirect, results)


class GroupCountableAbstract(GroupAbstract):
    membersSet = None
    class membersSet_kindClass:
        _kindName = 'Sequence'
        class itemKind_valueClass:
            _kindName = 'Id'
            def getServerRoles(self, slot):
                if not slot:
                    return []
                acceptedRoles = slot.getObject().acceptedRoles
                if acceptedRoles is None:
                    return []
                if acceptedRoles == ['__all__']:
                    return None
                return acceptedRoles
        label = N_('Members')

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        import glasnost.proxy.GroupsProxy as proxyGroups
        import glasnost.web.GroupsWeb as webGroups
        slotNames = GroupAbstract.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        i = slotNames.index('writersSet')
        slotNames = slotNames[:]
        slotNames[i:i] = ['membersSet', 'itemIds']
        return slotNames


class GroupAll(GroupAbstract):
    def contains2(self, objectId, indirect, results):
        if not objectId:
            return 1
        if self.acceptedRoles is None:
            return 1
        role = commonTools.extractRole(objectId)
        return role in self.acceptedRoles

    def getContainedIds2(self, serverRoles, results):
        if not serverRoles or self.acceptedRoles is None:
            return 'uncountable', 'uncountable'
        for acceptedRole in self.acceptedRoles:
            if acceptedRole in serverRoles:
                return 'uncountable', 'uncountable'
        return [], []


class GroupDelta(GroupCountableAbstract):
    membersSet_kind_balloonHelp = N_('Select the items to subtract.')

    def contains2(self, objectId, indirect, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return 0
        elementId = self.membersSet[0]
        if elementId != objectId and not isGroupId(elementId):
            return 0
        nonElementIds = []
        for nonElementId in self.membersSet[1:]:
            if nonElementId == objectId:
                return 0
            elif not isGroupId(nonElementId):
                pass
            else:
                nonElementIds.append(nonElementId)
        if not indirect:
            return elementId == objectId
        if self.groupContains(elementId, objectId, indirect, results) == 0:
            return 0
        result = 1
        for nonElementId in nonElementIds:
            nonElementResult = self.groupContains(
                    nonElementId, objectId, indirect, results)
            if nonElementResult is None:
                result = None
            elif nonElementResult:
                return 0
        return result

    def getContainedIds2(self, serverRoles, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return [], []

        elementId = self.membersSet[0]
        if isGroupId(elementId):
            result = self.getGroupContainedIds(elementId, serverRoles, results)
            if result is None:
                raise faults.IllegalRecursiveGroup(elementId)
            if not result[1]:
                return [], []
        else:
            if serverRoles:
                elementServerRole = commonTools.extractRole(elementId)
                if not elementServerRole in serverRoles:
                    return [], []
            result = [[elementId], [elementId]]
        for memberId in self.membersSet[1:]:
            if isGroupId(memberId):
                memberResult = self.getGroupContainedIds(
                        memberId, serverRoles, results)
                if memberResult is None:
                    raise faults.IllegalRecursiveGroup(memberId)
                memberResult.reverse()
                for i in [0, 1]:
                    if memberResult[i] == 'uncountable':
                        result[i] = []
                    elif result[i] == 'uncountable':
                        pass
                    else:
                        for nonElementId in memberResult[i]:
                            if nonElementId in result[i]:
                                result[i].remove(nonElementId)
            else:
                if serverRoles:
                    memberServerRole = commonTools.extractRole(memberId)
                    if not memberServerRole in serverRoles:
                        continue
                for i in [0, 1]:
                    if result[i] == 'uncountable':
                        pass
                    elif memberId in result[i]:
                        result[i].remove(memberId)
            if not result[1]:
                return [], []
        return result


class GroupIntersection(GroupCountableAbstract):
    membersSet_kind_balloonHelp = N_('Select the items to intersect.')

    def contains2(self, objectId, indirect, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return 0
        result = 1
        memberIds = []
        for memberId in self.membersSet:
            if memberId == objectId:
                pass
            elif not isGroupId(memberId):
                return 0
            else:
                memberIds.append(memberId)
        if not indirect:
            return not memberIds
        for memberId in memberIds:
            memberResult = self.groupContains(
                    memberId, objectId, indirect, results)
            if memberResult is None:
                result = None
            elif not memberResult:
                return 0
        return result

    def getContainedIds2(self, serverRoles, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return [], []
        result = ['uncountable', 'uncountable']
        for memberId in self.membersSet:
            if isGroupId(memberId):
                memberResult = self.getGroupContainedIds(
                        memberId, serverRoles, results)
                if memberResult is None:
                    raise faults.IllegalRecursiveGroup(memberId)
                for i in [0, 1]:
                    if memberResult[i] == 'uncountable':
                        continue
                    elif not memberResult[i]:
                        result[i] = []
                    else:
                        if result[i] == 'uncountable':
                            result[i] = memberResult[i][:]
                        else:
                            for elementId in result[i][:]:
                                if not elementId in memberResult[i]:
                                    result[i].remove(elementId)
            else:
                if serverRoles:
                    memberServerRole = commonTools.extractRole(memberId)
                    if not memberServerRole in serverRoles:
                        return [], []
                for i in [0, 1]:
                    if result[i] == 'uncountable' or memberId in result[i]:
                        result[i] = [memberId]
                    else:
                        result[i] = []
            if not result[1]:
                return [], []
        return result


class GroupRole(GroupAbstract):
    def contains2(self, objectId, indirect, results):
        if self.acceptedRoles is None:
            return 1
        if not objectId:
            return 0
        role = commonTools.extractRole(objectId)
        return role in self.acceptedRoles

    def getContainedIds2(self, serverRoles, results):
        # FIXME: not sure ?
        if not serverRoles or self.acceptedRoles is None:
            return 'uncountable', 'uncountable'
        for acceptedRole in self.acceptedRoles:
            if acceptedRole in serverRoles:
                return 'uncountable', 'uncountable'
        return [], []


class GroupUnion(GroupCountableAbstract):
    membersSet_kind_balloonHelp = N_('Select a member of this group. '\
            'Click on "[Others->buttons-others]" if it is not proposed in the list. '\
            'Click on "Add" to add another one. '\
            'Select "None" to remove one.')

    def contains2(self, objectId, indirect, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return 0
        result = 0
        memberIds = []
        for memberId in self.membersSet:
            if memberId == objectId:
                return 1
            elif isGroupId(memberId):
                memberIds.append(memberId)
        if not indirect:
            return 0
        for memberId in memberIds:
            try:
                memberResult = self.groupContains(
                        memberId, objectId, indirect, results)
            except faults.MissingItem:
                memberResult = None
            if memberResult is None:
                result = None
            elif memberResult:
                return 1
        return result

    def getContainedIds2(self, serverRoles, results):
        if self.membersSet is None or len(self.membersSet) == 0:
            return [], []
        result = [[], []]
        for memberId in self.membersSet:
            if isGroupId(memberId):
                memberResult = self.getGroupContainedIds(
                        memberId, serverRoles, results)
                if memberResult is None:
                    raise faults.IllegalRecursiveGroup(memberId)
                for i in [0, 1]:
                    if memberResult[i] == 'uncountable':
                        result[i] = 'uncountable'
                    elif result[i] == 'uncountable':
                        pass
                    else:
                        for elementId in memberResult[i]:
                            if not elementId in result[i]:
                                result[i].append(elementId)
            else:
                if serverRoles:
                    memberServerRole = commonTools.extractRole(memberId)
                    if not memberServerRole in serverRoles:
                        continue
                for i in [0, 1]:
                    if result[i] == 'uncountable':
                        pass
                    elif not memberId in result[i]:
                        result[i].append(memberId)
            if result[0] == 'uncountable':
                return 'uncountable', 'uncountable'
        return result


class GroupsCommonMixin(objects.ObjectsCommonMixin):
    adminClassName = 'AdminGroups'
    newObjectNameCapitalized = N_('New Group')
    objectClassName = 'GroupUnion'
    objectName = N_('group')
    objectNameCapitalized = N_('Group')
    objectsName = N_('groups')
    objectsNameCapitalized = N_('Groups')
    serverRole = 'groups'


def isGroupId(id):
    return (id and GroupsCommonMixin.serverRole == commonTools.extractRole(id))

