from Acquisition import aq_base
from Acquisition import aq_inner
from Acquisition import aq_parent
from Products.CMFPlone.browser.interfaces import IPlone
from Products.CMFPlone.browser.navtree import getNavigationRoot
from Products.CMFPlone.interfaces import INonStructuralFolder
from Products.CMFPlone.interfaces.NonStructuralFolder import \
     INonStructuralFolder as z2INonStructuralFolder
from Products.CMFPlone import utils
from Products.CMFPlone import IndexIterator
from Products.CMFCore.utils import getToolByName
from Products.CMFCore.utils import _checkPermission
from Products.CMFCore.permissions import ModifyPortalContent
from Products.CMFCore.permissions import ListFolderContents
from Products.CMFCore.permissions import AddPortalContent
from Products.CMFCore.permissions import DeleteObjects
from Products.CMFCore.permissions import ReviewPortalContent
from Products.CMFPlone.interfaces import IBrowserDefault

from zope.interface import implements
from zope.component import getMultiAdapter


from Products import CMFPlone
import ZTUtils
import sys

_marker = []

# A simple memoize decorator that saves the value of method calls in a mapping
# on the view, don't use this to store anything but python built-ins
def cache_decorator(method):
    key = method.__name__
    def cached_method(self, *args, **kwargs):
        value_cache = getattr(self, '_value_cache', _marker)
        if value_cache is _marker:
            value_cache = self._value_cache = {}
        cached = value_cache.get(key, _marker)
        if cached is not _marker:
            return cached
        else:
            result = method(self, *args, **kwargs)
            value_cache[key] = result
            return result
    return cached_method

class Plone(utils.BrowserView):
    implements(IPlone)

    def globalize(self):
        """
        Pure optimization hack, globalizes entire view for speed. Yes
        it's evil, but this hack will eventually be removed after
        globals are officially deprecated.

        YOU CAN ONLY CALL THIS METHOD FROM A PAGE TEMPLATE AND EVEN
        THEN IT MIGHT DESTROY YOU!
        """
        context = sys._getframe(2).f_locals['econtext']
        # Some of the original global_defines used 'options' to get parameters
        # passed in through the template call, so we need this to support
        # products which may have used this little hack
        options = context.vars.get('options',{})

        state = {}
        self._initializeData(options=options)
        for name, v in self._data.items():
            state[name] = v
            context.setGlobal(name, v)

    def __init__(self, context, request):
        super(Plone, self).__init__(context, request)

        self._data = {}

    def _initializeData(self, options=None):
        # We don't want to do this in __init__ because the view provides
        # methods which are useful outside of globals.  Also, performing
        # actions during __init__ is dangerous because instances are usually
        # created during traversal, which means authentication hasn't yet
        # happened.
        context = utils.context(self)
        if options is None:
            options = {}

        # XXX: Can't store data as attributes directly because it will
        # insert the view into the acquisition chain. Someone should
        # come up with a way to prevent this or get rid of the globals
        # view altogether

        # BBB: utool is deprecated, use portal or portal_url instead
        self._data['utool'] = utool = getToolByName(context, 'portal_url')
        self._data['portal'] = portal = utool.getPortalObject()
        # BBB: portal_object is deprecated use portal instead
        self._data['portal_object'] = portal_object = portal
        self._data['portal_url'] =  utool()
        self._data['mtool'] = mtool = getToolByName(portal, 'portal_membership')
        # BBB: gtool is deprecated because it is only used in sepcialized
        # contexts
        self._data['gtool'] =  getToolByName(portal, 'portal_groups', None)
        # BBB: gdtool is deprecated because it is only used in specialized
        # contexts
        self._data['gdtool'] = getToolByName(portal, 'portal_groupdata', None)
        # BBB: atool is deprecated because it is unsused
        self._data['atool'] = atool = getToolByName(portal, 'portal_actions')
        # BBB: aitool is deprecated because it is unused
        self._data['aitool'] = getToolByName(portal, 'portal_actionicons', None)
        self._data['putils'] = putils = getToolByName(portal, 'plone_utils')
        self._data['wtool'] = wtool = getToolByName(portal, 'portal_workflow')
        self._data['ifacetool'] = getToolByName(portal, 'portal_interface', None)
        self._data['syntool'] = getToolByName(portal, 'portal_syndication')
        self._data['portal_title'] = portal_object.Title()
        self._data['object_title'] = putils.pretty_title_or_id(context)
        self._data['checkPermission'] = checkPermission = mtool.checkPermission
        self._data['member'] = mtool.getAuthenticatedMember()
        self._data['membersfolder'] =  mtool.getMembersFolder()
        self._data['isAnon'] =  mtool.isAnonymousUser()
        self._data['actions'] = actions = (options.get('actions', None) or
                                        atool.listFilteredActionsFor(context))
        self._data['keyed_actions'] =  self.keyFilteredActions(actions)
        self._data['user_actions'] =  actions['user']
        self._data['workflow_actions'] =  actions['workflow']
        self._data['folder_actions'] =  actions['folder']
        self._data['global_actions'] =  actions['global']

        portal_tabs_view = getMultiAdapter((context, context.REQUEST), name='portal_tabs_view')
        self._data['portal_tabs'] =  portal_tabs_view.topLevelTabs(actions=actions)

        self._data['wf_state'] =  wtool.getInfoFor(context,'review_state', None)
        self._data['portal_properties'] = props = getToolByName(portal,
                                                          'portal_properties')
        self._data['site_properties'] = site_props = props.site_properties
        self._data['ztu'] =  ZTUtils
        # BBB: wf_actions is deprecated use workflow_actions instead
        self._data['wf_actions'] =  self._data['workflow_actions']
        self._data['isFolderish'] =  getattr(context.aq_explicit, 'isPrincipiaFolderish', False)
        self._data['slots_mapping'] = slots = (
                                         options.get('slots_mapping', None) or
                                         self._prepare_slots())
        self._data['sl'] = sl = slots['left']
        self._data['sr'] = sr = slots['right']
        self._data['here_url'] =  context.absolute_url()
        # BBB: hidecolumns is deprecated as it is not needed globally
        self._data['hidecolumns'] =  self.hide_columns(sl, sr)
        self._data['default_language'] = default_language = \
                              site_props.getProperty('default_language', None)
        self._data['language'] =  self.request.get('language', None) or \
                                  context.Language() or default_language
        self._data['is_editable'] = checkPermission('Modify portal content',
                                                     context)
        # BBB: isEditable is deprecated use is_editable instead
        self._data['isEditable'] =  self._data['is_editable']
        lockable = hasattr(aq_inner(context).aq_explicit, 'wl_isLocked')
        # BBB: locakble is deprecated use only isLocked intead, which implies
        # lockable
        self._data['lockable'] = lockable
        self._data['isLocked'] = lockable and context.wl_isLocked()
        self._data['isRTL'] =  self.isRightToLeft(domain='plone')
        self._data['visible_ids'] =  self.visibleIdsEnabled() or None
        self._data['current_page_url'] =  self.getCurrentUrl() or None
        self._data['normalizeString'] = putils.normalizeString
        self._data['toLocalizedTime'] = self.toLocalizedTime
        self._data['isStructuralFolder'] = self.isStructuralFolder()
        self._data['isContextDefaultPage'] = self.isDefaultPageInFolder()

        self._data['navigation_root_url'] = self.navigationRootUrl()
        self._data['Iterator'] = IndexIterator
        self._data['tabindex'] = IndexIterator(pos=30000, mainSlot=False)
        self._data['uniqueItemIndex'] = IndexIterator(pos=0)

    def keyFilteredActions(self, actions=None):
        """ See interface """
        context = utils.context(self)
        if actions is None:
            actions=context.portal_actions.listFilteredActionsFor()

        keyed_actions={}
        for category in actions.keys():
            keyed_actions[category]={}
            for action in actions[category]:
                id=action.get('id',None)
                if id is not None:
                    keyed_actions[category][id]=action.copy()

        return keyed_actions

    def getCurrentUrl(self):
        """ See interface """
        context = utils.context(self)
        request = context.REQUEST
        url = request.get('ACTUAL_URL', request.get('URL', None))
        query = request.get('QUERY_STRING','')
        if query:
            query = '?'+query
        return url+query
    getCurrentUrl = cache_decorator(getCurrentUrl)

    def visibleIdsEnabled(self):
        """ See interface """
        context = utils.context(self)
        props = getToolByName(context, 'portal_properties').site_properties
        if not props.getProperty('visible_ids', False):
            return False

        pm=context.portal_membership
        if pm.isAnonymousUser():
            return False

        user = pm.getAuthenticatedMember()
        if user is not None:
            return user.getProperty('visible_ids', False)
        return False
    visibleIdsEnabled = cache_decorator(visibleIdsEnabled)

    def isRightToLeft(self, domain='plone'):
        """ See interface """
        context = utils.context(self)
        try:
            from Products.PlacelessTranslationService import isRTL
        except ImportError:
            # This may mean we have an old version of PTS or no PTS at all.
            return 0
        else:
            try:
                return isRTL(context, domain)
            except AttributeError:
                # This may mean that PTS is present but not installed.
                # Can effectively only happen in unit tests.
                return 0

    # XXX: This is lame
    def hide_columns(self, column_left, column_right):
        """ See interface """

        if column_right==[] and column_left==[]:
            return "visualColumnHideOneTwo"
        if column_right!=[]and column_left==[]:
            return "visualColumnHideOne"
        if column_right==[]and column_left!=[]:
            return "visualColumnHideTwo"
        return "visualColumnHideNone"

    def _prepare_slots(self):
        """ Prepares a structure that makes it conveient to determine
            if we want to use-macro or render the path expression.
            The values for the dictioanries is a list of tuples
            that are path expressions and the second value is a
            1 for use-macro, 0 for render path expression.
        """
        context = utils.context(self)
        slots={ 'left':[],
                'right':[],
                'document_actions':[] }

        left_slots=getattr(context,'left_slots', [])
        right_slots=getattr(context,'right_slots', [])
        document_action_slots=getattr(context,'document_action_slots', [])

        #check if the *_slots attributes are callable so that they can be
        # overridden by methods or python scripts

        if callable(left_slots):
            left_slots=left_slots()

        if callable(right_slots):
            right_slots=right_slots()

        if callable(document_action_slots):
            document_action_slots=document_action_slots()

        for slot in left_slots:
            if not slot: continue
            if slot.find('/macros/')!=-1:
                slots['left'].append( (slot, 1) )
            else:
                slots['left'].append( (slot, 0) )

        for slot in right_slots:
            if not slot: continue
            if slot.find('/macros/')!=-1:
                slots['right'].append( (slot, 1) )
            else:
                slots['right'].append( (slot, 0) )

        for slot in document_action_slots:
            if not slot: continue
            if slot.find('/macros/')!=-1:
                slots['document_actions'].append( (slot, 1) )
            else:
                slots['document_actions'].append( (slot, 0) )

        return slots

    def toLocalizedTime(self, time, long_format=None):
        """ See interface """
        context = utils.context(self)
        tool = getToolByName(context, 'translation_service')
        return tool.ulocalized_time(time, long_format, context,
                                    domain='plone')

    def isDefaultPageInFolder(self):
        """ See interface """
        context = utils.context(self)
        request = context.REQUEST
        container = aq_parent(aq_inner((context)))
        if not container:
            return False
        view = getMultiAdapter((container, request), name='default_page')
        return view.isDefaultPage(context)
    isDefaultPageInFolder = cache_decorator(isDefaultPageInFolder)

    def isStructuralFolder(self):
        """ See interface """
        context = utils.context(self)
        folderish = bool(getattr(aq_base(context), 'isPrincipiaFolderish',
                                 False))
        if not folderish:
            return False
        elif INonStructuralFolder.providedBy(context):
            return False
        elif z2INonStructuralFolder.isImplementedBy(context):
            # BBB: for z2 interface compat
            return False
        else:
            return folderish
    isStructuralFolder = cache_decorator(isStructuralFolder)

    def navigationRootPath(self):
        context = utils.context(self)
        return getNavigationRoot(context)
    navigationRootPath = cache_decorator(navigationRootPath)

    def navigationRootUrl(self):
        context = utils.context(self)
        portal_url = getToolByName(context, 'portal_url')

        portal = portal_url.getPortalObject()
        portalPath = portal_url.getPortalPath()

        rootPath = getNavigationRoot(context)
        rootSubPath = rootPath[len(portalPath):]

        return portal.absolute_url() + rootSubPath
    navigationRootUrl = cache_decorator(navigationRootUrl)

    def getParentObject(self):
        context = utils.context(self)
        return aq_parent(aq_inner(context))

    def getCurrentFolder(self):
        context = utils.context(self)
        if self.isStructuralFolder() and not self.isDefaultPageInFolder():
            return context
        return self.getParentObject()

    def getCurrentFolderUrl(self):
        return self.getCurrentFolder().absolute_url()

    def getCurrentObjectUrl(self):
        context = utils.context(self)
        if self.isDefaultPageInFolder():
            obj = self.getParentObject()
        else:
            obj = context
        return obj.absolute_url()

    def isFolderOrFolderDefaultPage(self):
        context = utils.context(self)
        if self.isStructuralFolder() or self.isDefaultPageInFolder():
            return True
        return False
    isFolderOrFolderDefaultPage = cache_decorator(isFolderOrFolderDefaultPage)

    def isPortalOrPortalDefaultPage(self):
        context = utils.context(self)
        portal = getToolByName(context, 'portal_url').getPortalObject()
        if aq_base(context) is aq_base(portal) or \
           (aq_base(self.getParentObject()) is aq_base(portal) and
            self.isDefaultPageInFolder()):
            return True
        return False
    isPortalOrPortalDefaultPage = cache_decorator(isPortalOrPortalDefaultPage)

    def getViewTemplateId(self):
        """See interface"""
        context = utils.context(self)

        browserDefault = IBrowserDefault(context, None)
        if browserDefault is not None:
            try:
                return browserDefault.getLayout()
            except AttributeError:
                # Might happen if FTI didn't migrate yet.
                pass

        # Else, if there is a 'folderlisting' action, this will take
        # precedence for folders, so try this, else use the 'view' action.
        action = self._lookupTypeActionTemplate('object/view')

        if not action:
            action = self._lookupTypeActionTemplate('folder/folderlisting')

        return action
    getViewTemplateId = cache_decorator(getViewTemplateId)

    def _lookupTypeActionTemplate(self, actionId):
        context = utils.context(self)
        fti = context.getTypeInfo()
        try:
            # XXX: This isn't quite right since it assumes the action starts with ${object_url}
            action = fti.getActionInfo(actionId)['url'].split('/')[-1]
        except ValueError:
            # If the action doesn't exist, stop
            return None

        # Try resolving method aliases because we need a real template_id here
        action = fti.queryMethodID(action, default = action, context = context)

        # Strip off leading /
        if action and action[0] == '/':
            action = action[1:]
        return action

    def displayContentsTab(self):
        """See interface"""
        context = utils.context(self)
        modification_permissions = (ModifyPortalContent,
                                    AddPortalContent,
                                    DeleteObjects,
                                    ReviewPortalContent)

        contents_object = context
        # If this object is the parent folder's default page, then the
        # folder_contents action is for the parent, we check permissions
        # there. Otherwise, if the object is not folderish, we don not display
        # the tab.
        if self.isDefaultPageInFolder():
            contents_object = self.getCurrentFolder()
        elif not self.isStructuralFolder():
            return 0

        # If this is not a structural folder, stop.
        plone_view = getMultiAdapter((contents_object, self.request),
                                     name='plone')
        if not plone_view.isStructuralFolder():
            return 0

        show = 0
        # We only want to show the 'contents' action under the following
        # conditions:
        # - If you have permission to list the contents of the relavant
        #   object, and you can DO SOMETHING in a folder_contents view. i.e.
        #   Copy or Move, or Modify portal content, Add portal content,
        #   or Delete objects.

        # Require 'List folder contents' on the current object
        if _checkPermission(ListFolderContents, contents_object):
            # If any modifications are allowed on object show the tab.
            for permission in modification_permissions:
                if _checkPermission(permission, contents_object):
                    show = 1
                    break

        return show
    displayContentsTab = cache_decorator(displayContentsTab)
