# -*- 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 Cards Common Models"""

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


import faults
from ObjectsCommon import AdminCommon, ObjectCommon, ObjectsCommonMixin
import slots
import tools_new as commonTools


class AdminCardsCommon(AdminCommon):
    serverRole = 'cards'

    # Ids of cards which can be easily selected by the users as prototypes of
    # new cards.
    prototypeIds = None
    prototypeIds_kind_itemKind_value_serverRoles = ['cards']
    prototypeIds_kind_itemKind_valueName = 'Id'
    prototypeIds_kind_label = N_('Cards Models')
    prototypeIds_kindName = 'Sequence'


class CardCommon(ObjectCommon):
    language_kindName = None

    defaultImplementModeName = None
    class defaultImplementModeName_kindClass:
        _kindName = 'Choice'
        label = N_('Create Mode')
        widget_noneLabel = N_('Inherited')

        def getValues(self, slot):
            card = slot.getContainer()
            return card.getModeNames()

    defaultListModeName = None
    class defaultListModeName_kindClass:
        _kindName = 'Choice'
        label = N_('List Mode')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        widget_noneLabel = N_('Inherited')

        def getValues(self, slot):
            return ['viewAll']

    defaultModeName = None
    class defaultModeName_kindClass:
        _kindName = 'Choice'
        label = N_('Default Mode')
        widget_noneLabel = N_('Inherited')

        def getValues(self, slot):
            card = slot.getContainer()
            return card.getModeNames()

    editMode = None
    class editMode_kindClass:
        _kindName = 'Mode'
        importExport = 'private'
        label = N_('Edit Mode')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        valueThingName = 'Edit'

        def getLabels(self, slot):
            return {'Edit': slot.getValue().getMenuLabel()}

        def getter(self, slot):
            card = slot.getContainer()
            if card.editMode is None:
                card.editMode = commonTools.newThing('mode', 'Edit')
            return card.editMode

    editModeUsersSet = None
    class editModeUsersSet_kindClass:
        _kindName = 'UsersSet'
        itemKind_value_widget_noneLabel = N_('Inherited')
        label = N_('Edit Mode Users')

    modes = None
    class modes_kindClass:
        _kindName = 'Sequence'
        class itemKind_valueClass:
            _kindName = 'Mode'
            values = [None, 'Custom']

            def getLabels(self, slot):
                card = slot.parent.getContainer()
                if card.modes is not None and len(card.modes) > slot.index \
                       and card.modes[slot.index] is not None:
                    label = card.modes[slot.index].getMenuLabel()
                else:
                    label = N_('New Custom Mode')
                return {'Custom': label}
        label = N_('Modes')

    properties = None
    properties_kind_label = N_('Fields')
    properties_kindName = 'Properties'

    prototypeIds = None
    prototypeIds_kind_itemKind_value_serverRoles = ['cards']
    prototypeIds_kind_itemKind_valueName = 'Id'
    prototypeIds_kind_label = N_('Prototypes')
    prototypeIds_kind_stateInViewMode = 'read-only/hidden-if-empty'
    prototypeIds_kindName = 'Sequence'

    serverRole = 'cards'

    values = None

    viewMode = None
    class viewMode_kindClass:
        _kindName = 'Mode'
        importExport = 'private'
        label = N_('View Mode')
        stateInEditMode = 'hidden'
        stateInViewMode = 'hidden'
        valueThingName = 'View'

        def getLabels(self, slot):
            return {'View': slot.getValue().getMenuLabel()}

        def getter(self, slot):
            card = slot.getContainer()
            if card.viewMode is None:
                card.viewMode = commonTools.newThing('mode', 'View')
            return card.viewMode

    viewModeUsersSet = None
    class viewModeUsersSet_kindClass:
        _kindName = 'UsersSet'
        itemKind_value_widget_noneLabel = N_('Inherited')
        label = N_('View Mode Users')

    def canCache(self):
        if self.hasSlotName('readersSet'):
            import glasnost.proxy.GroupsProxy as proxyGroups
            try:
                proxyGroups.getSetContainedIds(
                        self.getSlot('readersSet').getValue(),
                        serverRoles = ['identities'],
                        raiseWhenUncountable = 1)
            except faults.UncountableGroup:
                # Even non identified users can read the object. We can cache
                # it.
                return 1
            return 0
        return 1

    def getC3Linearization(self):
        # An implementation of the C3 class hierarchy Linearization for the
        # cards prototypes.
        # Cf. http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
        return [self] + self.getC3PrototypeLinearization()

    def getC3LinearizationIds(self):
        # An implementation of the C3 class hierarchy Linearization for the
        # cards prototypes.
        # Cf. http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
        assert self.id
        return [self.id] + self.getC3PrototypeLinearizationIds()

    def getC3PrototypeLinearization(self):
        # An implementation of the C3 class hierarchy Linearization for the
        # cards prototypes.
        # Cf. http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
        # Note: Only the prototypes are put in this linearization, not "self"
        # itself. 
        linearization = []
        if self.prototypeIds:
            prototypes = []
            for prototypeId in self.prototypeIds:
                try:
                    prototype = self.getCard(prototypeId)
                except faults.MissingItem:
                    pass
                else:
                    prototypes.append(prototype)
            if prototypes:
                prototypesByPrecedenceOrder = prototypes[:]
                prototypesByPrecedenceOrder.reverse()
                remainingInputs = [prototype.getC3Linearization()
                                   for prototype in prototypes] \
                        + [prototypesByPrecedenceOrder]
                while remainingInputs:
                    # The first class which is not in the tail of each
                    # remaining input is the next class to append to the
                    # linearization.
                    for remainingInput in remainingInputs:
                        candidate = remainingInput[0]
                        for remainingInput2 in remainingInputs:
                            if candidate in remainingInput2[1:]:
                                break
                        else:
                            break
                    else:
                        raise faults.InconsistentPrototypeHierarchy(self)
                    # A good candidate has been found.
                    # Add it  to the partial linearization and remove it from
                    # the remaining inputs.
                    linearization.append(candidate)
                    newRemainingInputs = []
                    for remainingInput in remainingInputs:
                        if remainingInput[0] == candidate:
                            del remainingInput[0]
                            if not remainingInput:
                                continue
                        newRemainingInputs.append(remainingInput)
                    remainingInputs = newRemainingInputs
        return linearization

    def getC3PrototypeLinearizationIds(self):
        # An implementation of the C3 class hierarchy Linearization for the
        # cards prototypes.
        # Cf. http://www.webcom.com/haahr/dylan/linearization-oopsla96.html
        # Note: Only the prototypes are put in this linearization, not "self"
        # itself. 
        linearization = []
        if self.prototypeIds:
            prototypeIds = []
            prototypes = []
            for prototypeId in self.prototypeIds:
                try:
                    prototype = self.getCard(prototypeId)
                except faults.MissingItem:
                    pass
                else:
                    prototypeIds.append(prototypeId)
                    prototypes.append(prototype)
            if prototypeIds:
                prototypeIdsByPrecedenceOrder = prototypeIds[:]
                prototypeIdsByPrecedenceOrder.reverse()
                remainingInputs = [prototype.getC3LinearizationIds()
                                   for prototype in prototypes] \
                        + [prototypeIdsByPrecedenceOrder]
                while remainingInputs:
                    # The first class which is not in the tail of each
                    # remaining input is the next class to append to the
                    # linearization.
                    for remainingInput in remainingInputs:
                        candidate = remainingInput[0]
                        for remainingInput2 in remainingInputs:
                            if candidate in remainingInput2[1:]:
                                break
                        else:
                            break
                    else:
                        raise faults.InconsistentPrototypeHierarchy(self)
                    # A good candidate has been found.
                    # Add it  to the partial linearization and remove it from
                    # the remaining inputs.
                    linearization.append(candidate)
                    newRemainingInputs = []
                    for remainingInput in remainingInputs:
                        if remainingInput[0] == candidate:
                            del remainingInput[0]
                            if not remainingInput:
                                continue
                        newRemainingInputs.append(remainingInput)
                    remainingInputs = newRemainingInputs
        return linearization

    def getCard(self, cardId):
        raise NotImplementedError

    def getDefaultImplementModeName(self):
        if self.defaultImplementModeName is not None:
            return self.defaultImplementModeName
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.defaultImplementModeName is not None:
                return prototype.defaultImplementModeName
        return 'edit'

    def getDefaultListModeName(self):
        if self.defaultListModeName is not None:
            return self.defaultListModeName
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.defaultListModeName is not None:
                return prototype.defaultListModeName
        return 'viewAll'

    def getDefaultModeName(self):
        if self.defaultModeName is not None:
            return self.defaultModeName
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.defaultModeName is not None:
                return prototype.defaultModeName
        return 'view'

    def getDirectMode(self, modeName):
        if self.modes:
            for mode in self.modes:
                if mode.name == modeName:
                    return mode
        raise faults.MissingItem(modeName)

    def getDirectModeNames(self):
        if self.modes is None:
            return []
        return [mode.getName() for mode in self.modes]

    def getDirectPropertyNames(self):
        if self.properties is None:
            return []
        return [property.name for property in self.properties]

    def getDirectPropertyValue(self, propertyName):
        if not self.hasDirectPropertyValue(propertyName):
            raise faults.MissingItem(propertyName)
        return self.values[propertyName]

    def getDirectPropertyValueKind(self, propertyName):
        if self.properties:
            for property in self.properties:
                if property.name == propertyName:
                    return property.kind
        return None

    def getEmail(self):
        if not self.hasSlotName('email'):
            return None
        return self.getSlot('email')

# This method has ben commented, because it doesn't work when a property name
# has changed: self.values may contain an item whose name doesn't correspond to
# a property anymore.
##     def getExportSlotNames(self, parentSlot = None):
##         slotNames = ObjectCommon.getExportSlotNames(
##                 self, parentSlot = parentSlot)
##         if self.values is None:
##             return slotNames
##         return slotNames + self.values.keys()

    def getFingerprint(self):
        if not self.hasSlotName('fingerprint'):
            return None
        return self.getSlot('fingerprint')

    def getImportSlotNames(self, parentSlot = None):
        names = ObjectCommon.getImportSlotNames(self, parentSlot = parentSlot)
        names = names[:]
        if 'properties' in names:
            # The slot 'properties' is already imported by the method
            # importFromXmlRpc.
            names.remove('properties')
        if 'prototypeIds' in names:
            # The slot 'prototypeIds' is already imported by the method
            # importFromXmlRpc.
            names.remove('prototypeIds')
        return names

    def getInheritedMode(self, modeName):
        # FIXME: We should use a Python iterator for computing the C3
        # linearization, because here we use only the first prototype. 
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.hasDirectMode(modeName):
                return prototype.getDirectMode(modeName)
        raise faults.MissingItem(modeName)

    def getInheritedPropertyValue(self, propertyName, propertyKind):
        # FIXME: We should use a Python iterator for computing the C3
        # linearization, because here we use only the first prototype. 
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.hasDirectPropertyValue(propertyName):
                return prototype.getDirectPropertyValue(propertyName)
        return None

    def getLabel(self):
        if self.hasSlotName('title'):
            label = self.getSlot('title').getValue()
            if label:
                return label
        if self.id is None:
            return 'Untitled Card'
        else:
            return 'Card number %s' % commonTools.extractLocalId(self.id)

    def getLabelLanguage(self):
        language = self.getLanguage()
        if language is None:
            language = ''
        return language

    def getLanguage(self):
        if not self.hasSlotName('language'):
            return None
        return self.getSlot('language')

    def getMode(self, modeName, parentSlot = None):
        if modeName == 'edit':
            slot = self.getSlot('editMode', parentSlot = parentSlot)
            return slot.getValue()
        if modeName == 'view':
            slot = self.getSlot('viewMode', parentSlot = parentSlot)
            return slot.getValue()
        if self.hasDirectMode(modeName):
            return self.getDirectMode(modeName)
        return self.getInheritedMode(modeName)

    def getModeNames(self):
        modeNames = ['edit', 'view']
        prototypes = self.getC3PrototypeLinearization()
        prototypes.reverse()
        prototypes.append(self)
        for prototype in prototypes:
            for modeName in prototype.getDirectModeNames():
                if modeName not in modeNames:
                    modeNames.append(modeName)
        return modeNames

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        slotNames = ObjectCommon.getOrderedLayoutSlotNames(
                self, parentSlot = parentSlot)
        slotNames += ['prototypeIds', 'properties', 'modes',
                      'viewModeUsersSet', 'viewMode', 'editModeUsersSet',
                      'editMode', 'defaultModeName',
                      'defaultImplementModeName', 'defaultListModeName']
        # If a standard slotName is also a propertyName, display it using the
        # property order. For example, if a card has a "language" property,
        # this property is not displayed before the other properties.
        propertyNames = self.getPropertyNames()
        nonPropertyNames = [slotName
                            for slotName in slotNames
                            if not slotName in propertyNames]
        return nonPropertyNames + propertyNames

    def getPropertiesCount(self):
        return len(self.getPropertyNames())

    def getPropertyNames(self):
        propertyNames = []
        prototypes = self.getC3PrototypeLinearization()
        prototypes.reverse()
        prototypes.append(self)
        for prototype in prototypes:
            for propertyName in prototype.getDirectPropertyNames():
                if propertyName not in propertyNames:
                    propertyNames.append(propertyName)
        return propertyNames

    def getPropertySlot(self, propertyName, parentSlot = None):
        propertiesSlot = self.getSlot('properties', parentSlot = parentSlot)
        return slots.Property(propertyName, parent = propertiesSlot)

    def getPropertyValue(self, propertyName, propertyKind):
        if self.hasDirectPropertyValue(propertyName):
            return self.getDirectPropertyValue(propertyName)
        return self.getInheritedPropertyValue(propertyName, propertyKind)

    def getPropertyValueKind(self, propertyName):
        kind = self.getDirectPropertyValueKind(propertyName)
        if kind is not None:
            return kind
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            kind = prototype.getDirectPropertyValueKind(propertyName)
            if kind is not None:
                return kind
        raise Exception('Property "%s" of %s has no kind' % (
                propertyName, self))

    def getPropertyValueSlot(self, propertyName, parentSlot = None):
        if parentSlot is None:
            container = self
        else:
            container = None
        return slots.PropertyValue(
            propertyName, container = container, parent = parentSlot)

    def getSlot(self, attributeName, parentSlot = None):
        if attributeName in ObjectCommon.getSlotNames(
                self, parentSlot = parentSlot):
            return ObjectCommon.getSlot(
                    self, attributeName, parentSlot = parentSlot)
        return self.getPropertyValueSlot(
            attributeName, parentSlot = parentSlot)

    def getSlotNames(self, parentSlot = None):
        slotNames = ObjectCommon.getSlotNames(self, parentSlot = parentSlot)
        return slotNames + self.getPropertyNames()

    def hasDirectMode(self, modeName):
        if self.modes:
            for mode in self.modes:
                if mode.name == modeName:
                    return 1
        return 0

    def hasDirectPropertyValue(self, propertyName):
        return self.values and self.values.has_key(propertyName)

    def hasInheritedMode(self, modeName):
        # FIXME: We should use a Python iterator for computing the C3
        # linearization, because here we use only the first prototype. 
        prototypes = self.getC3PrototypeLinearization()
        for prototype in prototypes:
            if prototype.hasDirectMode(modeName):
                return 1
        return 0

    def hasMode(self, modeName):
        if modeName in ['edit', 'view']:
            return 1
        if self.hasDirectMode(modeName):
            return 1
        return self.hasInheritedMode(modeName)

    def importFromXmlRpc(self, dataImport, parentSlot = None):
        # The slots 'properties' and 'prototypeIds' must be imported first,
        # because they are needed to compute the slotNames list.
        if dataImport.has_key('__noneSlotNames__'):
            noneSlotNames = dataImport['__noneSlotNames__']
        else:
            noneSlotNames = []
        for slotName in ['properties', 'prototypeIds']:
            if dataImport.has_key(slotName):
                exportedValue = dataImport[slotName]
            elif slotName in noneSlotNames:
                exportedValue = None
            else:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            kind = slot.getKind()
            if not kind.isImportable():
                continue
            slot.setValue(kind.importValueFromXmlRpc(slot, exportedValue))
        return ObjectCommon.importFromXmlRpc(
                self, dataImport, parentSlot = parentSlot)

    def mustCryptEmails(self):
        if not self.hasSlotName('cryptEmails'):
            return 0
        if self.getSlot('cryptEmails'):
            return 1
        else:
            return 0

    def setDirectPropertyValue(self, propertyName, value):
        if self.values is None:
            self.values = {}
        self.values[propertyName] = value

    def setDirectPropertyValueKind(self, propertyName, propertyKind):
        if self.properties is None:
            self.properties = []
        else:
            for i in range(len(self.properties)):
                if self.properties[i].name == propertyName:
                    if propertyKind is None:
                        del self.properties[i]
                        return
                    else:
                        property = self.properties[i]
                        property.kind = propertyKind
                        return
        if propertyKind is not None:
            property = commonTools.newThing('other', 'Property')
            property.kind = propertyKind
            property.name = propertyName
            self.properties.append(property)

    def setPropertyValue(self, propertyName, propertyKind, value):
        if self.values is None:
            self.values = {}
        if value == self.getInheritedPropertyValue(propertyName, propertyKind):
            if self.values.has_key(propertyName):
                del self.values[propertyName]
                if not self.values:
                    del self.values
        else:
            self.values[propertyName] = value


class CardsCommonMixin(ObjectsCommonMixin):
    adminClassName = 'AdminCards'
    newObjectNameCapitalized = N_('New Card')
    objectClassName = 'Card'
    objectName = N_('card')
    objectNameCapitalized = N_('Card')
    objectsName = N_('cards')
    objectsNameCapitalized = N_('Cards')
    serverRole = 'cards'
