"""
$Id: Negotiator.py 21785 2006-04-04 20:34:35Z hannosch $
"""

import types

_langPrefsRegistry = {}

def getAcceptedHelper(self, request, kind='language'):
    """this is patched on prefs classes which don't define the getAccepted
    classes but define the deprecated getPreferredLanguages method"""
    return self.getPreferredLanguages()

def registerLangPrefsMethod(prefs, kind='language'):
    # check for correct format of prefs
    if type(prefs) is not type({}): prefs = {'klass':prefs,'priority':0}
    # add chain for kind
    if not _langPrefsRegistry.has_key(kind): _langPrefsRegistry[kind]=[]
    # backwards compatibilty monkey patch
    if not hasattr(prefs['klass'], 'getAccepted'): prefs['klass'].getAccepted = getAcceptedHelper
    # add this pref helper
    _langPrefsRegistry[kind].append(prefs)
    # sort by priority
    _langPrefsRegistry[kind].sort(lambda x, y: cmp(y['priority'], x['priority']))

def getLangPrefs(env, kind='language'):
    """get higest prio method for kind"""
    for pref in  _langPrefsRegistry[kind]:
        handler = pref['klass'](env)
        accepted = handler.getAccepted(env, kind)
        if accepted:
            return accepted
    return ()

def lang_normalize(lang):
    """filter"""
    return lang.replace('_', '-')

def str_lower(aString):
    """filter"""
    return aString.lower()

def str_strip(aString):
    """filter"""
    return aString.strip()

def type_accepted(available, preferred):
    # ex: preferred is text/* and available is text/html
    av = available.split('/')
    pr = preferred.split('/')
    if len(av) < 2 or len(pr) < 2:
        return False
    return pr[1] == '*' and pr[0] == av[0]

def lang_accepted(available, preferred):
    # ex: available is pt, preferred is pt-br
    return available.startswith(preferred)

def _false(*a, **kw):
    pass


class BrowserAccept:

    filters = {
        'content-type': (str_lower,),
        'language': (str_lower, lang_normalize, str_strip),
    }

    def __init__(self, request):
        pass

    def getAccepted(self, request, kind='content-type'):
        get = request.get
        custom_name = ('user_%s' % kind).lower()
        if kind == 'content-type':
            header_name = ('HTTP_ACCEPT').upper()
        else:
            header_name = ('HTTP_ACCEPT_%s' % kind).upper()

        try:
            user_accepts = get(custom_name, '')
            http_accepts = get(header_name, '')
        except:
            from traceback import print_exc
            print_exc()
            return
        if user_accepts and http_accepts and user_accepts == request.cookies.get('custom_name'):
            user_accepts = [a.strip() for a in user_accepts.split(',')]
            http_accepts = [a.strip() for a in http_accepts.split(',')]
            for l in user_accepts:
                if l not in http_accepts:
                    req_accepts = user_accepts + http_accepts
                    break
                else:
                    # user_accepts is a subset of http_accepts
                    request.RESPONSE.expireCookie('custom_name', path='/')
                    req_accepts = http_accepts
        else:
            req_accepts = (user_accepts +','+ http_accepts).split(',')

        accepts = []
        i=0
        length=len(req_accepts)
        filters = self.filters.get(kind, ())

        # parse quality strings and build a tuple like
        # ((float(quality), lang), (float(quality), lang))
        # which is sorted afterwards if no quality string is given then the
        # list order is used as quality indicator
        for accept in req_accepts:
            for normalizer in filters:
                accept = normalizer(accept)
            if accept:
                l = accept.split(';', 2)
                quality = []

                if len(l) == 2:
                    try:
                        q = l[1]
                        if q.startswith('q='):
                            q = q.split('=', 2)[1]
                            quality = float(q)
                    except:
                        pass

                if quality == []:
                    quality = float(length-i)

                accepts.append((quality, l[0]))
                i += 1

        # sort and reverse it
        accepts.sort()
        accepts.reverse()

        return [accept[1] for accept in accepts]


class CookieAccept:
    filters = (str_lower, lang_normalize, str_strip)

    def __init__(self, request):
        pass

    def getAccepted(self, request, kind='language'):
        if not hasattr(request, 'cookies'):
            return ()
        language = request.cookies.get('pts_language', None)
        if language:
            if type(language) is types.TupleType:
                return language
            else:
                #filter
                for filter in self.filters:
                    language = filter(language)
                return (language,)
        else:
            return ()

def setCookieLanguage(request, lang, REQUEST=None):
    """sets the language to a cookie

    request - the request object
    lang - language as string like de or pt_BR (it's normalizd)
    """
    if type(lang) is types.TupleType:
        lang = lang[1]
    lang = str_lower(lang_normalize(lang))
    request.RESPONSE.setCookie('pts_language', lang)
    if REQUEST:
        REQUEST.RESPONSE.redirect(REQUEST.URL0)
    else:
        return lang


class RequestGetAccept:
    filters = (str_lower, lang_normalize, str_strip)

    def __init__(self, request):
        pass

    def getAccepted(self, request, kind='language'):
        # get
        form = request.form
        language=form.get('language', None)
        setLanguage=form.get('setlanguage', None)

        if language:
            #filter
            for filter in self.filters:
                language = filter(language)
            try:
                if setLanguage == 1 or setLanguage.lower() in ('1','true', 'yes'):
                    setLanguage = True
                else:
                    setLanguage = False
            except (ValueError, AttributeError), msg:
                setLanguage = False
            if setLanguage:
                setCookieLanguage(request, language)
            return (language,)
        else:
            return ()


# higher number = higher priority
# if a acceptor returns a false value (() or None) then the next acceptor
# in the chain is queried
registerLangPrefsMethod({'klass':BrowserAccept,   'priority':10 }, 'language')
registerLangPrefsMethod({'klass':CookieAccept,   'priority':40 }, 'language')
registerLangPrefsMethod({'klass':RequestGetAccept,'priority':50 }, 'language')

registerLangPrefsMethod({'klass':BrowserAccept,'priority':10 }, 'content-type')


class Negotiator:

    tests = {
        'content-type': type_accepted,
        'language': lang_accepted,
    }

    def negotiate(self, choices, request, kind='content-type'):
        choices = tuple(choices)
        cache_name = '_pts_negotiator_cache_%s' % kind
        try:
            cache = request.other[cache_name]
        except KeyError:
            # Store cache in request object
            cache = {}
            request.set(cache_name, cache)
        try:
            return cache[choices]
        except KeyError:
            cache[choices] = self._negotiate(choices, request, kind)
        return cache[choices]

    def _negotiate(self, choices, request, kind):

        userchoices = getLangPrefs(request, kind)
        # Prioritize on the user preferred choices. Return the first user
        # preferred choice that the object has available.
        test = self.tests.get(kind, _false)
        for choice in userchoices:
            if choice in choices:
                return choice
            for l_avail in choices:
                if test(l_avail, choice):
                    return l_avail
        return None

    # backwards compatibility... should be deprecated
    def getLanguage(self, langs, request):
        return self.negotiate(langs, request, 'language')

    def getLanguages(self, request):
        return getLangPrefs(request, 'language')


negotiator = Negotiator()

def negotiate(langs, request):
    return negotiator.negotiate(langs, request, 'language')
