#
#  Copyright (c) 2016 Helmut Merz helmutm@cy55.de
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#

"""
Common base class for loops browser view classes.
"""

from cgi import parse_qs, parse_qsl
#import mimetypes   # use more specific assignments from cybertools.text
from datetime import date, datetime
import re
from time import strptime
from urllib import urlencode
from zope import component
from zope.app.form.browser.interfaces import ITerms
from zope.app.i18n.interfaces import ITranslationDomain
from zope.app.security.interfaces import IAuthentication, IUnauthenticatedPrincipal
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.app.security.interfaces import PrincipalLookupError
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form
from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.interface import Interface, implements
from zope.proxy import removeAllProxies
from zope.publisher.browser import applySkin
from zope.publisher.interfaces.browser import IBrowserSkinType, IBrowserView
from zope import schema
from zope.schema.vocabulary import SimpleTerm
from zope.security import canAccess
from zope.security.interfaces import ForbiddenAttribute, Unauthorized
from zope.security.proxy import removeSecurityProxy
from zope.traversing.browser import absoluteURL
from zope.traversing.api import getName, getParent, traverse

from cybertools.ajax.dojo import dojoMacroTemplate
from cybertools.browser.action import actions
from cybertools.browser.view import GenericView
from cybertools.meta.interfaces import IOptions
from cybertools.meta.element import Element
from cybertools.relation.interfaces import IRelationRegistry
from cybertools.stateful.interfaces import IStateful
from cybertools.text import mimetypes
from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.util.date import toLocalTime
from cybertools.util.format import formatDate
from cybertools.util.jeep import Jeep
from loops.browser.util import normalizeForUrl
from loops.common import adapted, baseObject
from loops.config.base import DummyOptions
from loops.i18n.browser import I18NView
from loops.interfaces import IResource, IView, INode, ITypeConcept
from loops.organize.personal import favorite
from loops.organize.party import getPersonForUser
from loops.organize.tracking import access
from loops.organize.util import getRolesForPrincipal
from loops.resource import Resource
from loops.security.common import checkPermission
from loops.security.common import canAccessObject, canListObject, canWriteObject
from loops.security.common import canEditRestricted
from loops.type import ITypeConcept, LoopsTypeInfo
from loops import util
from loops.util import _, saveRequest
from loops import version
from loops.versioning.interfaces import IVersionable


concept_macros = ViewPageTemplateFile('concept_macros.pt')
conceptMacrosTemplate = concept_macros
resource_macros = ViewPageTemplateFile('resource_macros.pt')
form_macros = ViewPageTemplateFile('form_macros.pt')


class NameField(schema.ASCIILine):

    def _validate(self, value):
        super(NameField, self)._validate(value)


class ViewMode(object):

    def __init__(self, name='view', title=None, url=None, active=False,
                 description=u''):
        self.name = name
        self.title = title
        self.url = url
        self.active = active
        self.description = description

    @property
    def cssClass(self):
        return self.active and u'active' or u'inactive'


class IAddForm(Interface):

    name = NameField(
            title=_(u'Object name'),
            description=_(u'Name of the object - will be used for addressing the '
                        u'object via a URL; should therefore be unique within '
                        u'the container and not contain special characters'))


class AddForm(form.AddForm):

    form_fields = FormFields(IAddForm)
    template = NamedTemplate('loops.pageform')


class EditForm(form.EditForm):

    template = NamedTemplate('loops.pageform')

    def deleteObjectAction(self):
        return None  # better not to show the delete button at the moment
        parent = getParent(self.context)
        parentUrl = absoluteURL(parent, self.request)
        return parentUrl + '/contents.html'


class SortableMixin(object):

    @Lazy
    def sortInfo(self):
        result = {}
        for k, v in self.request.form.items():
            if k.startswith('sortinfo_'):
                tableName = k[len('sortinfo_'):]
                if ',' in v:
                    fn, dir = v.split(',')
                else:
                    fn = v
                    dir = 'asc'
                result[tableName] = dict(
                    colName=fn, ascending=(dir=='asc'), fparam=v)
        result = favorite.updateSortInfo(getPersonForUser(
                        self.context, self.request), self.target, result)
        return result

    def isSortableColumn(self, tableName, colName):
        return False    # overwrite in subclass

    def getSortUrl(self, tableName, colName):
        url = str(self.request.URL)
        paramChar = '?' in url and '&' or '?'
        si = self.sortInfo.get(tableName)
        if si is not None and si.get('colName') == colName:
            dir = si['ascending'] and 'desc' or 'asc'
        else:
            dir = 'asc'
        return '%s%ssortinfo_%s=%s,%s' % (url, paramChar, tableName, colName, dir)

    def getSortParams(self, tableName):
        url = str(self.request.URL)
        paramChar = '?' in url and '&' or '?'
        si = self.sortInfo.get(tableName)
        if si is not None:
            colName = si['colName']
            dir = si['ascending'] and 'asc' or 'desc'
            return '%ssortinfo_%s=%s,%s' % (paramChar, tableName, colName, dir)
        return ''

    def getSortImage(self, tableName, colName):
        si = self.sortInfo.get(tableName)
        if si is not None and si.get('colName') == colName:
            if si['ascending']:
                return '/@@/cybertools.icons/arrowdown.gif'
            else:
                return '/@@/cybertools.icons/arrowup.gif'


class BaseView(GenericView, I18NView, SortableMixin):

    actions = {}
    portlet_actions = []
    parts = ()
    subparts = ()
    icon = None
    modeName = 'view'
    isToplevel = False
    isVisible = True

    def __init__(self, context, request):
        context = baseObject(context)
        super(BaseView, self).__init__(context, request)
        # TODO: get rid of removeSecurityProxy() call - not yet...
        self.context = removeSecurityProxy(context)
        try:
            if not self.checkPermissions():
                raise Unauthorized(str(self.contextInfo))
        except ForbiddenAttribute:  # ignore when testing
            pass
        saveRequest(request)

    def todayFormatted(self):
        return formatDate(date.today(), 'date', 'short',
                          self.languageInfo.language)

    def checkPermissions(self):
        return canAccessObject(self.context)

    def translate(self, text, msgFactory=_):
        if msgFactory is None:
            return text
        return msgFactory(text)

    @Lazy
    def contextInfo(self):
        return dict(view=self, context=getName(self.context))

    @Lazy
    def conceptMacros(self):
        return self.controller.getTemplateMacros('concept', concept_macros)
        #return concept_macros.macros

    concept_macros = conceptMacros

    @Lazy
    def resource_macros(self):
        return self.controller.getTemplateMacros('resource', resource_macros)

    @Lazy
    def form_macros(self):
        return self.controller.getTemplateMacros('form', form_macros)

    def breadcrumbs(self):
        return []

    def viewModes(self):
        return Jeep()

    @Lazy
    def name(self):
        return getName(self.context)

    def makeTargetUrl(self, baseUrl, targetId, title=None):
        if self.globalOptions('useInformativeURLs') and title:
            return '%s/.%s-%s' % (baseUrl, targetId, normalizeForUrl(title))
        return '%s/.%s' % (baseUrl, targetId)

    def filterInput(self):
        result = []
        for name in self.getOptions('filter_input'):
            view = component.queryMultiAdapter(
                        (self.context, self.request), name='filter_input.' + name)
            if view is not None:
                result.append(view)
        return result

    @Lazy
    def urlParamString(self):
        return self.getUrlParamString()

    def getUrlParamString(self):
        qs = self.request.get('QUERY_STRING')
        if qs:
            return '?' + qs
        return ''

    @Lazy
    def principalId(self):
        principal = self.request.principal
        return principal and principal.id or ''

    @Lazy
    def isAnonymous(self):
        return IUnauthenticatedPrincipal.providedBy(self.request.principal)

    def recordAccess(self, viewName, **kw):
        access.record(self.request, principal=self.principalId, view=viewName, **kw)

    @Lazy
    def versions(self):
        return version.versions

    @Lazy
    def longVersions(self):
        return version.longVersions

    def update(self):
        result = super(BaseView, self).update()
        self.checkLanguage()
        return result

    def registerPortlets(self):
        pass

    @Lazy
    def target(self):
        # allow for having a separate object the view acts upon
        return self.context

    @Lazy
    def viewAnnotations(self):
        return self.request.annotations.setdefault('loops.view', {})

    @Lazy
    def node(self):
        return self.viewAnnotations.get('node')

    @Lazy
    def nodeView(self):
        ann = self.request.annotations.get('loops.view', {})
        return self.viewAnnotations.get('nodeView')

    @Lazy
    def params(self):
        result = {}
        paramString = self.request.annotations.get('loops.view', {}).get('params')
        if paramString:
            result = parse_qs(paramString)
            for k, v in result.items():
                if len(v) == 1:
                    v = [x.strip() for x in v[0].split(',')]
                    result[k] = v
        return result

    def setSkin(self, skinName):
        skin = None
        if skinName and IView.providedBy(self.context):
            skin = component.queryUtility(IBrowserSkinType, skinName)
            if skin:
                applySkin(self.request, skin)
        self.skin = skin

    @Lazy
    def modifiedRaw(self):
        d = getattr(self.adapted, 'modified', None)
        if not d:
            dc = IZopeDublinCore(self.context)
            d = dc.modified or dc.created
        if isinstance(d, str):
            d = datetime(*(strptime(d, '%Y-%m-%dT%H:%M')[:6]))
        else:
            d = toLocalTime(d)
        return d

    @Lazy
    def modified(self):
        d = self.modifiedRaw
        return d and d.strftime('%Y-%m-%d %H:%M') or ''

    @Lazy
    def creatorsRaw(self):
        # TODO: use an IAuthorInfo (or similar) adapter
        creators = getattr(self.adapted, 'authors', None) or []
        if not creators:
            cr = IZopeDublinCore(self.context).creators or []
            pau = component.getUtility(IAuthentication)
            for c in cr:
                try:
                    principal = pau.getPrincipal(c)
                    if principal is None:
                        creators.append(c)
                    else:
                        creators.append(principal.title)
                except PrincipalLookupError:
                    creators.append(c)
        return creators

    @Lazy
    def creators(self):
        return ', '.join(self.creatorsRaw)

    @Lazy
    def lastCreator(self):
        return self.creatorsRaw and self.creatorsRaw[-1] or u''

    @Lazy
    def loopsRoot(self):
        return self.context.getLoopsRoot()

    @Lazy
    def conceptManager(self):
        return self.loopsRoot.getConceptManager()

    @Lazy
    def resourceManager(self):
        return self.loopsRoot.getResourceManager()

    @Lazy
    def typePredicate(self):
        return self.conceptManager.getTypePredicate()

    @Lazy
    def defaultPredicate(self):
        return self.conceptManager.getDefaultPredicate()

    @Lazy
    def isPartOfPredicate(self):
        return self.conceptManager.get('ispartof')

    @Lazy
    def queryTargetPredicate(self):
        return self.conceptManager.get('querytarget')

    @Lazy
    def memberPredicate(self):
        return self.conceptManager.get('ismember')

    @Lazy
    def masterPredicate(self):
        return self.conceptManager.get('ismaster')

    @Lazy
    def ownerPredicate(self):
        return self.conceptManager.get('isowner')

    @Lazy
    def personAssignmentPredicates(self):
        return [self.memberPredicate, self.masterPredicate, self.ownerPredicate]

    @Lazy
    def url(self):
        return absoluteURL(self.context, self.request)

    @Lazy
    def rootUrl(self):
        return absoluteURL(self.loopsRoot, self.request)

    @Lazy
    def view(self):
        return self

    @Lazy
    def token(self):
        return self.loopsRoot.getLoopsUri(self.context)

    @Lazy
    def adapted(self):
        return adapted(self.context, self.languageInfo)

    @Lazy
    def baseObject(self):
        return baseObject(self.context)

    @Lazy
    def title(self):
        return self.adapted.title or getName(self.context)

    @Lazy
    def description(self):
        return self.adapted.description

    @Lazy
    def tabTitle(self):
        return u'Info'

    @Lazy
    def additionalInfos(self):
        return []

    @Lazy
    def dublincore(self):
        zdc = IZopeDublinCore(self.context)
        zdc.languageInfo = self.languageInfo
        return zdc

    @Lazy
    def dcTitle(self):
        return self.dublincore.title or self.title

    @Lazy
    def dcDescription(self):
        return self.dublincore.description or u'' #self.description

    @Lazy
    def headTitle(self):
        return self.dcTitle

    @Lazy
    def value(self):
        return self.context

    @Lazy
    def uniqueId(self):
        return util.getUidForObject(self.context)

    @Lazy
    def breadcrumbsTitle(self):
        return self.title

    @Lazy
    def listingTitle(self):
        return self.title

    def getViewForObject(self, obj):
        if obj is not None:
            obj = baseObject(obj)
            basicView = component.getMultiAdapter((obj, self.request))
            if hasattr(basicView, 'view'):
                return basicView.view

    def viewIterator(self, objs):
        request = self.request
        for obj in objs:
            view = self.getViewForObject(obj)
            if view is None:
                view = BaseView(obj, request)
            yield view

    def xx_viewIterator(self,obj):
            view = component.queryMultiAdapter(
                            (o, request), name='index.html')
            #if view is None:
            #    view = component.queryMultiAdapter((o, request), IBrowserView)
            if view is None:
                view = BaseView(o, request)
            if hasattr(view, 'view'):   # use view setting for type
                view = view.view
            yield view

    # type stuff

    @Lazy
    def type(self):
        return IType(self.baseObject)

    @Lazy
    def typeProvider(self):
        return self.type.typeProvider

    @Lazy
    def typeInterface(self):
        return self.type.typeInterface

    @Lazy
    def typeAdapter(self):
        ifc = self.typeInterface
        if ifc is not None:
            return ifc(self.context)

    @Lazy
    def typeTitle(self):
        return self.type.title

    @Lazy
    def longTypeTitle(self):
        ct = getattr(self.context, 'contentType', None)
        if ct:
            ext = mimetypes.extensions.get(ct)
            #ext = mimetypes.guess_extension(ct)
            if ext:
                #return '%s (%s)' % (t, ext.upper())
                return ext.upper()  #.lstrip('.')
        return self.typeTitle

    @Lazy
    def typeUrl(self):
        provider = self.typeProvider
        if provider is not None:
            return absoluteURL(provider, self.request)
        return None

    def renderText(self, text, contentType='text/restructured'):
        text = util.toUnicode(text)
        typeKey = util.renderingFactories.get(contentType, None)
        if typeKey is None:
            if contentType == u'text/html':
                return text
            return u'<pre>%s</pre>' % util.html_quote(text)
        source = removeAllProxies(component.createObject(typeKey, text))
        view = component.getMultiAdapter((source, self.request))
        return view.render()

    def renderDescription(self, text=None):
        if text is None:
            text = self.description
        if text is None:
            return u''
        htmlPattern = re.compile(r'<(.+)>.+</\1>')
        if '<br />' in text or htmlPattern.search(text):
            return text
        return self.renderText(text, 'text/restructured')

    @Lazy
    def renderedDescription(self):
        return self.renderDescription()

    def getObjectForUid(self, uid):
        return util.getObjectForUid(uid)

    def getUidForObject(self, obj):
        return util.getUidForObject(baseObject(obj))

    # type listings

    def listTypes(self, include=None, exclude=None, sortOn='title'):
        types = [dict(token=t.token, title=t.title)
                    for t in ITypeManager(self.context).listTypes(include, exclude)]
        #if sortOn:
        #    types.sort(key=lambda x: x[sortOn])
        return types

    def getTypesVocabulary(self, include=None):
        return util.KeywordVocabulary(self.listTypes(include, ('hidden',)))

    def resourceTypes(self):
        return util.KeywordVocabulary(self.listTypes(('resource',), ('hidden',)))
            #if t.factory == Resource]) # ? if necessary -> type.qualifiers

    def conceptTypes(self):
        return util.KeywordVocabulary(self.listTypes(('concept',), ('hidden',)))

    def parentTypesFromOtherSites(self):
        result = []
        typeNames = self.typeOptions('foreign_parent_types') or []
        for path in self.typeOptions('foreign_parent_sites') or []:
            site = traverse(self.loopsRoot, path, None)
            if site is None:
                continue
            cm = site.getConceptManager()
            for tname in typeNames:
                t = cm.get(tname)
                if t is not None:
                    type = LoopsTypeInfo(t)
                    type.isForeignReference = True
                    result.append(type)
        return result

    def listTypesForSearch(self, include=None, exclude=None, sortOn='title'):
        types = [dict(token=t.tokenForSearch, title=t.title)
                    for t in ITypeManager(self.context).listTypes(include, exclude)]
        if sortOn:
            types.sort(key=lambda x: x[sortOn])
        for t in self.parentTypesFromOtherSites():
            types.append(dict(token=t.tokenForSearch, title=t.title))
        return types

    def typesForSearch(self):
        general = [('loops:resource:*', 'Any Resource'),
                   ('loops:concept:*', 'Any Concept'),]
        return util.KeywordVocabulary(general
                        + self.listTypesForSearch(exclude=('system', 'hidden',))
                        + [('loops:*', 'Any')])

    def conceptTypesForSearch(self):
        general = [('loops:concept:*', 'Any Concept'),]
        return util.KeywordVocabulary(general
                        + self.listTypesForSearch(('concept',),
                                                  ('hidden',),))
                                                  #('system', 'hidden',),))

    def resourceTypesForSearch(self):
        general = [('loops:resource:*', 'Any Resource'),]
        return util.KeywordVocabulary(general
                        + self.listTypesForSearch(('resource',),
                                                  ('system', 'hidden',),))

    def isPartOnlyResource(self, obj):
        if not IResource.providedBy(obj):
            return False
        isPart = False
        for r in obj.getConceptRelations():
            if r.predicate == self.isPartOfPredicate:
                isPart = True
            elif r.predicate != self.typePredicate:
                return False
        return isPart

    # options/settings

    @Lazy
    def options(self):
        if ITypeConcept.providedBy(self.adapted):
            return DummyOptions()
        return component.queryAdapter(self.adapted, IOptions) or DummyOptions()

    @Lazy
    def typeOptions(self):
        if self.typeProvider is None:
            return DummyOptions()
        if getattr(self.adapted, '__is_dummy__', None):
            typeToken = getattr(self, 'typeToken', None)
            if typeToken is not None:
                typeProvider = self.loopsRoot.loopsTraverse(typeToken)
                return IOptions(adapted(typeProvider))
        return IOptions(adapted(self.typeProvider))

    @Lazy
    def globalOptions(self):
        return IOptions(self.loopsRoot)

    def getOptions(self, keys):
        for opt in (self.options, self.typeOptions, self.globalOptions):
            if isinstance(opt, DummyOptions):
                continue
            v = opt
            for key in keys.split('.'):
                if isinstance(v, list):
                    break
                v = getattr(v, key)
            if not isinstance(v, DummyOptions):
                return v

    def getPredicateOptions(self, relation):
        return IOptions(adapted(relation.predicate), None) or DummyOptions()

    # versioning

    @Lazy
    def versionable(self):
        return IVersionable(self.target, None)

    @Lazy
    def useVersioning(self):
        if self.globalOptions('useVersioning'):
            return True
        options = getattr(self.controller, 'options', None)
        if options:
            return 'useVersioning' in options.value

    @Lazy
    def showVersions(self):
        permissions = self.globalOptions('showVersionsPermissions')
        if permissions:
            for p in permissions:
                if checkPermission(p, self.target):
                    return True
            else:
                return False
        return True

    @Lazy
    def versionLevels(self):
        if self.versionable is not None:
            return reversed([dict(token=idx, label=label)
                        for idx, label in enumerate(self.versionable.versionLevels)])
        return []

    @Lazy
    def versionId(self):
        versionable = IVersionable(self.target, None)
        return versionable and versionable.versionId or ''

    @Lazy
    def currentVersionId(self):
        versionable = IVersionable(self.target, None)
        return versionable and versionable.currentVersion.versionId or ''

    @Lazy
    def hasVersions(self):
        versionable = IVersionable(self.target, None)
        return versionable and len(versionable.versions) > 1 or False

    @Lazy
    def versionInfo(self):
        if not self.useVersioning:
            return None
        target = self.target
        if not IResource.providedBy(target):
            # no standard versioning yet for concepts
            return None
        versionable = IVersionable(target, None)
        if versionable is None:
            return ''
        versionId = versionable.versionId
        td = component.getUtility(ITranslationDomain, _._domain)
        current = ((versionable.currentVersion == target)
                   and td.translate(_(u'current'), context=self.request)
                   or u'')
        released = ((versionable.releasedVersion == target)
                    and td.translate(_(u'released'), context=self.request)
                    or u'')
        if not current and not released:
            return versionId
        addInfo = u', '.join(e for e in (current, released) if e)
        return u'%s (%s)' % (versionId, addInfo)

    # states

    @Lazy
    def viewStatesPermission(self):
        opt = self.globalOptions('organize.show_states')
        return opt and opt[0] or 'zope.ManageContent'

    @Lazy
    def states(self):
        return self.getStates()

    @Lazy
    def allStates(self):
        return self.getStates(False)

    def getStates(self, forDisplay=True):
        result = []
        if forDisplay and not checkPermission(self.viewStatesPermission, self.context):
            # do not display state information
            return result
        if IResource.providedBy(self.target):
            statesDefs = (self.globalOptions('organize.stateful.resource') or [])
        else:
            statesDefs = (self.globalOptions('organize.stateful.concept') or [])
        statesDefs += (self.typeOptions('organize.stateful') or [])
        for std in statesDefs:
            stf = component.getAdapter(self.target, IStateful, name=std)
            result.append(stf)
        return result

    def checkState(self):
        if checkPermission('loops.ManageSite', self.context):
            return True
        if not self.allStates:
            return True
        for stf in self.allStates:
            option = self.globalOptions(
                            'organize.stateful.restrict.' + stf.statesDefinition)
            if option:
                return stf.state in option
        return True

    # controlling actions and editing

    @Lazy
    def editable(self):
        return canWriteObject(self.context)

    def getActions(self, category='object', page=None, target=None):
        """ Return a list of actions that provide the view and edit actions
            available for the context object.
        """
        acts = []
        optKey = 'action.' + category
        actNames = (self.options(optKey) or []) + (self.typeOptions(optKey) or [])
        if actNames:
            acts = list(actions.get(category, actNames,
                                    view=self, page=page, target=target))
        if category in self.actions:
            acts.extend(self.actions[category](self, page, target))
        optKey = 'append_action.' + category
        actNames = (self.options(optKey) or []) + (self.typeOptions(optKey) or [])
        if actNames:
            acts.extend(list(actions.get(category, actNames,
                                    view=self, page=page, target=target)))
        return acts


    def getAdditionalActions(self, category='object', page=None, target=None):
        """ Provide additional actions; override by subclass.
        """
        return []

    def getAllowedActions(self, category='object', page=None, target=None):
        result = []
        for act in self.getActions(category, page=page, target=target):
            if act.permission is not None:
                ctx = (target is not None and target.context) or self.context
                if not checkPermission(act.permission, ctx):
                    continue
            result.append(act)
        return result

    @Lazy
    def showObjectActions(self):
        principal = self.request.principal
        if IUnauthenticatedPrincipal.providedBy(principal):
            return False
        perms = self.globalOptions('action.object.permissions')
        if perms:
            for p in perms:
                if checkPermission(p, self.context):
                    return True
            return False
        return True

    def checkAction(self, name, category, target):
        if name in ('create_resource',):
            if target is not None and target.options.showCreateResource:
                return True
            return not self.globalOptions('hideCreateResource')
        return True

    @Lazy
    def canAccessRestricted(self):
        return checkPermission('loops.ViewRestricted', self.context)

    @Lazy
    def canEditRestricted(self):
        return canEditRestricted(self.context)

    def openEditWindow(self, viewName='edit.html'):
        if self.editable:
            if checkPermission('loops.ManageSite', self.context):
                return "openEditWindow('%s/@@%s')" % (self.url, viewName)
        return ''

    @Lazy
    def xeditable(self):
        if self.typeOptions('no_external_edit'):
            return False
        ct = getattr(self.context, 'contentType', '')
        if not ct or ct in ('application/pdf', 'application/x-pdf'):
            return False
        if ct.startswith('text/') and ct != 'text/rtf':
            return checkPermission('loops.ManageSite', self.context)
        return canWriteObject(self.context)

    @Lazy
    def inlineEditingActive(self):
        # this may depend on system and user settings...
        return True

    @Lazy
    def conceptMapEditorUrl(self):
        return (checkPermission('loops.xmlrpc.ManageConcepts', self.context)
                    and self.rootUrl + '/swf.html'
                    or None)

    inlineEditable = False

    # work items
    @Lazy
    def workItems(self):
        return []

    # comments

    @Lazy
    def comments(self):
        return []

    # dojo stuff

    def inlineEdit(self, id):
        self.registerDojo()
        return 'return inlineEdit("%s", "")' % id

    def registerDojo(self):
        if self.controller is None:
            return
        cm = self.controller.macros
        cm.register('js', 'dojo.js', template=dojoMacroTemplate, name='main',
                    position=0,
                    djConfig='parseOnLoad: true, usePlainJson: true, '
                             #'isDebug: true, '
                             'locale: "%s"' % self.languageInfo.language)
        jsCall = ('dojo.require("dojo.parser"); '
                  'dojo.registerModulePath("jocy", "/@@/cybertools.jocy"); '
                  'dojo.require("jocy.data");')
        cm.register('js-execute', 'dojo_registration', jsCall=jsCall)
        cm.register('css', identifier='Lightbox.css', position=0,
                    resourceName='ajax.dojo/dojox/image/resources/Lightbox.css',
                    media='all')
        cm.register('css', identifier='tundra.css', position=0,
                    resourceName='ajax.dojo/dijit/themes/tundra/tundra.css',
                    media='all')
        #cm.register('css', identifier='dojo.css', position=1,
        #            resourceName='ajax.dojo/dojo/resources/dojo.css', media='all')

    def registerDojoDnd(self):
        if self.controller is None:
            return
        self.registerDojo()
        jsCall = 'dojo.require("dojo.dnd.Source")'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoDialog(self):
        self.registerDojo()
        jsCall = 'dojo.require("dijit.Dialog")'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoTooltipDialog(self):
        self.registerDojo()
        jsCall = ('dojo.require("dijit.Dialog");'
                  'dojo.require("dijit.form.Button");')
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoDateWidget(self):
        self.registerDojo()
        jsCall = ('dojo.require("dijit.form.DateTextBox"); '
                  'dojo.require("dijit.form.TimeTextBox");')
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoTextWidget(self):
        self.registerDojo()
        jsCall = 'dojo.require("dijit.form.ValidationTextBox");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoTextarea(self):
        self.registerDojo()
        jsCall = 'dojo.require("dijit.form.SimpleTextarea");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoNumberWidget(self):
        self.registerDojo()
        jsCall = 'dojo.require("dijit.form.NumberTextBox");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoEditor(self):
        self.registerDojo()
        jsCall = 'dojo.require("dijit.Editor");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
        jsCall = 'dojo.require("dijit._editor.plugins.LinkDialog");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)
        jsCall = 'dojo.require("dijit._editor.plugins.ViewSource")'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoLightbox(self):
        self.registerDojo()
        jsCall = 'dojo.require("dojox.image.Lightbox");'
        self.controller.macros.register('js-execute', jsCall, jsCall=jsCall)

    def registerDojoComboBox(self):
        self.registerDojo()
        jsCall = ('dojo.require("dijit.form.ComboBox");')
        self.controller.macros.register('js-execute',
                'dojo.require.ComboBox', jsCall=jsCall)

    def registerDojoFormAll(self):
        self.registerDojo()
        self.registerDojoEditor()
        cm = self.controller.macros
        jsCall = ('dojo.require("dijit.form.Form"); '
                  'dojo.require("dijit.form.DateTextBox"); '
                  'dojo.require("dijit.form.TimeTextBox"); '
                  'dojo.require("dijit.form.SimpleTextarea"); '
                  'dojo.require("dijit.form.FilteringSelect"); '
                  'dojo.require("dijit.layout.BorderContainer"); '
                  'dojo.require("dijit.layout.ContentPane"); '
                  'dojo.require("dojox.data.QueryReadStore"); ')
        cm.register('js-execute', 'dojo.form.all', jsCall=jsCall)

    def registerDojoFormAllGrid(self):
        self.registerDojoFormAll()
        cm = self.controller.macros
        jsCall = ('dojo.require("dijit.layout.TabContainer"); '
                  'dojo.require("dojox.grid.DataGrid"); '
                  'dojo.require("dojo.data.ItemFileWriteStore"); ')
        cm.register('js-execute', 'dojo.form.grid', jsCall=jsCall)
        cm.register('css', identifier='dojox.grid.css', position=0,
                    resourceName='ajax.dojo/dojox/grid/resources/Grid.css', media='all')
        cm.register('css', identifier='dojox.grid_tundra.css', position=0,
                    resourceName='ajax.dojo/dojox/grid/resources/tundraGrid.css',
                    media='all')


class LoggedIn(object):

    messages = dict(success=_(u'You have been logged in.'),
                    nosuccess=_(u'Login not successful.'),
                    error=_(u'Try again later.'))

    def __call__(self):
        code = 'success'
        if IUnauthenticatedPrincipal.providedBy(self.request.principal):
            code = 'nosuccess'
        info = self.request.form.get('message')
        if info == 'error' and code == 'nosuccess':
            code = 'error'
        message = self.messages[code]
        return self.request.response.redirect(self.nextUrl(message, code))

    def nextUrl(self, message, code):
        camefrom = self.request.form.get('camefrom', '').strip('?')
        url = camefrom or self.request.URL[-1]
        params = []
        if '?' in url:
            url, qs = url.split('?', 1)
            params = parse_qsl(qs)
        params = [(k, v) for k, v in params if k != 'loops.messages.top:record']
        params.append(('loops.messages.top:record', message.encode('UTF-8')))
        url = url.encode('utf-8')
        return '%s?%s' % (url, urlencode(params))

# vocabulary stuff

class SimpleTerms(object):
    """ Provide the ITerms interface, e.g. for usage in selection
        lists.
    """

    implements(ITerms)

    def __init__(self, source, request):
        # the source parameter is a list of tuples (token, title).
        self.source = source
        self.terms = dict(source)

    def getTerm(self, value):
        token = value[0]
        title = len(value) > 1 and value[1] or token
        return SimpleTerm(token, token, title)

    def getValue(self, token):
        return (token, self.terms[token])


class LoopsTerms(object):
    """ Provide the ITerms interface, e.g. for usage in selection
        lists.
    """

    implements(ITerms)

    def __init__(self, source, request):
        # the source parameter is a view or adapter of a real context object:
        self.source = source
        self.context = source.context
        self.request = request

    @Lazy
    def loopsRoot(self):
        return self.context.getLoopsRoot()

    def getTerm(self, value):
        #if value is None:
        #    return SimpleTerm(None, '', u'not assigned')
        title = value.title or getName(value)
        token = self.loopsRoot.getLoopsUri(value)
        return SimpleTerm(value, token, title)

    def getValue(self, token):
        return self.loopsRoot.loopsTraverse(token)


class InterfaceTerms(object):
    """ Provide the ITerms interface for source list of interfaces.
    """

    implements(ITerms)

    def __init__(self, source, request):
        self.source = source
        self.request = request

    def getTerm(self, value):
        token = '.'.join((value.__module__, value.__name__))
        return SimpleTerm(value, token, token)

    def getValue(self, token):
        return resolve(token)


