import string
from logging import DEBUG

from Products.Archetypes.Field import *
from Products.Archetypes.Widget import *
from Products.Archetypes.Schema import Schema
from Products.Archetypes.Schema import MetadataSchema
from Products.Archetypes.interfaces.metadata import IExtensibleMetadata
from Products.Archetypes.utils import DisplayList, shasattr
from Products.Archetypes.debug import log
from Products.Archetypes.debug import log_exc
from Products.Archetypes.debug import deprecated
from Products.Archetypes import config
import Persistence
from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
from AccessControl import Unauthorized
from DateTime.DateTime import DateTime
from Globals import InitializeClass, DTMLFile
from Products.CMFCore import permissions
from Products.CMFCore.utils  import getToolByName
from Products.CMFDefault.utils import _dtmldir
from ComputedAttribute import ComputedAttribute

_marker=[]

try:
    True
except NameError:
    True=1
    False=0

# http://www.zope.org/Collectors/CMF/325
# http://www.zope.org/Collectors/CMF/476
_zone = DateTime().timezone()

FLOOR_DATE = DateTime(1000, 0) # always effective
CEILING_DATE = DateTime(2500, 0) # never expires

## MIXIN
class ExtensibleMetadata(Persistence.Persistent):
    """a replacement for CMFDefault.DublinCore.DefaultDublinCoreImpl
    """


    # XXX This is not completely true. We need to review this later
    # and make sure it is true.
    # Just so you know, the problem here is that Title
    # is on BaseObject.schema, so it does implement IExtensibleMetadata
    # as long as both are used together.
    __implements__ = IExtensibleMetadata

    security = ClassSecurityInfo()

    schema = type = MetadataSchema(
        (
        StringField(
            'allowDiscussion',
            accessor="isDiscussable",
            mutator="allowDiscussion",
            edit_accessor="editIsDiscussable",
            default=None,
            enforceVocabulary=1,
            vocabulary=DisplayList((
        ('None', 'Default', 'label_discussion_default'),
        ('1',    'Enabled', 'label_discussion_enabled'),
        ('0',    'Disabled', 'label_discussion_disabled'),
        )),
            widget=SelectionWidget(
                label="Allow Discussion on this item",
                label_msgid="label_allow_discussion",
                description_msgid="help_allow_discussion",
                i18n_domain="plone"),
        ),
        LinesField(
            'subject',
            multiValued=1,
            accessor="Subject",
            searchable=True,
            widget=KeywordWidget(
                label="Keywords",
                label_msgid="label_keywords",
                description_msgid="help_keyword",
                i18n_domain="plone"),
        ),
        TextField(
            'description',
            default='',
            searchable=1,
            accessor="Description",
            widget=TextAreaWidget(
                label='Description',
                description="A short summary of the content",
                label_msgid="label_description",
                description_msgid="help_description",
                i18n_domain="plone"),
        ),
        LinesField(
            'contributors',
            accessor="Contributors",
            widget=LinesWidget(
                label='Contributors',
                label_msgid="label_contributors",
                description="The names of people that have contributed to this "
                            "item. Each contributor should be on a separate "
                            "line.",
                description_msgid="help_contributors",
                i18n_domain="plone"),
        ),
        LinesField(
            'creators',
            accessor="Creators",
            widget=LinesWidget(
                label='Creators',
                label_msgid="label_creators",
                rows = 3,
                description="Persons responsible for creating the content of  "
                            "this item. Please enter a list of user names, one "
                            "per line. The principal creator should come first.",
                description_msgid="help_creators",
                i18n_domain="plone"),
        ),
        DateTimeField(
            'effectiveDate',
            mutator='setEffectiveDate',
            languageIndependent = True,
            #default=FLOOR_DATE,
            widget=CalendarWidget(
                label="Effective Date",
                description=("Date when the content should become available "
                             "on the public site"),
                label_msgid="label_effective_date",
                description_msgid="help_effective_date",
                i18n_domain="plone"),
        ),
        DateTimeField(
            'expirationDate',
            mutator='setExpirationDate',
            languageIndependent = True,
            #default=CEILING_DATE,
            widget=CalendarWidget(
                label="Expiration Date",
                description=("Date when the content should no longer be "
                             "visible on the public site"),
                label_msgid="label_expiration_date",
                description_msgid="help_expiration_date",
                i18n_domain="plone"),
        ),

        StringField(
            'language',
            accessor="Language",
            # Special default here, cite limi: "If you don't add any language to
            # an item, the template that renders the Plone page will fall back
            # to the declared portal-wide language setting. This is the
            # behaviour we want, and thus setting language explicitly is not
            # necessary. (I fixed this behaviour in Plone 2.0.5, IIRC)"
            # So I keep it backward compatible if needed and adding a
            # configureable behaviour for 1.3.x. (Jensens)
            default = config.LANGUAGE_DEFAULT,
            default_method ='defaultLanguage',
            vocabulary='languages',
            widget=SelectionWidget(
                label='Language',
                label_msgid="label_language",
                description_msgid="help_language",
                i18n_domain="plone"),
        ),
        TextField(
            'rights',
            accessor="Rights",
            widget=TextAreaWidget(
                label='Copyrights',
                description="The copyrights on this item.",
                label_msgid="label_copyrights",
                description_msgid="help_copyrights",
                i18n_domain="plone")),
        )) + Schema((
        # XXX change this to MetadataSchema in AT 1.4
        # Currently we want to stay backward compatible without migration
        # between beta versions so creation and modification date are using the
        # standard schema which leads to AttributeStorage
        DateTimeField(
            'creation_date',
            accessor='created',
            mutator='setCreationDate',
            default_method=DateTime,
            languageIndependent=True,
            isMetadata=True,
            schemata='metadata',
            generateMode='mVc',
            widget=CalendarWidget(
                label="Creation Date",
                description=("Date this object was created"),
                label_msgid="label_creation_date",
                description_msgid="help_creation_date",
                i18n_domain="plone",
                visible={'edit':'invisible', 'view':'invisible'}),
        ),
        DateTimeField(
            'modification_date',
            accessor='modified',
            mutator = 'setModificationDate',
            default_method=DateTime,
            languageIndependent=True,
            isMetadata=True,
            schemata='metadata',
            generateMode='mVc',
            widget=CalendarWidget(
                label="Modification Date",
                description=("Date this content was modified last"),
                label_msgid="label_modification_date",
                description_msgid="help_modification_date",
                i18n_domain="plone",
                visible={'edit':'invisible', 'view':'invisible'}),
        ),
        ))

    def __init__(self):
        pass
        #self.setCreationDate(None)
        #self.setModificationDate(None)

    security.declarePrivate('defaultLanguage')
    def defaultLanguage(self):
        """Retrieve the default language"""
        # XXX This method is kept around for backward compatibility only
        return config.LANGUAGE_DEFAULT

    security.declareProtected(permissions.View, 'isDiscussable')
    def isDiscussable(self, encoding=None):
        dtool = getToolByName(self, 'portal_discussion')
        return dtool.isDiscussionAllowedFor(self)

    security.declareProtected(permissions.View, 'editIsDiscussable')
    def editIsDiscussable(self, encoding=None):
        # XXX this method highly depends on the current implementation
        # it's a quick hacky fix
        result = getattr(aq_base(self), 'allow_discussion', None)
        if result is not None:
            try:
                # deal with booleans
                result = int(result)
            except (TypeError, ValueError):
                pass
        return str(result)

    security.declareProtected(permissions.ModifyPortalContent,
                              'allowDiscussion')
    def allowDiscussion(self, allowDiscussion=None, **kw):
        if allowDiscussion is not None:
            try:
                allowDiscussion = int(allowDiscussion)
            except (TypeError, ValueError):
                allowDiscussion = allowDiscussion.lower().strip()
                d = {'on' : 1, 'off': 0, 'none':None, '':None, 'None':None}
                allowDiscussion = d.get(allowDiscussion, None)
        dtool = getToolByName(self, 'portal_discussion')
        try:
            dtool.overrideDiscussionFor(self, allowDiscussion)
        except (KeyError, AttributeError), err:
            if allowDiscussion is None:
                # work around a bug in CMFDefault.DiscussionTool. It's using
                # an unsafe hasattr() instead of a more secure getattr() on an
                # unwrapped object
                msg = "Unable to set discussion on %s to None. Already " \
                      "deleted allow_discussion attribute? Message: %s" % (
                       self.getPhysicalPath(), str(err))
                log(msg, level=DEBUG)
            else:
                raise
        except ("Unauthorized", Unauthorized):
            # Catch Unauthorized exception that could be raised by the
            # discussion tool when the authenticated users hasn't
            # ModifyPortalContent permissions. IMO this behavior is safe because
            # this method is protected, too.
            # Explanation:
            # A user might have CreatePortalContent but not ModifyPortalContent
            # so allowDiscussion could raise a Unauthorized error although it's
            # called from trusted code. That is VERY bad inside setDefault()!
            #
            # XXX: Should we have our own implementation of
            #      overrideDiscussionFor?
            log('Catched Unauthorized on discussiontool.' \
                'overrideDiscussionFor(%s)' % self.absolute_url(1),
                level=DEBUG)

    # Vocabulary methods ######################################################

    security.declareProtected(permissions.View, 'languages')
    def languages(self):
        """Vocabulary method for the language field
        """
        # XXX document me
        # use a list of languages from PLT?
        available_langs = getattr(self, 'availableLanguages', None)
        if available_langs is None:
            return DisplayList(
                (('en','English'), ('fr','French'), ('es','Spanish'),
                 ('pt','Portuguese'), ('ru','Russian')))
        if callable(available_langs):
            available_langs = available_langs()
        return DisplayList(available_langs)


    #  DublinCore interface query methods #####################################

    security.declareProtected(permissions.View, 'CreationDate')
    def CreationDate(self, zone=None):
        """ Dublin Core element - date resource created.
        """
        if zone is None:
            zone = _zone
        creation = self.getField('creation_date').get(self)
        # XXX return unknown if never set properly
        return creation is None and 'Unknown' or creation.toZone(zone).ISO()

    security.declareProtected( permissions.View, 'EffectiveDate')
    def EffectiveDate(self, zone=None):
        """ Dublin Core element - date resource becomes effective.
        """
        if zone is None:
            zone = _zone
        effective = self.getField('effectiveDate').get(self)
        return effective is None and 'None' or effective.toZone(zone).ISO()

    def _effective_date(self):
        """Computed attribute accessor
        """
        return self.getField('effectiveDate').get(self)

    security.declareProtected(permissions.View, 'effective_date')
    effective_date = ComputedAttribute(_effective_date, 1)


    security.declareProtected( permissions.View, 'ExpirationDate')
    def ExpirationDate(self, zone=None):
        """Dublin Core element - date resource expires.
        """
        if zone is None:
            zone = _zone
        expires = self.getField('expirationDate').get(self)
        return expires is None and 'None' or expires.toZone(zone).ISO()

    def _expiration_date(self):
        """Computed attribute accessor
        """
        return self.getField('expirationDate').get(self)

    security.declareProtected(permissions.View, 'expiration_date')
    expiration_date = ComputedAttribute(_expiration_date, 1)

    security.declareProtected(permissions.View, 'Date')
    def Date(self, zone=None):
        """
        Dublin Core element - default date
        """
        # Return effective_date if specifically set, modification date
        # otherwise
        if zone is None:
            zone = _zone
        effective = self.getField('effectiveDate').get(self)
        if effective is None:
            effective = self.modified()
        return (effective is None and DateTime().toZone(zone) or
                effective.toZone(zone).ISO())

    security.declareProtected(permissions.View, 'Format')
    def Format(self):
        """cmf/backward compat
        Dublin Core element - resource format
        """
        # FIXME: get content type from marshaller
        return self.getContentType()

    security.declareProtected(permissions.ModifyPortalContent,
                              'setFormat')
    def setFormat(self, value):
        """cmf/backward compat: ignore setFormat"""
        self.setContentType(value)

    def Identifer(self):
        """ dublin core getId method"""
        return self.getId()

    #  DublinCore utility methods #############################################

    security.declareProtected(permissions.View, 'contentEffective')
    def contentEffective(self, date):
        """Is the date within the resource's effective range?
        """
        effective = self.getField('effectiveDate').get(self)
        expires = self.getField('expirationDate').get(self)
        pastEffective = ( effective is None or effective <= date )
        beforeExpiration = ( expires is None or expires >= date )
        return pastEffective and beforeExpiration

    security.declareProtected(permissions.View, 'contentExpired')
    def contentExpired(self, date=None):
        """ Is the date after resource's expiration """
        if not date:
            # XXX we need some unittesting for this
            date = DateTime()
        expires = self.getField('expirationDate').get(self)
        if not expires:
            expires = CEILING_DATE
        return expires <= date

    #  CatalogableDublinCore methods ##########################################

    security.declareProtected(permissions.View, 'created')
    def created(self):
        """Dublin Core element - date resource created,
        returned as DateTime.
        """
        # allow for non-existent creation_date, existed always
        created = self.getField('creation_date').get(self)
        return created is None and FLOOR_DATE or created

    security.declareProtected(permissions.View, 'modified')
    def modified(self):
        """Dublin Core element - date resource last modified,
        returned as DateTime.
        """
        modified = self.getField('modification_date').get(self)
        # XXX may return None
        return modified

    security.declareProtected(permissions.View, 'effective')
    def effective(self):
        """Dublin Core element - date resource becomes effective,
        returned as DateTime.
        """
        effective = self.getField('effectiveDate').get(self)
        return effective is None and FLOOR_DATE or effective

    security.declareProtected(permissions.View, 'expires')
    def expires(self):
        """Dublin Core element - date resource expires,
        returned as DateTime.
        """
        expires = self.getField('expirationDate').get(self)
        return expires is None and CEILING_DATE or expires

    ## code below come from CMFDefault.DublinCore.DefaultDublinCoreImpl #######

    ###########################################################################
    #
    # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved
    #
    # This software is subject to the provisions of the Zope Public License,
    # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
    # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
    # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
    # FOR A PARTICULAR PURPOSE
    #
    ###########################################################################

    #
    #  Set-modification-date-related methods.
    #  In DefaultDublinCoreImpl for lack of a better place.
    #

    security.declareProtected(permissions.ModifyPortalContent,
                              'notifyModified')
    def notifyModified(self):
        """
        Take appropriate action after the resource has been modified.
        For now, change the modification_date.
        """
        # XXX This could also store the id of the user doing modifications.
        self.setModificationDate(DateTime())
        if shasattr(self, 'http__refreshEtag'):
            self.http__refreshEtag()

    security.declareProtected(permissions.ManagePortal,
                              'setModificationDate')
    def setModificationDate(self, modification_date=None):
        """Set the date when the resource was last modified.
        When called without an argument, sets the date to now.
        """
        if modification_date is None:
            modified = DateTime()
        else:
            modified = self._datify(modification_date)
        self.getField('modification_date').set(self, modified)

    security.declareProtected(permissions.ManagePortal,
                              'setCreationDate')
    def setCreationDate(self, creation_date=None):
        """Set the date when the resource was created.
        When called without an argument, sets the date to now.
        """
        if creation_date is None:
            created = DateTime()
        else:
            created = self._datify(creation_date)
        self.getField('creation_date').set(self, created)

    security.declarePrivate( '_datify' )
    def _datify(self, date):
        """Try to convert something into a DateTime instance or None
        """
        # stupid web
        if date == 'None':
            date = None
        if not isinstance(date, DateTime):
            if date is not None:
                date = DateTime(date)
        return date

    #
    #  DublinCore interface query methods
    #
    security.declareProtected(permissions.View, 'Publisher')
    def Publisher(self):
        """Dublin Core element - resource publisher
        """
        # XXX: fixme using 'portal_metadata'
        return 'No publisher'

    security.declareProtected(permissions.View, 'ModificationDate')
    def ModificationDate(self, zone=None):
        """ Dublin Core element - date resource last modified.
        """
        if zone is None:
            zone = _zone
        modified = self.modified()
        return (modified is None and DateTime().toZone(zone)
                or modified.toZone(zone).ISO())

    security.declareProtected(permissions.View, 'Type')
    def Type(self):
        """Dublin Core element - Object type"""
        if hasattr(aq_base(self), 'getTypeInfo'):
            ti = self.getTypeInfo()
            if ti is not None:
                return ti.Title()
        return self.meta_type

    security.declareProtected(permissions.View, 'Identifier')
    def Identifier(self):
        """Dublin Core element - Object ID"""
        # XXX: fixme using 'portal_metadata' (we need to prepend the
        #      right prefix to self.getPhysicalPath().
        return self.absolute_url()

    security.declareProtected(permissions.View, 'listContributors')
    def listContributors(self):
        """Dublin Core element - Contributors"""
        return self.Contributors()

    security.declareProtected(permissions.ModifyPortalContent,
                              'addCreator')
    def addCreator(self, creator=None):
        """ Add creator to Dublin Core creators.
        """
        if creator is None:
            mtool = getToolByName(self, 'portal_membership')
            creator = mtool.getAuthenticatedMember().getId()

        # call self.listCreators() to make sure self.creators exists
        curr_creators = self.listCreators()
        if creator and not creator in curr_creators:
            self.setCreators(curr_creators + (creator, ))

    security.declareProtected(permissions.View, 'listCreators')
    def listCreators(self):
        """ List Dublin Core Creator elements - resource authors.
        """
        creators = self.Schema()['creators']
        if not creators.get(self):
            # for content created with CMF versions before 1.5
            owner_id = self.getOwnerTuple()[1]
            if owner_id:
                creators.set(self, (owner_id,))
            else:
                creators.set(self, ())

        return creators.get(self)

    security.declareProtected(permissions.View, 'Creator')
    def Creator(self):
        """ Dublin Core Creator element - resource author.
        """
        creators = self.listCreators()
        return creators and creators[0] or ''

    #
    #  DublinCore utility methods
    #

    # Deliberately *not* protected by a security declaration
    # See https://dev.plone.org/archetypes/ticket/712
    def content_type(self):
        """ WebDAV needs this to do the Right Thing (TM).
        """
        return self.Format()
    #
    #  CatalogableDublinCore methods
    #

    security.declareProtected(permissions.View, 'getMetadataHeaders')
    def getMetadataHeaders(self):
        """ Return RFC-822-style headers.
        """
        hdrlist = []
        hdrlist.append( ( 'Title', self.Title() ) )
        hdrlist.append( ( 'Subject', string.join( self.Subject(), ', ' ) ) )
        hdrlist.append( ( 'Publisher', self.Publisher() ) )
        hdrlist.append( ( 'Description', self.Description() ) )
        hdrlist.append( ( 'Contributors', string.join(
            self.Contributors(), '; ' ) ) )
        hdrlist.append( ( 'Creators', string.join(
            self.Creators(), '; ' ) ) )
        hdrlist.append( ( 'Effective_date', self.EffectiveDate() ) )
        hdrlist.append( ( 'Expiration_date', self.ExpirationDate() ) )
        hdrlist.append( ( 'Type', self.Type() ) )
        hdrlist.append( ( 'Format', self.Format() ) )
        hdrlist.append( ( 'Language', self.Language() ) )
        hdrlist.append( ( 'Rights', self.Rights() ) )
        return hdrlist

    #
    #  Management tab methods
    #

    security.declarePrivate( '_editMetadata' )
    def _editMetadata(self
                      , title=_marker
                      , subject=_marker
                      , description=_marker
                      , contributors=_marker
                      , effective_date=_marker
                      , expiration_date=_marker
                      , format=_marker
                      , language=_marker
                      , rights=_marker
                      ):
        """ Update the editable metadata for this resource.
        """
        if title is not _marker:
            self.setTitle( title )
        if subject is not _marker:
            self.setSubject( subject )
        if description is not _marker:
            self.setDescription( description )
        if contributors is not _marker:
            self.setContributors( contributors )
        if effective_date is not _marker:
            self.setEffectiveDate( effective_date )
        if expiration_date is not _marker:
            self.setExpirationDate( expiration_date )
        if format is not _marker:
            self.setFormat( format )
        if language is not _marker:
            self.setLanguage( language )
        if rights is not _marker:
            self.setRights( rights )

    security.declareProtected(permissions.ModifyPortalContent,
                              'manage_metadata' )
    manage_metadata = DTMLFile('zmi_metadata', _dtmldir)

    security.declareProtected(permissions.ModifyPortalContent,
                               'manage_editMetadata')
    def manage_editMetadata( self
                           , title
                           , subject
                           , description
                           , contributors
                           , effective_date
                           , expiration_date
                           , format
                           , language
                           , rights
                           , REQUEST
                           ):
        """ Update metadata from the ZMI.
        """
        self._editMetadata( title, subject, description, contributors
                          , effective_date, expiration_date
                          , format, language, rights
                          )
        REQUEST[ 'RESPONSE' ].redirect( self.absolute_url()
                                + '/manage_metadata'
                                + '?manage_tabs_message=Metadata+updated.' )

    security.declareProtected(permissions.ModifyPortalContent,
                              'editMetadata')
    def editMetadata(self
                     , title=''
                     , subject=()
                     , description=''
                     , contributors=()
                     , effective_date=None
                     , expiration_date=None
                     , format='text/html'
                     , language='en-US'
                     , rights=''
                     ):
        """
        used to be:  editMetadata = WorkflowAction(_editMetadata)
        Need to add check for webDAV locked resource for TTW methods.
        """
        self.failIfLocked()
        self._editMetadata(title=title
                           , subject=subject
                           , description=description
                           , contributors=contributors
                           , effective_date=effective_date
                           , expiration_date=expiration_date
                           , format=format
                           , language=language
                           , rights=rights
                           )
        self.reindexObject()

InitializeClass(ExtensibleMetadata)

ExtensibleMetadataSchema = ExtensibleMetadata.schema

__all__ = ('ExtensibleMetadata', 'ExtensibleMetadataSchema', )
