# Copyright (c) 2008 gocept gmbh & co. kg
# See also LICENSE.txt
# $Id: translation.py 11046 2008-04-10 14:49:57Z ctheune $

import persistent.dict
import zope.interface
import zope.schema
import zope.component
import zope.annotation.interfaces

import gocept.schematranslation.interfaces


class Translation(object):
    """Provide translation of objects with schemas.
    """

    zope.interface.implements(
        gocept.schematranslation.interfaces.ITranslation)

    def __init__(self):
        self.translation_iface_cache = {}

    def translate(self, obj, schema, lang=None, translation_context=None):
        """Return an object that provides the schema and has translated
        attribute values.
        """
        lang = self._get_language(lang, translation_context)
        if lang is None:
            return obj
        clsid = schema.getTaggedValue('clsid')

        translation = get_annotation(
            obj, ['gocept.schematranslation', clsid, lang])

        return TranslatedObject(obj, schema, translation)

    def interface(self, schema, lang=None, translation_context=None):
        """Return a schema that can adapt objects to a version translated into
        a specific language.
        """
        lang = self._get_language(lang, translation_context)
        if lang is None:
            return schema
        key = schema.getTaggedValue('clsid') + lang.upper()
        iface = self.translation_iface_cache.get(key)
        if iface is not None:
            return iface

        name = schema.__name__ + lang.upper()
        module = __name__ + '.__generated__.' + schema.__module__
        fields = {}
        for field_name in zope.schema.getFieldNames(schema):
            fields[field_name] = clone_field(schema[field_name])
        iface = zope.interface.interface.InterfaceClass(
            name, (schema,), __module__=module, attrs=fields)
        self.translation_iface_cache[key] = iface

        def adapt(obj):
            # We don't pass in the context, because the language was
            # determined when creating the interface.
            translated = self.translate(obj, schema, lang)
            zope.interface.alsoProvides(translated, iface)
            return translated

        zope.component.provideAdapter(factory=adapt,
                                      adapts=(schema,),
                                      provides=iface)

        return iface

    def _get_language(self, lang, translation_context):
        if (lang is not None and translation_context is not None):
            raise TypeError(
                "Only one of lang or translation_context can be given.")
        if translation_context:
            preferences = zope.i18n.interfaces.IUserPreferredLanguages(
                translation_context)
            langs = preferences.getPreferredLanguages()
            if langs:
                lang = langs[0]
        return lang


class TranslatedObject(object):
    """Wrapper.
    """

    def __init__(self, original, schema, translation):
        self._original = original
        self._schema = schema
        self._translation = translation
        zope.interface.alsoProvides(self, schema)

    def __getattr__(self, name):
        if name not in zope.schema.getFieldNames(self._schema):
            raise AttributeError

        if name in self._translation:
            return self._translation[name]

        return getattr(self._original, name)

    def __setattr__(self, name, value):
        if name.startswith('_'):
            super(TranslatedObject, self).__setattr__(name, value)
            return

        if name not in zope.schema.getFieldNames(self._schema):
            raise AttributeError

        self._translation[name] = value


def get_annotation(obj, path):
    """Traverse the annotations of obj along a given path.

    Creates persistent dicts where a path element does not exist.

    """
    data = zope.annotation.interfaces.IAnnotations(obj)
    for key in path:
        if key not in data:
            data[key] = persistent.dict.PersistentDict()
        data = data.get(key)
    return data


def clone_field(field):
    clone = field.__class__.__new__(field.__class__)
    clone.__dict__.update(field.__dict__)
    return clone

