__title__ = 'slim.models.fields'
__version__ = '0.1'
__build__ = 0x000001
__author__ = 'Artur Barseghyan <artur.barseghyan@gmail.com>'
__all__ = ('LanguageField', 'SimpleLanguageField')

from django.db import models
from django.core import exceptions
from django.utils.translation import ugettext_lazy as _

from slim import get_languages, default_language, get_languages_keys
from slim.monkey_patches import monkeypatch_method, monkeypatch_property
from slim.translations import is_primary_language


class LanguageField(models.CharField):
    """
    LanguageField model. Stores language string in a ``CharField`` field.

    Using `contrib_to_class` melthod adds `translation_of` field, which is simply a ``ForeignKey``
    to the same class.
    """
    __metaclass__ = models.SubfieldBase

    def __init__(self, *args, **kwargs):
        """
        Create new field. Argument ``populate`` will be sent as-is to the form field.
        """
        defaults = {
            'verbose_name': _('Language'),
            'populate': None,
            'max_length': 10,
            'choices': get_languages(),
            'default': default_language
        }
        defaults.update(kwargs)
        self.populate = defaults.pop('populate', None)
        super(LanguageField, self).__init__(*args, **defaults)

    def contribute_to_class(self, cls, name):
        """
        Language field consists of more than one database record. We have ``lanaguage`` (CharField)
        and ``translation_of`` (ForeignKey to ``cls``) in order to identify translated and
        primary objects.

        We have a set of very useful methods implemented in order to get translations easily.
        """
        self.name = name
        self.translation_of = models.ForeignKey(cls, blank=True, null=True, verbose_name=_('Translation of'), \
                                                related_name='translations', \
                                                limit_choices_to={'language': default_language}, \
                                                help_text=_('Leave this empty for entries in the primary language.'))
        cls.add_to_class('translation_of', self.translation_of)
        super(LanguageField, self).contribute_to_class(cls, name)

        @monkeypatch_property(cls)
        def is_multilingual(self):
            """
            Simple flat to use on objects to find our wheither they are multilinugal
            or not

            :return bool: Always returns boolean True
            """
            return True

        @monkeypatch_method(cls)
        def get_redirect_to_target(self, request):
            """
            Find an acceptable redirect target. If this is a local link, then try
            to find the page this redirect references and translate it according
            to the user's language. This way, one can easily implement a localized
            "/"-url to welcome page redirection.
            """
            target = self.redirect_to
            if target and target.find('//') == -1: # Not an offsite link http://bla/blubb
                try:
                    page = cls.objects.page_for_path(target)
                    page = page.get_translation(getattr(request, 'LANGUAGE_CODE', None))
                    target = page.get_absolute_url()
                except cls.DoesNotExist:
                    pass
            return target

        @monkeypatch_method(cls)
        def available_translations(self):
            """
            Returns available translations.

            :return interable: At this moment a list of objects.
            """
            if not self.id: # New, unsaved pages have no translations
                return []
            if is_primary_language(self.language):
                return self.translations.all()
            elif self.translation_of:
                return [self.translation_of] + list(self.translation_of.translations.exclude(
                    language=self.language))
            else:
                return []

        @monkeypatch_method(cls)
        def get_original_translation(self, *args, **kwargs):
            """
            Gets original translation of current object.

            :return obj: Object of the same class as the one queried.
            """
            if is_primary_language(self.language):
                return self
            return self.translation_of

        @monkeypatch_method(cls)
        def translation_admin(self, *args, **kwargs):
            """
            Gets a HTML with URL to the original translation of available. For admin use.

            :return str:
            """
            if self.translation_of:
                url = u'/admin/' + self._meta.app_label + '/' + self._meta.module_name + '/' + \
                      str(self.translation_of.id) + '/'
                name = unicode(self.translation_of)

                return u'<a href="%(url)s">%(name)s</a>' % {'url': url, 'name': name}
            return ''
        translation_admin.allow_tags = True
        translation_admin.short_description = _('Translation of')

        @monkeypatch_method(cls)
        def _available_translations_admin(self, include_self=True):
            """
            Gets a HTML with all available translation URLs for current object if available. For admin use.

            :return str:
            """
            try:
                original_translation = self.original_translation
                available_translations = list(self.available_translations())
                languages_keys = get_languages_keys()
                languages = dict(get_languages())

                if include_self:
                    available_translations.append(self)

                output = []
                # Processing all available translations. Adding edit links.
                if available_translations:
                    for translation in available_translations:
                        url = u'/admin/' + translation._meta.app_label + '/' + translation._meta.module_name + \
                              '/' + str(translation.id) + '/'
                        name = unicode(languages[translation.language])
                        output.append(u'<a href="%(url)s">%(name)s</a>' % {'url': url, 'name': name})
                        languages_keys.remove(translation.language)

                if self.pk and self.language in languages_keys:
                    languages_keys.remove(self.language)

                # For all languages that are still available (original object has no translations for)
                for language in languages_keys:
                    url = u'/admin/' + self._meta.app_label + '/' + self._meta.module_name + \
                          '/add/?translation_of=' + str(original_translation.id) + '&amp;language=' + language
                    name = unicode(languages[language])
                    output.append(u'<a href="%(url)s" style="color:#baa">%(name)s</a>' % {'url': url, 'name': name})
                return u' | '.join(output)
            except Exception, e:
                return u''

        @monkeypatch_method(cls)
        def available_translations_admin(self, *args, **kwargs):
            """
            Gets a HTML with all available translation URLs for current object if available. For admin use.

            :return str:
            """
            return self._available_translations_admin(include_self=True, *args, **kwargs)
        available_translations_admin.allow_tags = True
        available_translations_admin.short_description = _('Translations')

        @monkeypatch_method(cls)
        def available_translations_exclude_current_admin(self, *args, **kwargs):
            """
            Same as `available_translations_admin` but does not include itself to the list.

            :return str:
            """
            return self._available_translations_admin(include_self=False, *args, **kwargs)
        available_translations_exclude_current_admin.allow_tags = True
        available_translations_exclude_current_admin.short_description = _('Translations')

        @monkeypatch_property(cls)
        def original_translation(self):
            """
            Property for ``get_original_translation`` method.

            :return obj: Object of the same class as the one queried.
            """
            return self.get_original_translation()

        @monkeypatch_method(cls)
        def get_translation_for(self, language):
            """
            Get translation article in given language.

            :param str language: Which shall be one of the languages specified in ``LANGUAGES``
                in `settings.py`.
            :return obj: Either object of the same class as or None if no translations are
                available for the given ``language``.
            """
            if not language in get_languages_keys():
                return None
            if str(self.language) == str(language):
                return self
            if str(self.original_translation.language) == str(language):
                return self.original_translation
            try:
                return self.original_translation.translations.get(language=language)
            except Exception, e:
                return None

    def formfield(self, **kwargs):
        """
        Returns best form field to represent this model field
        """
        defaults = {
            'form_class': LanguageField,
            'populate': self.populate,
        }
        defaults.update(kwargs)
        return super(models.CharField, self).formfield(**defaults)

    def validate(self, value, model_instance):
        """
        Validating the field.

        We shall make sure that there are double translations for the same language for the same object. That's
        why, in case if model is not yet saved (``translated_object`` does not yet have a primary key), we check
        if there are already translations of the same object in the language we specify now.

        Otherwise, if ``model_instance`` already has a primary key, we anyway try to get a ``translated_object``
        and compare it with our ``model_instance``. In case if ``translated_object`` exists and not equal to our
        ``model_instance`` we raise an error.

        NOTE: This has nothing to do with unique fields in the original ``model_instance``. Make sure you have
        properly specified all unique attributes with respect to ``LanguageField` of your original
        ``model_instance`` if you need those records to be unique.
        """
        if model_instance.original_translation:
            translated_object = model_instance.original_translation.get_translation_for(value)
        else:
            translated_object = None
        if not model_instance.pk:
            if translated_object and translated_object.pk:
                raise exceptions.ValidationError("Translation in language %s for this object already exists." % value )
        else:
            if translated_object and translated_object.pk and model_instance != translated_object:
                raise exceptions.ValidationError("Translation in language %s for this object already exists." % value )
        super(LanguageField, self).validate(value, model_instance)

class SimpleLanguageField(models.CharField):
    """
    SimpleLanguageField model. Stores language string in a ``CharField`` field.
    """

    def __init__(self, *args, **kwargs):
        """
        Create new field. Argument ``populate`` will be sent as-is to the form field.
        """
        defaults = {
            'verbose_name': _('Language'),
            'populate': None,
            'max_length': 10,
            'choices': get_languages(),
            'default': default_language
        }
        defaults.update(kwargs)
        self.populate = defaults.pop('populate', None)
        super(SimpleLanguageField, self).__init__(*args, **defaults)

    def formfield(self, **kwargs):
        """
        Returns best form field to represent this model field
        """
        defaults = {
            'form_class': SimpleLanguageField,
            'populate': self.populate,
        }
        defaults.update(kwargs)
        return super(models.CharField, self).formfield(**defaults)

# Add schema's for South
try:
    from south.modelsinspector import add_introspection_rules
    add_introspection_rules(
        rules=[((LanguageField,), [], {'populate': ('populate', {'default':None}),})], \
        patterns=['slim.models\.fields\.LanguageField', 'slim.models\.fields\.SimpleLanguageField']
        )
except ImportError:
    pass
