# -*- coding: iso-8859-15 -*-


# Glasnost
# By: Odile Bénassy <obenassy@entrouvert.com>
#     Romain Chantereau <rchantereau@entrouvert.com>
#     Nicolas Clapiès <nclapies@easter-eggs.org>
#     Pierre-Antoine Dejace <padejace@entrouvert.be>
#     Thierry Dulieu <tdulieu@easter-eggs.com>
#     Florent Monnier <monnier@codelutin.com>
#     Cédric Musso <cmusso@easter-eggs.org>
#     Frédéric Péters <fpeters@entrouvert.be>
#     Benjamin Poussin <poussin@codelutin.com>
#     Emmanuel Raviart <eraviart@entrouvert.com>
#     Sébastien Régnier <regnier@codelutin.com>
#     Emmanuel Saracco <esaracco@easter-eggs.com>
#
# Copyright (C) 2000, 2001 Easter-eggs & Emmanuel Raviart
# Copyright (C) 2002 Odile Bénassy, Code Lutin, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Frédéric Péters, Benjamin Poussin, Emmanuel Raviart,
#     Emmanuel Saracco & Théridion
# Copyright (C) 2003 Odile Bénassy, Romain Chantereau, Nicolas Clapiès,
#     Code Lutin, Pierre-Antoine Dejace, Thierry Dulieu, Easter-eggs,
#     Entr'ouvert, Florent Monnier, Cédric Musso, Ouvaton, Frédéric Péters,
#     Benjamin Poussin, Rodolphe Quiédeville, Emmanuel Raviart, Sébastien
#     Régnier, Emmanuel Saracco, Théridion & Vecam
#
# 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.


from __future__ import nested_scopes
    # lambdas are so much fun with nested scopes...


__doc__ = """Glasnost Web API Module"""

__version__ = '$Revision$'[11:-2]


import locale
import imp
import math
import os
import re
import sys
import time

import glasnost.common.context as context
import glasnost.common.faults as faults
import glasnost.common.parsers as parsers
import glasnost.common.tools_new as commonTools
from glasnost.common.translation import languageLabels
import glasnost.common.xhtmlgenerator as X

import glasnost.proxy.GroupsProxy

import tools_new as webTools
import tools


class GlasnostTime:
    def __init__(self, time):
        self._time = time

    def __repr__(self):
        return self.isoDate

    def __cmp__(self, other):
        return cmp(self._time, other._time)
    
    def __getattr__(self, attr):
        formats = {
            'isoDateTime': '%Y-%m-%d %H:%M:%S',
            'isoDate': '%Y-%m-%d',
            'euroDate': '%d-%m-%Y',
            'usDate': '%m-%d-%Y',
            'time': '%H:%M:%S',
            'yearMonth': '%Y-%m'
        }
        if formats.has_key(attr):
            setattr(self, attr, time.strftime(formats[attr],
                                           time.localtime(self._time)))
            return getattr(self, attr)
        if attr == 'euro':
            self.euro = '%s %s' % (self.euroDate, self.time)
            return self.euro
        if attr == 'us':
            self.us = '%s %s' % (self.usDate, self.time)
            return self.us


        raise AttributeError, 'no such attr'


class GlasnostServerRole:
    def __init__(self, web):
        if type(web) is type(''):
            web = tools.getWebForServerRole(web)
        self.web = web
        self.labelPlural = web.getTranslatedObjectsNameCapitalized()
        if hasattr(web, 'objectNameCapitalized'):
            self.labelSingular = web.getTranslatedObjectNameCapitalized()
        else:
            self.labelSingular = self.labelPlural
        self.label = self.labelPlural
        self.url = X.roleUrl(web.serverRole)
        self.urlNew = X.roleUrl(web.serverRole, 'edit')

    def getUrl(self):
        return self.url


class GlasnostObject:
    def __init__(self, id='', object=None):
        if object:
            self.objectId = object.id
            self.resolve(object)
        elif id:
            if id[0] == '/':
                id = context.getVar('dispatcherId') + id
            self.objectId = id
            self.resolved = 0
        else:
            raise "You should give an id or an object!"
        try:
            self.serverRole = commonTools.extractRole(self.objectId)
        except IndexError:
            pass
        except:
            # FIXME: this happens with virtualhosts where self.objectId ==
            # 'glasnost://www.entrouvert.be' (for example)
            self.serverRole = ''

    def __getattr__(self, attr):
        if attr == 'isAccessible':
            self.isAccessible = self._isAccessible()
            return self.isAccessible
        if attr.startswith('__'):
            raise AttributeError, 'No such attribute'
        if attr == 'label':
            if not self.objectId:
                return self.getLabel()
            try:
                self.label = tools.getObjectLabelTranslated(
                        self.objectId,
                        context.getVar('readLanguages'))
            except faults.MissingItem:
                self.label = '[deleted object]'
            return self.label
        if attr.startswith('t8d_'):
            # won't work on list (ie Multi fields) for now
            realAttr = attr[4:]
            fieldValue = getattr(self, realAttr)
            if not self.objectId:
                return fieldValue
            path = 'self.%s' % realAttr
            translationsProxy = tools.getProxyForServerRole('translations')
            if translationsProxy:
                fieldValue, destinationLanguage, state = \
                        translationsProxy.getTranslationInfos(
                            fieldValue, self.objectId, path, self.language,
                            context.getVar('readLanguages'))
            #language = context.getVar('readLanguages')[0]

            setattr(self, attr, fieldValue)
            return getattr(self, attr)
            
        if self.resolved and hasattr(self.object, attr):
            return getattr(self.object, attr)
        if self.resolved:
            raise AttributeError, "I don't have a %s attribute" % attr
        self.resolve()
        if self.__dict__.has_key(attr):
            return self.__dict__[attr]
        return getattr(self.object, attr)

    def _isAccessible(self):
        web = tools.getWeb(self.objectId)
        return web.canGetObject(self.objectId)

    def resolve(self, object = None):
        self.resolved = 1
        if not object:
            web = tools.getWeb(self.objectId)
            self.object = web.getObject(self.objectId)
        else:
            self.object = object
        for key, val in self.object.__dict__.items():
            # dumb
            setattr(self, key, val)

        if hasattr(self.object, 'getSlotNames'):
            # smart
            for slotName in self.object.getSlotNames():
                if not hasattr(self.object, slotName) \
                   or not getattr(self.object, slotName):
                    continue
                if slotName == 'id':
                    continue
                slot = self.getSlot(slotName)
                kind = slot.getKind()
                if kind.__class__.__name__ == 'Id':
                    setattr(self, slotName,
                            GlasnostObject(getattr(self.object, slotName)))
                elif kind.__class__.__name__ == 'Sequence' and \
                        kind.itemKind.__class__.__name__ == 'Id':
                    kind = [GlasnostObject(x)
                            for x in getattr(self.object, slotName)]
                    setattr(self, slotName, kind)
                elif kind.__class__.__name__ in (
                            'Time', 'ModificationTime', 'CreationTime'):
                    kind = GlasnostTime(getattr(self.object, slotName))
                    setattr(self, slotName, kind)

    def __repr__(self):
        return '<GlasnostObject (%s) (resolved: %d)>' % (
            self.objectId, self.resolved)

    def __cmp__(self, other):
        if not self: return -1
        if not other: return 1
        return cmp(self.objectId, other.objectId)
    
    def __str__(self):
        if self.serverRole == 'virtualhosts':
            return self.t8d_title
        return self.label

    def getUrl(self, action='', args=None, absolute=0):
        t = X.idUrl(self.objectId, action)
        if args:
            for k,v in args.items():
                t.add(k,v)
        if absolute:
            return t.getAsAbsoluteUrl()
        else:
            return t.getAsUrl()


def canAddObject(objectType):
    if isinstance(objectType, GlasnostServerRole):
        web = objectType.web
    else:
        web = tools.getWebForServerRole(objectType)
    try:
        return web.canAddObject()
    except: # TODO: tighter check
        return 0


def getAPIDict():
    module = sys.modules[__name__]
    api = {}
    for k,v in module.__dict__.items():
        if k[0] == '_':
            continue
        if type(v) not in (type(getMainRubric), type(GlasnostObject)) :
            continue
        api[k] = v
    api['userToken'] = context.getVar('userToken')
    api['fileName'] = context.getVar('webFileName')
    try:
        api['user'] = GlasnostObject(id = context.getVar(
                'userId', default = ''))
    except: # TODO: tighter check
        api['user'] = None
    virtualHost = context.getVar('virtualHost')
    if virtualHost is not None:
        api['virtualHost'] = GlasnostObject(object = virtualHost)
    else:
        api['virtualHost'] = ''
    api['currentURI'] = context.getVar('req')._req.uri
    api['math'] = math
    api['time'] = time
    api['urlargs'] = UrlArgs(api)
    api['context'] = context
    api['parsers'] = parsers
    api['webTools'] = webTools
    api['X'] = X
    return api


def getGotoObjectsLabelsAndLinks():
    return tools.getGotoObjectsLabelsAndLinks()


def getLastObjects(serverRole, number):
    web = tools.getWebForServerRole(serverRole)
    if not hasattr(web, 'getLastObjects'):
        return []
    userId = context.getVar('userId', default = '')
    if userId:
        userSet = [userId]
    else:
        userSet = None

    return [
        GlasnostObject(object = x)
        for x in web.getLastObjects(number, None, userSet, None)]


def getMainRubric():
    try:
        object = tools.getWebForServerRole('rubrics').getMainObject()
    except: # TODO: should check the exception is MainRubricMissing
        return None
    glasnostObject = GlasnostObject(object=object)
    return glasnostObject


def getMenuIds(roles):
    return X.menuIds(roles).getAsXml()


def getNewObjectLabelsAndLinks():
    return tools.getNewObjectLabelsAndLinks()


cachedPath = {}


def _getPathToObject(object, source):
    if not object:
        return None
    try:
        source.membersSet
    except: # TODO: tighter check
        # this probably means we don't have read access to this source
        return None
    if object == source.contentId:
        return [source]
    if not source.membersSet:
        return None
    if object in source.membersSet:
        return [source]
    for m in source.membersSet:
        if commonTools.extractRole(m.objectId) != 'rubrics':
            # we don't go through getSetContainedIds here
            continue
        t = _getPathToObject(object, m)
        if t:
            return [source] + t
    return None


def getPathToObject(object):
    # this benefits heavily from the cache
    mR = getMainRubric()
    path = _getPathToObject(object, mR)
    if not path:
        if not object or not object.serverRole:
            return []
        path = [ GlasnostServerRole(object.serverRole), ]
    return path


def getTree(source, done = None, level=5):
    if done is None:
        done = []
    if commonTools.extractRole(source.objectId) != 'rubrics' or \
            level == 0 or source in done:
        return (source, [])
    try:
        source.membersSet
    except: # TODO: tighter check
        # this probably means we don't have read access to this source
        return (source, [])
    if not source.membersSet:
        return (source, [])
    done.append(source)
    return (source, [getTree(x, done, level-1) for x in source.membersSet])


def flattenTree(tree, list, indent=0):
    list.append( (indent, tree[0]) )
    for x in tree[1]:
        flattenTree(x, list, indent+1)
    return list


def getServerRoles():
    serverIds = []
    roles = context.getVar('knownRoles')[:]
    for role in roles:
        try:
            web = tools.getWebForServerRole(role)
        except: # TODO: tighter check
            continue
        if not web:
            continue
        if hasattr(web, 'objectsNameCapitalized'):
            s = GlasnostServerRole(web)
            serverIds.append(s)
        
    serverIds.sort(lambda x, y: locale.strcoll(x.label, y.label))
    return serverIds


def getSetContainedObjects(set, serverRoles=[], sortOn=None):
    if not set:
        return []
    set = [ x.objectId for x in set ]
    result = glasnost.proxy.GroupsProxy.getSetContainedIds(set, serverRoles)
    result = [ GlasnostObject(x) for x in result ]
    if len(result) > 1 and sortOn:
        cmpFct = cmp
        if sortOn[0] == '-':
            sortOn = sortOn[1:]
            coeff = -1
        else:
            coeff = 1
        if sortOn == 'label':
            cmpFct = locale.strcoll
        elif hasattr(result[0].object, sortOn + '_kind'):
            kind = getattr(result[0].object, sortOn + '_kind')
            cmpFct = kind.getCmpFunction()
        
        result.sort(lambda x, y: coeff*cmpFct(
            getattr(x, sortOn),
            getattr(y, sortOn)))
    #elif sortOn:
    #    result.sort(lambda x, y: cmp(
    #                                eval(sortOn, {'self': x}),
    #                                eval(sortOn, {'self': y})) )

    return result


def loadExtension(name):
    directoryPaths = context.getVar('talExtensionsDirectoryPaths')
    fileName = name + '.py'
    for directoryPath in directoryPaths:
        filePath = os.path.join(directoryPath, fileName)
        if os.path.exists(filePath):
            break
    else:
        raise Exception('Unable to load extension %s' % filePath)
    return imp.load_source(name, filePath)


def pretion(text):
    pretion = '<hr />' + text + '<hr />'
    hr = re.compile("<hr.*?>", re.I + re.M)
    pretion = hr.split(pretion)
    return pretion[1:-1]
#    pretion = pretion[0] + '<div class="page">' \
#                  + '</div><div class="page">'.join(pretion[1:-1]) \
#                  + '</div>' + pretion[-1]


def sortList(l, sortOn):
    if len(l) == 0:
        return l

    cmpFct = cmp
    if sortOn[0] == '-':
        sortOn = sortOn[1:]
        coeff = -1
    else:
        coeff = 1
    if sortOn == 'label':
        cmpFct = locale.strcoll
    elif hasattr(l[0].object, sortOn + '_kind'):
        kind = getattr(l[0].object, sortOn + '_kind')
        cmpFct = kind.getCmpFunction()
        
    l.sort(lambda x, y: coeff*cmpFct(
            getattr(x, sortOn),
            getattr(y, sortOn)))
    return l


compiledFiles = {}


def template(filename, xtal=0, **keywords):
    from TAL.TALParser import TALParser
    from TAL.HTMLTALParser import HTMLTALParser
    from glasnost.web.GlasnostTALGenerator import GlasnostTALGenerator

    if not os.path.exists(filename):
        fallbackPath = context.getVar(
            'fallbackTemplatesDirectoryPath')
        filename = filename.split('/')[-1]
        if os.path.exists('%s/default/%s' % (fallbackPath, filename)):
            filename = '%s/default/%s' % (fallbackPath, filename)
        else:
            raise Exception('Function template() called for inexistant file')

    if 0 and compiledFiles.has_key(filename):
        t = compiledFiles[filename]
    else:
        if xtal:
            t = TALParser(gen = GlasnostTALGenerator(xml=1))
        else:
            t = HTMLTALParser(gen = GlasnostTALGenerator(xml=0))
        t.parseString(open(filename).read())
        compiledFiles[filename] = t

    program, macros = t.getCode()

    from GlasnostTALInterpreter import GlasnostTALInterpreter
    from GlasnostTALEngine import GlasnostTALEngine
    engine = GlasnostTALEngine(macros)
    #context.setVar('talEngine', engine)
    engine.locals = getAPIDict()
    for k, v in keywords.items():
        engine.locals[k] = v
    stream = tools.StringIO()
    interp = GlasnostTALInterpreter(program, macros, engine, stream=stream, wrap=80)
    interp()
    return stream.getvalue()


def aliasUrl(alias, action = None, **keywords):
    url = X.aliasUrl(alias, action)
    for key, value in keywords.items():
        url.add(key, value)
    return url


def idUrl(id, action = None, **keywords):
    url = X.idUrl(id, action)
    for key, value in keywords.items():
        url.add(key, value)
    return url


def roleUrl(role, action = None, **keywords):
    url = X.roleUrl(role, action)
    for key, value in keywords.items():
        url.add(key, value)
    return url


def rootUrl():
    return X.rootUrl().getAsUrl()


class UrlArgs:
    def __init__(self, dict):
        self.dict = dict

    def hasArg(self, name):
        return self.dict.has_key(name)

    def getArg(self, name, default=None):
        if self.dict.has_key(name):
            return self.dict[name]
        else:
            return default
    
    def __getattr__(self, name):
        return self.dict[name]


def cacheDepends(args):
    ids = []
    for a in args:
        if isinstance(a, GlasnostObject):
            ids.append(a.objectId)
        else:
            ids.append(a)
    req = context.getVar('req')
    req.depends += ids


def exists(varname):
    engine = context.getVar('talEngine')
    if not engine:
        return 0
    return (engine.locals.has_key(varname) or engine.globals.has_key(varname))

def getPossibleLanguagesLabelAndUrl(objectId = None, notSelf = 0):
    translationsProxy = tools.getProxyForServerRole('translations')
    if not objectId:
        objectId = context.getVar('objectId')
    if objectId:
        languages = translationsProxy.getLanguagesForObjectId(objectId)
    else:
        languages = translationsProxy.getPossibleLanguages()
    if notSelf and context.getVar('readLanguages')[0] in languages:
        languages.remove(context.getVar('readLanguages')[0])
    result = []
    cleanedUpUri = tools.cleanUpUnparsedUri([], 'http')
    for l in languages:
        result.append((l, _(languageLabels[l]),
                       X.roleUrl('switchLang').add('lang', l).add(
                               'nextUri', cleanedUpUri)))
    return result

