from WebKit.Page import Page
try:
    from WebKit.HTTPExceptions import *
    hasExceptions = True
except ImportError:
    hasExceptions = False
try:
    from WebKit import AppServer
except ImportError:
    AppServer = None

class CPage(Page):

    """
    CPage is an alternative to WebKit.Page, which adds support
    for Components.

    Components can contain functionality like user logins,
    widgets and layout generation, template handling... whatever.

    Components for a servlet are listed in the components
    class variable, like::

        class SitePage(CPage):
            components = [UserComponent(), OtherFancyComponent()]

    You can also run ``servlet.addComponent(component)``.

    Components can respond to events, and add methods to the
    servlet.  See the ServletComponent class for more about writing
    components.

    To fire your own event use callEvent, like::

        self.callEvent('eventname', ...)

    Use whatever arguments you want.  Interested components
    will implement a method eventnameEvent(), which will be
    passed those arguments.  callEvent will return a false
    value if a component wants to abort this event (because
    the component deals with the event on its own).
    """

    # Events listed in _standardEvents will be run a bit faster,
    # as CPage keeps track of these events specially.  You can
    # add your custom events here.
    _standardEvents = ['awake', 'sleep', 'exception']

    def __init__(self):
        Page.__init__(self)
        self._instanceComponents = []
        self._standardEventHandlers = {}
        for eventName in self._standardEvents:
            self._standardEventHandlers[eventName] = []
        for componentFactory in self.components:
            self.addComponent(componentFactory)
        if hasattr(self, '__traceback_supplement_hide_vars__'):
            hide_vars = self.__traceback_supplement_hide_vars__
            for var in ['_standardEventHandlers']:
                if var not in hide_vars:
                    hide_vars.append(var)

    def actions(self):
        return []

    def addComponent(self, componentFactory):
        component = componentFactory.componentFor(self)
        if not component:
            return
        component.addComponentTo(self)
        self._instanceComponents.append(component)
        for name in self._standardEventHandlers.keys():
            if getattr(component, '%sEvent' % name, None):
                self._standardEventHandlers[name].append(getattr(component, '%sEvent' % name))

    def callEvent(self, eventName, *args, **kw):
        """
        Fire an event, to interested components.

	@@: maybe this should be replaced with PyDispatcher:
	http://pydispatcher.sourceforge.net/
        """
        if self._standardEventHandlers.has_key(eventName):
            for handler in self._standardEventHandlers[eventName]:
                if not handler(*args, **kw):
                    return 0
        else:
            for component in self._instanceComponents:
                meth = getattr(component, "%sEvent" % eventName, None)
                if meth and not meth(*args, **kw):
                    return 0
        return 1

    def awake(self, trans):
        Page.awake(self, trans)
        self._title = self.__class__.__name__
        self.setView('writeContent')
        self.callEvent('awake', trans)

    def sleep(self, trans):
        self.callEvent('sleep', trans)
        self._title = None
        self._view = None
        Page.sleep(self, trans)

    def runTransaction(self, trans):
        try:
            try:
                self.awake(trans)
                self.respond(trans)
            except Exception, e:
                if self.handleException(e) == 'break':
                    return
                for component in self._instanceComponents:
                    if component.handleExceptionEvent(e) == 'break':
                        return
                raise
        finally:
            self.sleep(trans)

    def handleException(self, exc):
        pass

    def servletLink(self, name, absolute=False, extraURLPath=None,
                    args=None):
        """
        Links to the named servlet; or use 'path/to/servlet'; do not
        include a leading /
        """
        assert not name.startswith('/'), 'The link name must not start with a / (%r)' % name
        url = ''
        req = self.request()
        env = req.environ()
        if absolute:
            # @@: Should detect HTTPS
            url = 'http://%s' % env.get('HTTP_HOST', env.get('SERVER_NAME'))
            port = env.get('SERVER_PORT', '80')
            if port != '80':
                url = url + ':' + port
        url = url + req.adapterName() + '/' + name
        if extraURLPath:
            if not extraURLPath.startswith('/'):
                extraURLPath = '/' + extraURLPath
            url = url + extraURLPath
        if args:
            fields = []
            if isinstance(args, dict):
                args = args.items()
            for name, value in args:
                if value is None:
                    continue
                fields.append('%s=%s' % (self.urlEncode(str(name)),
                                         self.urlEncode(str(value))))
            url = url + '?' + '&'.join(fields)
        return url

    def linkToSelf(self, absolute=False, extraURLPath=None, args=None):
        name = self.__class__.__name__
        contextName = self.request().contextName()
        app = self.transaction().application()
        if (contextName != app.setting('Contexts', {}).get('default')
            and contextName != 'default'):
            if contextName:
                name = contextName + '/' + name
        return self.servletLink(
            name,
            absolute=absolute,
            extraURLPath=extraURLPath, args=args)

    ############################################################
    ## Reorganized action system:
    ############################################################

    def writeHeader(self):
        pass

    def writeFooter(self):
        pass

    def title(self):
        return self._title

    def viewMethod(self):
        """
        Return the method object for the current view.
        """
        if type(self._view) in (type(""), type(u"")):
            return getattr(self, self._view)
        else:
            return self._view

    def view(self):
        """
        Return the string name of the current view (the method
        name)
        """
        if type(self._view) in (type(""), type(u"")):
            return self._view
        else:
            view = self._view
            if view is None:
                return None
            try:
                view = view.im_func
            except AttributeError:
                pass
            try:
                return view.func_name
            except AttributeError:
                raise AttributeError, "I don't know what kind of view this is: %r" % view

    def setView(self, view):
        """
        Set the 'view'.  The view is the method that is used to write
        the content (typically writeContent -- but you can override
        this).

        You can set the view to a string, which will be a method of
        self, or you can provide a function-like object.
        """
        self._view = view

    def writeBodyParts(self):
        self.writeHeader()
        self.viewMethod()()
        self.writeFooter()
        
    def _respond(self, transaction):
        """
        Handles actions if an ``_action_`` or ``_action_XXX``
        field is defined, otherwise invokes `writeHTML`.
        Invoked by both `respondToGet` and `respondToPost`.

        Copied with slight modification from Page._respond
        """
        req = transaction.request()

        # Support old style actions from 0.5.x and below.
        # Use setting OldStyleActions in Application.config
        # to use this.
        if transaction.application().setting('OldStyleActions', False) \
           and req.hasField('_action_'):
            action = self.methodNameForAction(req.field('_action_'))
            actions = self._actionSet()
            if actions.has_key(action):
                self.preAction(action)
                apply(getattr(self, action), (transaction,))
                self.postAction(action)
                return
            else:
                raise PageError, "Action '%s' is not in the public list of actions, %s, for %s." % (action, actions.keys(), self)

        # Check for actions
        # @@: This is the only change for CPage:
        for action in self._actionSet().keys():
            if req.hasField('_action_%s' % action) or \
               req.field('_action_', None) == action or \
               (req.hasField('_action_%s.x' % action) and \
                req.hasField('_action_%s.y' % action)):
                self.handleAction(action)
                return

        self.defaultAction()

    if not hasattr(Page, 'defaultAction'):
        def defaultAction(self):
            self.writeHTML()

    def _actionSet(self):
        if not hasattr(self, '_actionDict'):
            self._actionDict = {}
            for action in self.actions():
                self._actionDict[action] = 1
            for component in self._instanceComponents:
                for action in component.actions():
                    self._actionDict[action] = 1
        return self._actionDict

    def preAction(self, actionName):
        pass

    def postAction(self, actionName):
        pass

    components = []

    def sendResponseAndEnd(self, status, headers=None, body=None):
        res = self.response()
        res.setHeader('Status', str(status))
        if headers:
            for name, value in headers.items():
                res.setHeader(name, value)
        if body:
            res.write(body)
        else:
            res.write('<html><head><title>%s</title></head><body><h1>%s</h1>' % (status, status))
            if headers and headers.get('location'):
                res.write('See <a href="%s">%s</a>' % headers['location'])
            res.write('</body></html>')
        self.endResponse()
