import os
from Acquisition import aq_base, aq_inner
from Globals import InitializeClass
from zExceptions import Forbidden
from AccessControl import ClassSecurityInfo
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.CMFCore.permissions import View, ManagePortal
from Products.CMFCore.utils import getToolByName, expandpath
from Products.CMFCore.FSMetadata import FSMetadata, CMFConfigParser
from FormAction import FormAction, FormActionContainer
from FormValidator import FormValidator, FormValidatorContainer
from globalVars import ANY_CONTEXT, ANY_BUTTON
from utils import log
from logging import WARNING

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'
    require_post = False

    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"""
        self._setPOSTRequired(REQUEST)
        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"""
        self._setPOSTRequired(REQUEST)
        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 _setPOSTRequired(self, REQUEST):
        self.require_post = bool(REQUEST.get('require_post', False))

    security.declareProtected(ManagePortal, 'getPostRequired')
    def getPOSTRequired(self):
        """Returns a boolean indicating whether or not to require POST requests
        """
        return self.require_post

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

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

        button = controller_state.getButton()
        controller = getToolByName(aq_inner(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 getattr(context_base, 'formcontroller_actions', None) is not None:
                    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):
        if REQUEST.get('REQUEST_METHOD', 'GET') == 'GET':
            form_id = getattr(self, 'id', 'Unknown')
            if not self.require_post:
                log('You have triggered the form controller action "%s" using '
                    'a GET REQUEST.  This is a potential security hazard.  In '
                    'Plone 3.0 this will FAIL unless you explicitly enable '
                    'your form to support GET requests in the ZMI (or '
                    'using the .metadata file).'%form_id, WARNING)
            else:
                raise Forbidden, ("This form's actions can not be triggered "
                                  "using a GET 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
                if k == 'require_post':
                    # Set to False if the value is False or 'False' or '0'
                    require_post =  not (not v or v in ['False', '0']) and \
                                   True or False
                    self.require_post = require_post
                    continue
                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)
