"""
CacheSetup
~~~~~~~~~~~~~~~~~~~~~~~~~~~

$Id: $
"""

__authors__ = 'Geoff Davis <geoff@geoffdavis.net>'
__docformat__ = 'restructuredtext'

import time
import DateTime
from AccessControl import ClassSecurityInfo, SpecialUsers
from Products.Archetypes import public as atapi
try:
    from Products.CMFCore import permissions
except ImportError:
    from Products.CMFCore import CMFCorePermissions as permissions
from Products.CMFCore import Expression
from Products.CMFCore.utils import getToolByName
from Products.CacheSetup.config import CACHE_TOOL_ID

from Products.PageTemplates.Expressions import getEngine, SecureModuleImporter
from Products.PageTemplates.TALES import CompilerError
from nocatalog import NoCatalog


schema = atapi.BaseContent.schema + \
   atapi.Schema((atapi.StringField(
                    'description',
                    required=0,
                    default='',
                    widget=atapi.TextAreaWidget(
                              label='Description',
                              description='Basic documentation for this macro caching rule',
                              cols=60,
                              rows=5,),
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.StringField(
                    'macroName',
                    required=1,
                    default='',
                    widget=atapi.StringWidget(
                              label='Macro path',
                              description='Path of the macro to cache.  For macros on the file system, use \'file:\' + the path to the template relative to Products + \'/macros/\' + the name of the macro (e.g. file:MyProduct/skins/my_skin/my_macrofile.pt/macros/my_macro).  For macros in the zodb, use the physical path of the template + \'/macros/\' + the name of the macro (e.g. for a portlet, /plone_site_id/my_portlet/macros/portlet)',
                              size=80,
                              maxsize=100,),
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.BooleanField(
                    'enabled',
                    required=0,
                    default=True,
                    widget=atapi.BooleanWidget(
                              label='Enable',
                              description='Enable caching for this macro?',),
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.StringField(
                    'keyComponents',
                    default=('member','skin','language','gzip','catalog_modified'),
                    widget=atapi.MultiSelectionWidget(
                              label='Key Components',
                              description='Items used to construct the key',
                              format='checkbox',),
                    multiValued=1,
                    vocabulary=atapi.DisplayList((('host','Host name'),
                                                  ('member','Current member\'s ID'),
                                                  ('roles','Current member\'s roles'),
                                                  ('permissions','Current member\'s permissions'),
                                                  ('url','Current url'),
                                                  ('context','Context path'),
                                                  ('container','Container path'),
                                                  ('template', 'Template ID'),
                                                  ('skin','Current skin'),
                                                  ('language','Browser\'s preferred language'),
                                                  ('user_language','User\'s preferred language'),
                                                  ('last_modified','Context modification time'),
                                                  ('catalog_modified', 'Time of last catalog change'),
                                                  )),
                    enforce_vocabulary = 1,
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.LinesField(
                    'keyRequestValues',
                    default=[],
                    widget=atapi.LinesWidget(
                              label='Key Request Values',
                              description='Request values used to construct the key'),
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.IntegerField(
                    'keyTimeout',
                    required=0,
                    default=3600,
                    widget=atapi.IntegerWidget(
                              label='Key Timeout',
                              description='Maximum amount of time an key is valid (leave blank for forever)'),
                    write_permission = permissions.ManagePortal,
                    ),
                 atapi.StringField(
                    'keyExpression',
                    required=0,
                    edit_accessor='getKeyExpression',
                    widget=atapi.StringWidget(
                              label='Key Expression',
                              description='A TALES expression that will be appended to the key generated with the above settings when "The expression below" is selected.  Available values: request, context, container, user, template, traverse_subpath',
                              size=80),
                    write_permission = permissions.ManagePortal,
                    ),
                 ))

class MacroCacheRule(NoCatalog, atapi.BaseContent):
    """
    """
    security = ClassSecurityInfo()
    archetype_name = 'Macro Cache Rule'
    portal_type = meta_type = 'MacroCacheRule'
    __implements__ = (atapi.BaseContent.__implements__,)
    schema = schema
    global_allow = 0
    _at_rename_after_creation = True

    actions = (
        {'action':      'string:$object_url/cache_setup_config',
         'category':    'object',
         'id':          'view',
         'name':        'Cache Setup',
         'permissions': (permissions.ManagePortal,),
         'visible':     False},
    )

    aliases = {
        '(Default)':    'cache_setup_config',
        'view' :        'cache_setup_config',
        'edit' :        'base_edit'
    }

    def _addComponent(self, key, component):
        return key + '|' + str(component)

    def _validate_expression(self, expression):
        try:
            getEngine().compile(expression)
        except CompilerError, e:
            return 'Bad expression:', str(e)
        except:
            raise

    def getKeyExpression(self):
        expression = self.getField('keyExpression').get(self)
        if expression:
            return expression.text
        
    def setKeyExpression(self, expression):
        if expression is None:
            expression = ''
        return self.getField('keyExpression').set(self, Expression.Expression(expression))

    def validate_keyExpression(self, expression):
        return self._validate_expression(expression)

    def getKeyExpressionValue(self, expr_context):
        expression = self.getField('keyExpression').get(self)
        if expression:
            return expression(expr_context)

    def _getExpressionContext(self, request, context, container, user, template, traverse_subpath):
        """Construct an expression context for TALES expressions used in cache rules and header sets"""
        if time is None:
            time = DateTime.DateTime()
        data = {'rule'     : self,
                'request'  : request,
                'context'  : context,
                'container': container,
                'user'     : user,
                'template' : template,
                'traverse_subpath' : traverse_subpath,
                'modules'  : SecureModuleImporter,
                'nothing'  : None
               }
        return getEngine().getContext(data)

    security.declarePublic('getKey')
    def getKey(self, request, context, container, user, template, traverse_subpath):
        portal = getToolByName(self, 'portal_url').getPortalObject()
        pcs = getattr(portal, CACHE_TOOL_ID)
        key = '/'.join(portal.getPhysicalPath())
        values = self.getKeyComponents()
        if 'host' in values:
            self._addComponent(key, getToolByName(self, 'portal_url')())
        if 'member' in values:
            if user == SpecialUsers.nobody:
                username = ''
            else:
                username = user.getUserName()
            key = self._addComponent(key, username)
        member = None
        if 'roles' in values or 'permissions' in values:
            member = portal.portal_membership.wrapUser(user)
            roles = list(member.getRolesInContext(context))
            roles.sort()
            key = self._addComponent(key, ';'.join(roles))
            if 'permissions' in values:
                key = self._addComponent(key, pcs.getPermissionCount())
        if 'url' in values:
            key = self._addComponent(key, request.get('ACTUAL_URL', ''))
        if 'context' in values:
            key = self._addComponent(key, context.getPhysicalPath())
        if 'container' in values:
            key = self._addComponent(key, container.getPhysicalPath())
        if 'template' in values:
            key = self._addComponent(key, template.getId())
        if 'skin' in values:
            try:
                skin_name = self.getCurrentSkinName()
            except AttributeError:
                stool = getToolByName(self, 'portal_skins')
                skin_name = self.getSkinNameFromRequest(request)
                
                if skin_name is None:
                    # Use default skin
                    skin_name = stool.getDefaultSkin()

            key = self._addComponent(key, skin_name)
        if 'language' in values:
            key = self._addComponent(key, request.get('HTTP_ACCEPT_LANGUAGE', ''))
        if 'user_language' in values:
            ltool = getToolByName(self, 'portal_languages', None)
            if ltool is None:
                ptool = getToolByName(self, 'portal_properties')
                lang = ptool.site_properties.default_language
            else:
                lang = ltool.getPreferredLanguage()
            key = self._addComponent(key, lang)
        if 'last_modified' in values:
            key = self._addComponent(key, context.modified().timeTime())
        if 'catalog_modified' in values:
            key = self._addComponent(key, pcs.getCatalogCount())
        if self.getKeyExpression():
            expr_context = self._getExpressionContext(request, context, container, user, template, traverse_subpath)
            key = self._addComponent(key, self.getKeyExpressionValue(expr_context))

        marker = []
        req_values = self.getKeyRequestValues()
        if req_values:
            for rv in req_values:
                v = request.get(rv, marker)
                if v is not marker:
                    key = self._addComponent(key, v)
                else:
                    key = self._addComponent(key, '')
                    
        timeout = self.getKeyTimeout()
        if timeout:
            key = self._addComponent(key, int(time.time()/timeout))

        return key

atapi.registerType(MacroCacheRule)
