from django.contrib.admin import models
from django.contrib.contenttypes.models import ContentType
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
from django.utils.text import get_text_list

def get_change_message(fields):
    """
    Create a change message for *fields* (a sequence of field names).
    """
    return _('Changed %s.') % get_text_list(fields, _('and'))

def addition(request, object):
    """
    Log that an object has been successfully added.
    """
    models.LogEntry.objects.log_action(
        user_id=request.user.pk,
        content_type_id=ContentType.objects.get_for_model(object).pk,
        object_id=object.pk,
        object_repr=force_unicode(object),
        action_flag=models.ADDITION
    )

def change(request, object, message_or_fields):
    """
    Log that an object has been successfully changed.

    The argument *message_or_fields* must be a sequence of modified field names 
    or a custom change message.
    """
    if isinstance(message_or_fields, basestring):
        message = message_or_fields
    else:
        message = get_change_message(message_or_fields)
    models.LogEntry.objects.log_action(
        user_id=request.user.pk,
        content_type_id=ContentType.objects.get_for_model(object).pk,
        object_id=object.pk,
        object_repr=force_unicode(object),
        action_flag=models.CHANGE,
        change_message=message
    )

def deletion(request, object, object_repr=None):
    """
    Log that an object will be deleted.
    """
    models.LogEntry.objects.log_action(
        user_id=request.user.id,
        content_type_id=ContentType.objects.get_for_model(object).pk,
        object_id=object.pk,
        object_repr=object_repr or force_unicode(object),
        action_flag=models.DELETION
    )

def in_bulk(request, added, changed, deleted):
    """
    Log all *added*, *changed* and *deleted* instances.
    
    Note that, while *added* and *deleted* are sequences of instances,
    *changed* must be a sequence of tuples *(instance, message_or_fields)*,
    where *message_or_fields* is a sequence of modified field names 
    or a custom change message.
    """
    for instance in added:
        addition(request, instance)
    for instance, fields in changed:
        if fields:
            change(request, instance, fields)
    for instance in deleted:
        deletion(request, instance)


class AdminLogMixin(object):
    """
    Class based views mixin that adds simple wrappers to 
    the three functions above.
    """
    def log_addition(self, instance):
        """
        Log that an object has been successfully added.   
        """
        addition(self.request, instance)

    def log_change(self, instance, message_or_fields):
        """
        Log that an object has been successfully changed.
        """
        change(self.request, instance, message_or_fields)

    def log_deletion(self, instance, instance_repr=None):
        """
        Log that an object will be deleted.
        """
        deletion(self.request, instance, instance_repr)

    def logall(self, added, changed, deleted):
        in_bulk(self.request, added, changed, deleted)


class AdminLogger(AdminLogMixin):
    """
    A more generic Python object that can be used as a logger 
    taking the request in the constructor.
    """
    def __init__(self, request):
        self.request = request


class AdminLogCollector(object):
    """
    A class to collect logs that will be reported later.

    It can be useful, for example, when you need to add log entries
    in forms (e.g. in a custom admin page) without the need to pass a
    request argument::

        class MyForm(forms.Form):
            def __init__(self, *args, **kwargs):
                super(MyForm, self).__init__(*args, **kwargs)
                self.collector = AdminLogCollector()

            def save(self):
                ... add some instance
                self.collector.added(instance)
            
    If you have a formset of forms like the above, it is easy to
    collect all logs::

        class MyBaseFormSet(BaseFormSet):
            def save(self):
                collectors = []
                for form in self.forms:
                    form.save()
                    collectors.append(form.collector)
                # collect changes for all forms
                self.collector = sum(collectors, AdminLogCollector())
                # return the number of forms that did something
                return len(filter(None, collectors))

    In the view you can actually save all collected log entries::

        formset.collector.logall(request)
    """
    def __init__(self, added=None, changed=None, deleted=None, logger=None):
        self._added = set() if added is None else set(added)
        self._changed = set() if changed is None else set(changed)
        self._deleted = set() if deleted is None else set(deleted)
        self._logger = logger or in_bulk
        self._done = False

    def __add__(self, other):
        added, changed, deleted = other.get_collected()
        return self.__class__(
            self._added.union(added),
            self._changed.union(changed), 
            self._deleted.union(deleted),
            logger=self._logger
        )

    def __repr__(self):
        return repr(self.get_collected())

    def __nonzero__(self):
        return any(self.get_collected())
        
    def added(self, instance):
        """
        Collect an addition log.
        """
        self._added.add(instance)

    def changed(self, instance, message_or_fields):
        """
        Collect a change log.
        """
        if not isinstance(message_or_fields, basestring):
            message_or_fields = tuple(message_or_fields)
        self._changed.add((instance, message_or_fields))

    def deleted(self, instance):
        """
        Collect a deletion log.
        """
        self._deleted.add(instance)

    def get_collected(self):
        """
        Return a tuple *(additions, changes, deletions)*
        representing all the collected logs.
        """
        return self._added, self._changed, self._deleted
    
    def logall(self, request, redo=False):
        """
        Actually save all log entries using the given *request*.
        """
        if redo or not self._done:
            self._logger(request, self._added, self._changed, self._deleted)
            self._done = True