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

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


import copy
import types

import context
import faults
import tools
import tools_new as commonTools


class ThingClasses:
    name = None
    prototype = None
    classes = None

    def __init__(self, name, prototype = None):
        self.name = name
        if prototype is not None:
            self.prototype = prototype
        self.classes = {}

    def get(self, thingCategory, thingName):
        key = '%s_%s' % (thingCategory, thingName)
        if self.classes.has_key(key):
            return self.classes[key]
        elif thingCategory == 'object' and self.loadObjectModule(thingName) \
                 and self.classes.has_key(key):
            return self.classes[key]
        else:
            if self.prototype is None:
                raise Exception(
                        'Unknown Thing class (category = %s, name = %s) in'
                        ' %s (= %s)' % (thingCategory, thingName, self.name,
                        self.classes.keys()))
            else:
                return self.prototype.get(thingCategory, thingName)

    def getAll(self):
        if self.prototype is None:
            return self.classes.copy()
        all = self.prototype.getAll()
        all.update(self.classes)
        return all

    def loadObjectModule(self, serverRoleDotobjectName):
        return 0

    def register(self, thingClass):
        thingCategory = thingClass.thingCategory
        assert thingCategory
        thingName = thingClass.getThingName.im_func(thingClass)
        assert thingName
        key = '%s_%s' % (thingCategory, thingName)
##         assert not self.classes.has_key(key), \
##                 'Thing class (category = %s, name = %s) already registered' \
##                 ' in %s(= %s)' % (
##                 thingCategory, thingName, self.name, self.classes.keys())
        self.classes[key] = thingClass


thingClasses = ThingClasses('common')
context.setVar('thingClasses', thingClasses)


def register(thingClass):
    thingClasses.register(thingClass)


class BaseThing:
    """The base class of all glasnost things.

    This is THE super class of the most of the glasnost objects, class, etc...

    Attributes
    ==========
    
    Theses attributes are glasnost Slots. They define the kind class attributes
    instanciations.
        
    *thingCategory* (=> thingCategory_kind):
        + The category name of the thing is 'other'.
        + Not a property
        + kindName : String.
        
    *thingName* (=> thingName_kind):
        + The name of the thing is None.
        + Not a property
        + kindName : String.
    
    """
    thingCategory = 'other'
    thingCategory_kind_importExport = 'private'
    thingCategory_kind_isRequiredInEditMode = 1
    thingCategory_kind_isTranslatable = 0
    thingCategory_kind_label = N_('Internal Category')
    thingCategory_kind_stateInEditMode = 'hidden'
    thingCategory_kind_stateInViewMode = 'hidden'
    thingCategory_kindName = 'String'

    thingName = None
    thingName_kind_importExport = 'private'
    thingName_kind_isRequiredInEditMode = 1
    thingName_kind_isTranslatable = 0
    thingName_kind_label = N_('Internal Name')
    thingName_kind_stateInEditMode = 'hidden'
    thingName_kind_stateInViewMode = 'hidden'
    thingName_kindName = 'String'

    thingPublicCategory = None
    thingPublicCategory_kind_importExport = 'private'
    thingPublicCategory_kind_label = N_('Category')
    thingPublicCategory_kind_stateInEditMode = 'hidden'
    thingPublicCategory_kind_stateInViewMode = 'hidden'
    thingPublicCategory_kindName = 'String'

    thingPublicName = None
    thingPublicName_kind_importExport = 'private'
    thingPublicName_kind_label = N_('Name')
    thingPublicName_kind_stateInEditMode = 'hidden'
    thingPublicName_kind_stateInViewMode = 'hidden'
    thingPublicName_kindName = 'String'

    def __init__(self):
        pass

    def __str__(self):
        return self.getThingPublicName()

    def acquireNonCore(self, objectDirectoryPath, parentSlot = None):
        pass

    def buildKinds(self):
        """Instanciate the r'.*_kind$' attributes."""
        
        # Should be converted to a class method, when porting Glasnost to
        # Python 2.2.

        baseClasses = self.getC3ClassLinearization()

        # Build kinds from _kindName attributes.
        slotNames = [slotName
                     for slotName in self.__dict__.keys()
                     if slotName.endswith('_kindName')]
        for baseClass in baseClasses:
            for slotName in baseClass.__dict__.keys():
                if slotName.endswith('_kindName') \
                   and not slotName in slotNames:
                    slotNames.append(slotName)
        slotNames = [slotName[:-len('_kindName')]
                     for slotName in slotNames
                     if getattr(self, slotName) is not None]
        slotNames = [slotName
                     for slotName in slotNames
                     if not '_' in slotName \
                        or not slotName.split('_', 1)[0] in slotNames]
        for slotName in slotNames:
            slotOptionHeader = slotName + '_kind_'
            slotOptionHeaderLen = len(slotOptionHeader)
            slotOptionNames = [name
                               for name in self.__dict__.keys()
                               if name.startswith(slotOptionHeader)]
            for baseClass in baseClasses:
                for name in baseClass.__dict__.keys():
                    if name.startswith(slotOptionHeader) \
                       and name not in slotOptionNames:
                        slotOptionNames.append(name)
            slotOptions = {}
            for slotOptionName in slotOptionNames:
                slotOptions[slotOptionName[slotOptionHeaderLen:]] = getattr(
                    self, slotOptionName)
            kindName = getattr(self, slotName + '_kindName')
            if kindName is None or kindName == '__getter__':
                kind = None
            else:
                kind = commonTools.newThing('kind', kindName)
                kind.buildOptions(slotOptions)
            setattr(self.__class__, slotName + '_kind', kind)

        # Build kinds from _kindClass attributes.
        kindClassNames = [kindClassName
                     for kindClassName in self.__dict__.keys()
                     if kindClassName.endswith('_kindClass')]
        for baseClass in baseClasses:
            for kindClassName in baseClass.__dict__.keys():
                if kindClassName.endswith('_kindClass') \
                   and not kindClassName in kindClassNames:
                    kindClassNames.append(kindClassName)
        for kindClassName in kindClassNames:
            kindClass = getattr(self, kindClassName)
            if kindClass is None:
                continue
            realKindClass = commonTools.getThingClass(
                    'kind', kindClass._kindName)
            if kindClass.__module__.find('accounting') != -1:
                class class_(realKindClass):
                    pass
            else:
                class class_(kindClass, realKindClass):
                    pass
            kind = class_()
            kind.buildOptions({})
            setattr(self.__class__,
                    '%s_kind' % kindClassName[:-len('_kindClass')],
                    kind)

    def convertIds(self, sourceDispatcherId, destinationDispatcherId,
                   parentSlot = None):
        slotNames = self.getSlotNames(parentSlot = parentSlot)
        for slotName, value in self.__dict__.items():
            if not slotName in slotNames:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            newValue = slot.getKind().convertValueIds(
                slot, value, sourceDispatcherId,
                destinationDispatcherId)
            if newValue != value:
                slot.setValue(newValue)

    def execute(self, when, command = None, parentSlot = None):
        for slotName in self.getSlotNames(parentSlot = parentSlot):
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            slot.getKind().executeModel(slot, when, command = command)

    def exportToXmlRpc(self, requiredSlotNames = None, parentSlot = None):
        """Convert the current instance to a exportable XML RPC dictionnary.

        Keyword arguments:
        ==================

        *requiredSlotNames*:
            Sequence of the wanted slot name to export. If defined, only the
            slot with name in the sequence will be exported. Default = None.

        *parentSlot*:
            This is the slot where the object is (if applicable). Default =
            None.
        
        Return the dictionnary of the class designed to be send via XML RPC.

        Exceptions:
        ===========
        
        *AssertionError*:
            - A required slot name is a private slot name.
           
            - A element of the required slots names sequence is not a string.
        
        """
        
        result = {
                '__thingCategory__': self.thingCategory,
                '__thingName__': self.getThingName(),
                }
        noneSlotNames = []
        for slotName in self.getExportSlotNames(parentSlot = parentSlot):
            if requiredSlotNames and not slotName in requiredSlotNames:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            kind = slot.getKind()
            if not kind.isExportable():
                continue
            # Key must be a string for xmlrpclib.
            # A slotName must always be a valid Python Indentifier, so no
            # encoding is needed
            # slotName = tools.utf8(slotName)
            assert type(slotName) == types.StringType
            assert not slotName in [
                '__noneSlotNames__', '__thingCategory__', '__thingName__']
            if not slot.hasLocalValue():
                continue
            value = slot.getLocalValue()
            exportedValue = kind.exportValueToXmlRpc(slot, value)
            if exportedValue is None:
                noneSlotNames.append(slotName)
            else:
                result[slotName] = exportedValue
        if noneSlotNames:
            result['__noneSlotNames__'] = noneSlotNames
        return result

    def getC3ClassLinearization(self):
        import tools_new as commonTools
        return commonTools.getC3ClassLinearization(self.__class__)

    def getExportSlotNames(self, parentSlot = None):
        """Return the slot names sequence."""
        
        return self.getSlotNames(parentSlot = parentSlot)

    def getImportSlotNames(self, parentSlot = None):
        return self.getSlotNames(parentSlot = parentSlot)

    def getModifySlotNames(self, parentSlot = None):
        return self.getSlotNames(parentSlot = parentSlot)

    def getOrderedLayoutSlotNames(self, parentSlot = None):
        return ['thingCategory', 'thingName', 'thingPublicCategory',
                'thingPublicName']

    def getPython22ClassLinearization(self):
        import tools_new as commonTools
        baseClasses = []
        commonTools.buildPython22ClassLinearization(
            self.__class__, baseClasses)
        return baseClasses

    def getSlot(self, attributeName, parentSlot = None):
        """Get the class attribute slot.

        Keyword arguments:
        ==================

        *attributeName*:
            The class attribute name.
        
        *parentSlot*:
            This is the slot where the object is (if applicable).
            
        """
        
        import slots
        self.initClass()
        if parentSlot is None:
            container = self
        else:
            container = None
        slot = slots.Attribute(
            attributeName, container = container, parent = parentSlot)
        return slot

    def getSlotByPath(self, path, parentSlot = None):
        if path and path.startswith('self'):
            path = path[len('self'):]
        if not path:
            return parentSlot
        if path[0] != '.':
            raise faults.NonExistentSlotPath(path)
        path = path[1:]
        foundSlotName = None
        for slotName in self.getSlotNames(parentSlot = parentSlot):
            if path.startswith(slotName) and (
                foundSlotName is None or len(slotName) > len(foundSlotName)):
                foundSlotName = slotName
        if foundSlotName is None:
            raise faults.NonExistentSlotPath(path)
        slot = self.getSlot(foundSlotName, parentSlot = parentSlot)
        return slot.getKind().getModelSlotByPath(
            slot, path[len(foundSlotName):])

    def getSlotNames(self, parentSlot = None):
        """Return the slot names sequence of the current object instance.

        Keyword argument:
        =================

        *parentSlot*:
            This is the slot where the object is (if applicable). Default =
            None.

        Return the slot names sequence.
        
        """
        
        self.initClass()
        baseClasses = self.getC3ClassLinearization()
        slotNames = [slotName
                     for slotName in self.__dict__.keys()
                     if slotName.endswith('_kind')]
        for baseClass in baseClasses:
            for slotName in baseClass.__dict__.keys():
                if slotName.endswith('_kind') and slotName not in slotNames:
                    slotNames.append(slotName)
        slotNames = [slotName[:-5]
                     for slotName in slotNames
                     if getattr(self, slotName) is not None \
                        or hasattr(self, slotName + 'Getter') \
                        and getattr(self, slotName + 'Getter') is not None]
        slotNames = [slotName
                     for slotName in slotNames
                     if not '_' in slotName \
                        or not slotName.split('_', 1)[0] in slotNames]
        return slotNames

    def getSlotObject(self, parentSlot = None):
        if parentSlot is None:
            if self.thingCategory == 'object':
                return self
            else:
                return None
        else:
            return parentSlot.getObject()

    def getThingName(cls):
        if type(cls) == types.InstanceType:
            cls = cls.__class__
        if cls.thingName:
            thingName = cls.thingName
        else:
            thingName = cls.__name__
        return thingName

    def getThingPublicName(cls):
        if type(cls) == types.InstanceType:
            cls = cls.__class__
        if cls.thingPublicName:
            name = cls.thingPublicName
        else:
            name = cls.getThingName.im_func(cls)
        return name

    def hasSlotName(self, attributeName, parentSlot = None):
        return attributeName in self.getSlotNames(parentSlot = parentSlot)

    def hasSlotPath(self, path, parentSlot = None):
        try:
            self.getSlotByPath(path, parentSlot = parentSlot)
        except faults.NonExistentSlotPath:
            return 0
        else:
            return 1

    def importFromXmlRpc(self, dataImport, parentSlot = None):
        """Import object class instance from a XML RPC dictionnary.
        
        This method mutates the current object instance in the corresponding
        class and instance defined in the *dataImport* dictionnary.
        
        Keyword attributes:
        ===================

        *dataImport*:
            The XML RPC dictionnary.

        *parentSlot*:
            This is the slot where the object is (if applicable). Default =
            None.
            
        """
        
        if dataImport.has_key('__noneSlotNames__'):
            noneSlotNames = dataImport['__noneSlotNames__']
        else:
            noneSlotNames = []
        for slotName in self.getImportSlotNames(parentSlot = parentSlot):
            if slotName in [
                '__noneSlotNames__', '__thingCategory__', '__thingName__']:
                continue
            # A slotName must always be a valid Python Indentifier, so no
            # encoding is needed to for the dataImport keys.
            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))

    def initClass(self):
        # Should be converted to a class method, when porting Glasnost to
        # Python 2.2.

        if not self.__class__.__dict__.has_key('classIsInited') \
           or not self.__class__.__dict__['classIsInited']:
            # classIsInited should be set prior to calling buildKinds()
            self.__class__.__dict__['classIsInited'] = 1
            self.buildKinds()

    def isEmpty(self, parentSlot = None):
        return not self.__dict__

    def newKind(self, parentSlot = None):
        kind = commonTools.newThing('kind', 'Thing')
        kind.valueThingCategory = self.thingCategory
        kind.valueThingName = self.getThingName()
        return kind

    def releaseNonCore(self, parentSlot = None):
        pass

    def removeIds(self, rolesToKeep, parentSlot = None):
        slotNames = self.getSlotNames(parentSlot = parentSlot)
        for slotName, value in self.__dict__.items():
            if not slotName in slotNames:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            newValue = slot.getKind().removeValueIds(slot, value, rolesToKeep)
            if newValue != value:
                slot.setValue(newValue)

    def repair(self, toVersion, parentSlot = None):
        changed = 0
        slotNames = self.getSlotNames(parentSlot = parentSlot)
        for slotName, value in self.__dict__.items():
            if not slotName in slotNames:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            newValue = slot.getKind().repairValue(slot, value, toVersion)
            if newValue is not None:
                changed = 1
                slot.setValue(newValue)
        return changed

    def saveNonCore(self, objectDirectoryPath, parentSlot = None):
        pass

    def upgrade(self, toVersion, parentSlot = None):
        changed = 0

        # Call object upgrade_xxx methods.
        classes = commonTools.getC3ClassLinearization(self.__class__)
        upgradeMethodNames = []
        for class_ in classes:
            for attributeName in class_.__dict__.keys():
                if attributeName.startswith('upgrade_'):
                    if not attributeName in upgradeMethodNames:
                        upgradeMethodNames.append(attributeName)
        upgradeMethodNames.sort()
        for upgradeMethodName in upgradeMethodNames:
            if upgradeMethodName[len('upgrade_'):] <= toVersion:
                continue
            changed = getattr(self, upgradeMethodName)(
                    parentSlot = parentSlot) or changed

        # Upgrade each object slot.
        slotNames = self.getSlotNames(parentSlot = parentSlot)
        for slotName, value in self.__dict__.items():
            if not slotName in slotNames:
                continue
            slot = self.getSlot(slotName, parentSlot = parentSlot)
            valueChanged, value = slot.getKind().upgradeModel(
                    slot, value, toVersion)
            if valueChanged:
                changed = 1
                slot.setValue(value)

        return changed

