"""Translation/Localization functions.

Provides :mod:`gettext` translation functions via an app's
``pylons.translator`` and get/set_lang for changing the language
translated to.

"""
import os
from gettext import NullTranslations, translation

import pylons

__all__ = ['_', 'add_fallback', 'get_lang', 'gettext', 'gettext_noop',
           'lazy_gettext', 'lazy_ngettext', 'lazy_ugettext', 'lazy_ungettext',
           'ngettext', 'set_lang', 'ugettext', 'ungettext', 'LanguageError',
           'N_']


class LanguageError(Exception):
    """Exception raised when a problem occurs with changing languages"""
    pass


class LazyString(object):
    """Has a number of lazily evaluated functions replicating a
    string. Just override the eval() method to produce the actual value.

    This method copied from TurboGears.

    """
    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    def eval(self):
        return self.func(*self.args, **self.kwargs)

    def __unicode__(self):
        return unicode(self.eval())

    def __str__(self):
        return str(self.eval())

    def __mod__(self, other):
        return self.eval() % other

    def format(self, *args):
        return self.eval().format(*args)


def lazify(func):
    """Decorator to return a lazy-evaluated version of the original"""
    def newfunc(*args, **kwargs):
        return LazyString(func, *args, **kwargs)
    newfunc.__name__ = 'lazy_%s' % func.__name__
    newfunc.__doc__ = 'Lazy-evaluated version of the %s function\n\n%s' % \
        (func.__name__, func.__doc__)
    return newfunc


def gettext_noop(value):
    """Mark a string for translation without translating it. Returns
    value.

    Used for global strings, e.g.::

        foo = N_('Hello')

        class Bar:
            def __init__(self):
                self.local_foo = _(foo)

        h.set_lang('fr')
        assert Bar().local_foo == 'Bonjour'
        h.set_lang('es')
        assert Bar().local_foo == 'Hola'
        assert foo == 'Hello'

    """
    return value
N_ = gettext_noop


def gettext(value):
    """Mark a string for translation. Returns the localized string of
    value.

    Mark a string to be localized as follows::

        gettext('This should be in lots of languages')

    """
    return pylons.translator.gettext(value)
lazy_gettext = lazify(gettext)


def ugettext(value):
    """Mark a string for translation. Returns the localized unicode
    string of value.

    Mark a string to be localized as follows::

        _('This should be in lots of languages')

    """
    return pylons.translator.ugettext(value)
_ = ugettext
lazy_ugettext = lazify(ugettext)


def ngettext(singular, plural, n):
    """Mark a string for translation. Returns the localized string of
    the pluralized value.

    This does a plural-forms lookup of a message id. ``singular`` is
    used as the message id for purposes of lookup in the catalog, while
    ``n`` is used to determine which plural form to use. The returned
    message is a string.

    Mark a string to be localized as follows::

        ngettext('There is %(num)d file here', 'There are %(num)d files here',
                 n) % {'num': n}

    """
    return pylons.translator.ngettext(singular, plural, n)
lazy_ngettext = lazify(ngettext)


def ungettext(singular, plural, n):
    """Mark a string for translation. Returns the localized unicode
    string of the pluralized value.

    This does a plural-forms lookup of a message id. ``singular`` is
    used as the message id for purposes of lookup in the catalog, while
    ``n`` is used to determine which plural form to use. The returned
    message is a Unicode string.

    Mark a string to be localized as follows::

        ungettext('There is %(num)d file here', 'There are %(num)d files here',
                  n) % {'num': n}

    """
    return pylons.translator.ungettext(singular, plural, n)
lazy_ungettext = lazify(ungettext)


def _get_translator(lang, **kwargs):
    """Utility method to get a valid translator object from a language
    name"""
    if not lang:
        return NullTranslations()
    if 'pylons_config' in kwargs:
        conf = kwargs.pop('pylons_config')
    else:
        conf = pylons.config.current_conf()
    localedir = os.path.join(conf['pylons.paths']['root'], 'i18n')
    if not isinstance(lang, list):
        lang = [lang]
    try:
        translator = translation(conf['pylons.package'], localedir,
                                 languages=lang, **kwargs)
    except IOError, ioe:
        raise LanguageError('IOError: %s' % ioe)
    translator.pylons_lang = lang
    return translator


def set_lang(lang, set_environ=True, **kwargs):
    """Set the current language used for translations.

    ``lang`` should be a string or a list of strings. If a list of
    strings, the first language is set as the main and the subsequent
    languages are added as fallbacks.
    """
    translator = _get_translator(lang, **kwargs)
    if not set_environ:
        return translator
    environ = pylons.request.environ
    environ['pylons.pylons'].translator = translator
    if 'paste.registry' in environ:
        environ['paste.registry'].replace(pylons.translator, translator)


def get_lang():
    """Return the current i18n language used"""
    return getattr(pylons.translator, 'pylons_lang', None)


def add_fallback(lang, **kwargs):
    """Add a fallback language from which words not matched in other
    languages will be translated to.

    This fallback will be associated with the currently selected
    language -- that is, resetting the language via set_lang() resets
    the current fallbacks.

    This function can be called multiple times to add multiple
    fallbacks.
    """
    return pylons.translator.add_fallback(_get_translator(lang, **kwargs))
