##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (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.
#
##############################################################################
"""Machinery for making things viewable

$Id: viewable.py 19645 2005-11-08 15:45:35Z yuppie $
"""
import inspect
from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getDefaultViewName, ComponentLookupError
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest

from Products.Five.traversable import FakeRequest
from Products.Five.interfaces import IBrowserDefault

_marker = object

class Viewable:
    """A mixin to make an object viewable.
    """
    __five_viewable__ = True

    def __fallback_default__(self, request):
        """Try to dispatch to existing index_html or __call__"""
        if getattr(self, 'index_html', None):
            return self, ('index_html',)
        if getattr(self, 'fallback_call__', None):
            return self, ('fallback_call__',)
        # XXX Should never get this far. But if it does?

    # def fallback_call__(self, *args, **kw):
    #    """By default, return self"""
    #    return self

    # we have a default view, tell zpublisher to go there
    def __browser_default__(self, request):
        obj = self
        path = None
        if request['REQUEST_METHOD'] not in ('GET', 'POST'):
            return obj, [request['REQUEST_METHOD']]
        try:
            obj, path = IBrowserDefault(self).defaultView(request)
        except ComponentLookupError:
            pass
        if path:
            if len(path) == 1 and path[0] == '__call__':
                return obj, ('fallback_call__',)
            return obj, path
        return self.__fallback_default__(request)
    __browser_default__.__five_method__ = True

    # this is technically not needed because ZPublisher finds our
    # attribute through __browser_default__; but we also want to be
    # able to call pages from python modules, PythonScripts or ZPT
    # def __call__(self, *args, **kw):
    #    """ """
    #    request = kw.get('REQUEST')
    #    if not IBrowserRequest.providedBy(request):
    #        request = getattr(self, 'REQUEST', None)
    #        if not IBrowserRequest.providedBy(request):
    #            request = FakeRequest()
    #    obj, path = self.__browser_default__(request)
    #    if path and not simpleRecursion():
    #        meth = obj.unrestrictedTraverse(path)
    #        if meth is not None:
    #            return meth(*args, **kw)
    #    return self.fallback_call__(*args, **kw)
    # __call__.__five_method__ = True

# def simpleRecursion():
#     # This tests for simple recursion, which can easily happen
#     # in CMF, like the following:
#     # - Object has a method named 'view'
#     # - 'view' method calls '__call__'
#     # - five:viewable overrides call to use '__browser_default__'
#     #   to find a default view and call it
#     # - defaultView is set to 'view'
#     # Bang. Infinite recursion.
#     stack = inspect.stack()
#     try:
#         if len(stack) < 4:
#             return False
#         if stack[2][1:4] == stack[4][1:4]:
#             return True
#     finally:
#         del stack
#     return False

class BrowserDefault(object):
    implements(IBrowserDefault)

    def __init__(self, context):
        self.context = context

    def defaultView(self, request):
        context = self.context
        try:
            name = getDefaultViewName(context, request)
            return context, [name,]
        except ComponentLookupError:
            return context, None
