##############################################################################
#
# 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
#
##########################################################################

import os
from Acquisition import aq_base
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
# BBB CMF < 1.5
try:
    from Products.CMFCore.permissions import View, ManagePortal
except ImportError:
    from Products.CMFCore.CMFCorePermissions import View, ManagePortal
from Products.CMFCore.utils import getToolByName, expandpath
from Products.CMFCore.FSMetadata import FSMetadata, CMFConfigParser
from FormAction import FormActionType, FormAction, FormActionContainer
from FormValidator import FormValidator, FormValidatorContainer
from globalVars import ANY_CONTEXT, ANY_BUTTON
from utils import log

class ControllerBase:
    """Common functions for objects controlled by portal_form_controller"""

    security = ClassSecurityInfo()
    security.declareObjectProtected(View)

    security.declareProtected(ManagePortal, 'manage_formActionsForm')
    manage_formActionsForm = PageTemplateFile('www/manage_formActionsForm', globals())
    manage_formActionsForm.__name__ = 'manage_formActionsForm'

    security.declareProtected(ManagePortal, 'manage_formValidatorsForm')
    manage_formValidatorsForm = PageTemplateFile('www/manage_formValidatorsForm', globals())
    manage_formValidatorsForm.__name__ = 'manage_formValidatorsForm'

    def _updateActions(self, container, old_id, new_id, move):
        """Copy action overrides stored in portal_form_controller from one 
        object id to another"""
        actions = container.getFiltered(object_id=old_id)
        for a in actions:
            # if not container.match(new_id, a.getStatus(), a.getContextType(), a.getButton()):
            container.set(FormAction(new_id, a.getStatus(), a.getContextType(),
                           a.getButton(), a.getActionType(), a.getActionArg()))
        if move:
            for a in actions:
                container.delete(a.getKey())
                
    def _updateValidators(self, container, old_id, new_id, move):
        """Copy validator overrides stored in portal_form_controller from one 
        object id to another"""
        validators = container.getFiltered(object_id=old_id)
        for v in validators:
            # if not container.match(new_id, v.getContextType(), v.getButton()):
            container.set(FormValidator(new_id, v.getContextType(), v.getButton(), v.getValidators()))
        if move:
            for v in validators:
                container.delete(v.getKey())
        
    def _base_notifyOfCopyTo(self, container, op=0):
        self._old_id = self.getId()
        if op==0:  # copy
            self._cloned_object_path = self.getPhysicalPath()

    def _fixup_old_ids(self, old_id):
        fc = getToolByName(self, 'portal_form_controller')
        id = self.getId()
        if old_id != id:
            if hasattr(aq_base(self), 'actions'):
                self._updateActions(self.actions, old_id, id, move=1) # swap the ids for the default actions
                self._updateActions(fc.actions, old_id, id, move=0) # copy the overrides
            if hasattr(aq_base(self), 'validators'):
                self._updateValidators(self.validators, old_id, id, move=1) # swap the ids for the default validators
                self._updateValidators(fc.validators, old_id, id, move=0) # copy the overrides

    def _base_manage_afterAdd(self, object, container):
        old_id = getattr(self, '_old_id', None)
        if old_id:
            self._fixup_old_ids(old_id)
            delattr(self, '_old_id')

    def _base_manage_afterClone(self, object):
        # clean up the old object
        cloned_object_path = getattr(self, '_cloned_object_path')
        cloned_object = self.getPhysicalRoot().unrestrictedTraverse(cloned_object_path)
        delattr(cloned_object, '_old_id')
        delattr(cloned_object, '_cloned_object_path')
        # clean up the new object
        delattr(self, '_cloned_object_path')

    security.declareProtected(ManagePortal, 'listActionTypes')
    def listActionTypes(self):
        """Return a list of available action types."""
        return getToolByName(self, 'portal_form_controller').listActionTypes()

    security.declareProtected(ManagePortal, 'listFormValidators')
    def listFormValidators(self, override, **kwargs):
        """Return a list of existing validators.  Validators can be filtered by
           specifying required attributes via kwargs"""
        controller = getToolByName(self, 'portal_form_controller')
        if override:
            return controller.validators.getFiltered(**kwargs)
        else:
            return self.validators.getFiltered(**kwargs)


    security.declareProtected(ManagePortal, 'listFormActions')
    def listFormActions(self, override, **kwargs):
        """Return a list of existing actions.  Actions can be filtered by
           specifying required attributes via kwargs"""
        controller = getToolByName(self, 'portal_form_controller')
        if override:
            return controller.actions.getFiltered(**kwargs)
        else:
            return self.actions.getFiltered(**kwargs)


    security.declareProtected(ManagePortal, 'listContextTypes')
    def listContextTypes(self):
        """Return list of possible types for template context objects"""
        return getToolByName(self, 'portal_form_controller').listContextTypes()


    security.declareProtected(ManagePortal, 'manage_editFormValidators')
    def manage_editFormValidators(self, REQUEST):
        """Process form validator edit form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.validators
        else:
            container = self.validators
        controller._editFormValidators(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')


    security.declareProtected(ManagePortal, 'manage_addFormValidators')
    def manage_addFormValidators(self, REQUEST):
        """Process form validator add form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.validators
        else:
            container = self.validators
        controller._addFormValidators(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')


    security.declareProtected(ManagePortal, 'manage_delFormValidators')
    def manage_delFormValidators(self, REQUEST):
        """Process form validator delete form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.validators
        else:
            container = self.validators
        controller._delFormValidators(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formValidatorsForm')


    security.declareProtected(ManagePortal, 'manage_editFormActions')
    def manage_editFormActions(self, REQUEST):
        """Process form action edit form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.actions
        else:
            container = self.actions
        controller._editFormActions(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')


    security.declareProtected(ManagePortal, 'manage_addFormAction')
    def manage_addFormAction(self, REQUEST):
        """Process form action add form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.actions
        else:
            container = self.actions
        controller._addFormAction(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')


    security.declareProtected(ManagePortal, 'manage_delFormActions')
    def manage_delFormActions(self, REQUEST):
        """Process form action delete form"""
        controller = getToolByName(self, 'portal_form_controller')
        if REQUEST.form.get('override', 0):
            container = controller.actions
        else:
            container = self.actions
        controller._delFormActions(container, REQUEST)
        return REQUEST.RESPONSE.redirect(self.absolute_url()+'/manage_formActionsForm')


    def getNext(self, controller_state, REQUEST):
        id = self.getId()
        status = controller_state.getStatus()
        context = controller_state.getContext()

        context_type = getattr(context, 'portal_type', None)
        if context_type is None:
            context_type = getattr(context, '__class__', None)
            if context_type:
                context_type = getattr(context_type, '__name__', None)

        button = controller_state.getButton()
        controller = getToolByName(self, 'portal_form_controller')

        next_action = None
        try:
            next_action = controller.getAction(id, status, context_type, button)
        except ValueError:
            pass
        if next_action is None:
            try:
                if hasattr(aq_base(context), 'formcontroller_actions'):
                    next_action = context.formcontroller_actions.match(id, status, context_type, button)
            except ValueError:
                pass
        if next_action is None:
            try:
                next_action = self.actions.match(id, status, context_type, button)
            except ValueError:
                pass
            if next_action is None:
                next_action = controller_state.getNextAction()
                if next_action is None:
                    # default for failure is to traverse to the form
                    if status == 'failure':
                        next_action=FormAction(id, status, ANY_CONTEXT, ANY_BUTTON, 'traverse_to', 'string:%s' % id, controller)
                    if next_action is None:
                        metadata_actions = [str(a) for a in self.actions.getFiltered(object_id=id)]
                        zmi_actions = [str(a) for a in controller.actions.getFiltered(object_id=id)]
                        raise ValueError, 'No next action found for %s.%s.%s.%s\nMetadata actions:\n%s\n\nZMI actions:\n%s\n' % \
                            (id, status, context_type, button, '\n'.join(metadata_actions), '\n'.join(zmi_actions))

        REQUEST.set('controller_state', controller_state)
        return next_action.getAction()(controller_state)


    def getButton(self, controller_state, REQUEST):
        buttons = []
        for k in REQUEST.form.keys():
            if k.startswith('form.button.'):
                buttons.append(k)
        if buttons:
            # Clicking on an image button results in 2 button variables in REQUEST.form
            # (maybe 3),namely form.button.button_name.x, form.button.button_name.y, and
            # possibly form.button.button_name (not for IE, though)
            # If we see more than one key with the button prefix, try to handle sensibly.
            if len(buttons) > 1:
                buttons.sort(lambda x, y: cmp(len(x), len(y)))
                if buttons[0].endswith('.x') or buttons[0].endswith('.y'):
                    buttons[0] = buttons[0][:-2]
            button = buttons[0][len('form.button.'):]
            controller_state.setButton(button)
        return controller_state


    def getValidators(self, controller_state, REQUEST):
        controller = getToolByName(self, 'portal_form_controller')
        context = controller_state.getContext()
        context_type = controller._getTypeName(context)
        button = controller_state.getButton()

        validators = None
        try:
            validators = controller.validators.match(self.id, context_type, button)
            if validators is not None:
                return validators
        except ValueError:
            pass
        try:
            if hasattr(aq_base(context), 'formcontroller_validators'):
                validators = context.formcontroller_validators.match(self.id, context_type, button)
                if validators is not None:
                    return validators
        except ValueError:
            pass
        try:
            validators = self.validators.match(self.id, context_type, button)
            if validators is not None:
                return validators
        except ValueError:
            pass
        return FormValidator(self.id, ANY_CONTEXT, ANY_BUTTON, [])


    def _read_action_metadata(self, id, filepath):
        self.actions = FormActionContainer()

        metadata = FSMetadata(filepath)
        cfg = CMFConfigParser()
        filepath = expandpath(filepath)
        if os.path.exists(filepath + '.metadata'):
            cfg.read(filepath + '.metadata')

            try:
                controller = getToolByName(self, 'portal_form_controller')
            except AttributeError:
                controller = None

            _buttons_for_status = {}
                
            actions = metadata._getSectionDict(cfg, 'actions')
            if actions is None:
                actions = {}

            for (k, v) in actions.items():
                # action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG
                component = k.split('.')
                while len(component) < 4:
                    component.append('')
                if component[0] != 'action':
                    raise ValueError, '%s: Format for .metadata actions is action.STATUS.CONTEXT_TYPE.BUTTON = ACTION_TYPE:ACTION_ARG (not %s)' % (filepath, k)
                act = v.split(':',1)
                while len(act) < 2:
                    act.append('')

                context_type = component[2]
                if controller:
                    if context_type and (not context_type in controller.listContextTypes()):
                        # Don't raise an exception because sometimes full list of
                        # types may be unavailable (e.g. when moving a site)
                        # raise ValueError, 'Illegal context type %s' % context_type
                        log('Unknown context type %s for template %s' % (str(context_type), str(id)))

                self.actions.set(FormAction(id, component[1], component[2], component[3], act[0], act[1], controller))

                status_key = str(component[1])+'.'+str(context_type)
                if _buttons_for_status.has_key(status_key):
                    _buttons_for_status[status_key].append(component[3])
                else:
                    _buttons_for_status[status_key] = [component[3]]

            for (k, v) in _buttons_for_status.items():
                if v and not '' in v:
                    sk = k.split('.')
                    status = sk[0]
                    content_type = sk[1]
                    if not status:
                        status = 'ANY'
                    if not content_type:
                        content_type = 'ANY'
                    log('%s: No default action specified for status %s, content type %s.  Users of IE can submit pages using the return key, resulting in no button in the REQUEST.  Please specify a default action for this case.' % (str(filepath), status, content_type))
                    

    def _read_validator_metadata(self, id, filepath):
        self.validators = FormValidatorContainer()

        metadata = FSMetadata(filepath)
        cfg = CMFConfigParser()
        filepath = expandpath(filepath)
        if os.path.exists(filepath + '.metadata'):
            cfg.read(filepath + '.metadata')
            try:
                controller = getToolByName(self, 'portal_form_controller')
            except AttributeError:
                controller = None

            _buttons_for_status = {}

            validators = metadata._getSectionDict(cfg, 'validators')
            if validators is None:
                validators = {}
            for (k, v) in validators.items():
                # validators.CONTEXT_TYPE.BUTTON = LIST
                component = k.split('.')
                while len(component) < 3:
                    component.append('')
                if component[0] != 'validators':
                    raise ValueError, '%s: Format for .metadata validators is validators.CONTEXT_TYPE.BUTTON = LIST (not %s)' % (filepath, k)

                context_type = component[1]
                if controller:
                    if context_type and not context_type in controller.listContextTypes():
                        # Don't raise an exception because sometimes full list of
                        # types may be unavailable (e.g. when moving a site)
                        # raise ValueError, 'Illegal context type %s' % context_type
                        log('Unknown context type %s for template %s' % (str(context_type), str(id)))

                self.validators.set(FormValidator(id, component[1], component[2], v, controller))

                status_key = str(context_type)
                if _buttons_for_status.has_key(status_key):
                    _buttons_for_status[status_key].append(component[2])
                else:
                    _buttons_for_status[status_key] = [component[2]]

            for (k, v) in _buttons_for_status.items():
                if v and not '' in v:
                    content_type = k
                    if not content_type:
                        content_type = 'ANY'
                    log('%s: No default validators specified for content type %s.  Users of IE can submit pages using the return key, resulting in no button in the REQUEST.  Please specify default validators for this case.' % (str(filepath), content_type))


    security.declarePublic('writableDefaults')
    def writableDefaults(self):
        """Can default actions and validators be modified?"""
        return 1

InitializeClass(ControllerBase)
