from zope.tales.tales import CompilerError

from AccessControl import Role, ClassSecurityInfo
from Acquisition import aq_base, aq_parent, aq_inner
from Products.CMFCore.Expression import Expression
from Products.PageTemplates.Expressions import getEngine
from Products.PageTemplates.Expressions import SecureModuleImporter

from Products.CMFCore.utils import getToolByName
from Products.CMFFormController.config import URL_ENCODING
from Products.CMFFormController.utils import log
from IFormAction import IFormAction
from ZTUtils.Zope import make_query

class BaseFormAction(Role.RoleManager):
    __implements__ = IFormAction,

    security = ClassSecurityInfo()
    security.declareObjectPublic()
    security.setDefaultAccess('allow')

    expression = None

    def __init__(self, arg=None):
        if arg is None:
            log('No argument specified for action.  This means that some of your CMFFormController actions may have been corrupted.  You may be able to fix them by editing the actions in question via the Actions tab and re-saving them.')
        else:
            try:
                self.expression = Expression(arg)
            except:
                raise CompilerError, 'Bad action expression %s' % str(arg)


    def __call__(self, controller_state):
        raise NotImplementedError


    def getArg(self, controller_state):
        """Generate an expression context for the TALES expression used as
        the argument to the action and evaluate the expression."""
        context = controller_state.getContext()

        portal = getToolByName(context, 'portal_url').getPortalObject()
        portal_membership = getToolByName(portal, 'portal_membership')

        if context is None or not hasattr(context, 'aq_base'):
            folder = portal
        else:
            folder = context
            # Search up the containment hierarchy until we find an
            # object that claims to be a folder.
            while folder is not None:
                if getattr(aq_base(folder), 'isPrincipiaFolderish', 0):
                    # found it.
                    break
                else:
                    folder = aq_parent(aq_inner(folder))

        object_url = context.absolute_url()

        if portal_membership.isAnonymousUser():
            member = None
        else:
            member = portal_membership.getAuthenticatedMember()
        data = {
            'object_url':   object_url,
            'folder_url':   folder.absolute_url(),
            'portal_url':   portal.absolute_url(),
            'object':       context,
            'folder':       folder,
            'portal':       portal,
            'nothing':      None,
            'request':      getattr( context, 'REQUEST', None ),
            'modules':      SecureModuleImporter,
            'member':       member,
            'state':        controller_state,
            }
        exprContext = getEngine().getContext(data)
        return self.expression(exprContext)


    def combineArgs(self, url, kwargs):
        """Utility method that takes a URL, parses its existing query string,
        and combines the resulting dict with kwargs"""
        import urlparse
        import cgi

        # parse the existing URL
        parsed_url = list(urlparse.urlparse(url))
        # get the existing query string
        qs = parsed_url[4]
        # parse the query into a dict
        d = cgi.parse_qs(qs, 1)
        # update with stuff from kwargs
        for k, v in kwargs.items():
            if isinstance(v, unicode):
                v = v.encode(URL_ENCODING)
            d[k] = [v] # put in a list to be consistent with parse_qs
        # parse_qs behaves a little unexpectedly -- all query string args
        # are represented as lists.  I think the reason is so that you get
        # consistent behavior for things like http://myurl?a=1&a=2&a=3
        # For this case parse_qs returns d['a'] = ['1','2','3']
        # However, that means that http://myurl?a=1 comes back as d['a']=['1']
        # unmunge some of parse_qs's weirdness
        dnew = {}
        for k, v in d.items():
            if v and len(v) == 1:
                dnew[k] = v[0]
            else:
                dnew[k] = v
        return dnew


    def updateQuery(self, url, kwargs):
        """Utility method that takes a URL, parses its existing query string,
        url encodes
        and updates the query string using the values in kwargs"""
        d = self.combineArgs(url, kwargs)
        
        import urlparse

        # parse the existing URL
        parsed_url = list(urlparse.urlparse(url))
            
        # re-encode the string
        # We use ZTUtils.make_query here because it
        # does Zope-specific marshalling of lists,
        # dicts, integers and DateTime.
        # XXX *Normal* people should not be affected by this.
        # but one can argue about the need of using
        # standard query encoding instead for non-Zope
        # destination urls.
        parsed_url[4] = make_query(**d)
        # rebuild the URL
        return urlparse.urlunparse(parsed_url)

