from django import forms, template
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext, ugettext_lazy as _
from django.shortcuts import render_to_response
from django.utils.encoding import force_unicode
from django.utils.safestring import mark_safe
from django.forms.formsets import all_valid
from django.contrib import admin
from django.contrib.admin import helpers
from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied

try:
    from django.contrib.admin import actions
except ImportError:
    actions = False

from authority.models import Permission
from authority.widgets import GenericForeignKeyRawIdWidget
from authority import get_choices_for

class PermissionInline(generic.GenericTabularInline):
    model = Permission
    raw_id_fields = ('user', 'group', 'creator')
    extra = 1

    def formfield_for_dbfield(self, db_field, **kwargs):
        if db_field.name == 'codename':
            perm_choices = get_choices_for(self.parent_model)
            kwargs['label'] = _('permission')
            kwargs['widget'] = forms.Select(choices=perm_choices)
            return db_field.formfield(**kwargs)
        return super(PermissionInline, self).formfield_for_dbfield(db_field, **kwargs)

class ActionPermissionInline(PermissionInline):
    raw_id_fields = ()
    template = 'admin/edit_inline/action_tabular.html'

class ActionErrorList(forms.util.ErrorList):
    def __init__(self, inline_formsets):
        for inline_formset in inline_formsets:
            self.extend(inline_formset.non_form_errors())
            for errors_in_inline_form in inline_formset.errors:
                self.extend(errors_in_inline_form.values())

def edit_permissions(modeladmin, request, queryset):
    opts = modeladmin.model._meta
    app_label = opts.app_label

    # Check that the user has the permission to edit permissions
    if not (request.user.is_superuser or
            request.user.has_perm('authority.change_permission') or
            request.user.has_perm('authority.change_foreign_permissions')):
        raise PermissionDenied

    inline = ActionPermissionInline(queryset.model, modeladmin.admin_site)
    formsets = []
    for obj in queryset:
        prefixes = {}
        FormSet = inline.get_formset(request, obj)
        prefix = "%s-%s" % (FormSet.get_default_prefix(), obj.pk)
        prefixes[prefix] = prefixes.get(prefix, 0) + 1
        if prefixes[prefix] != 1:
            prefix = "%s-%s-%s" % (prefix, prefixes[prefix])
        if request.POST.get('post'):
            formset = FormSet(data=request.POST, files=request.FILES,
                              instance=obj, prefix=prefix)
        else:
            formset = FormSet(instance=obj, prefix=prefix)
        formsets.append(formset)

    media = modeladmin.media
    inline_admin_formsets = []
    for formset in formsets:
        fieldsets = list(inline.get_fieldsets(request))
        inline_admin_formset = helpers.InlineAdminFormSet(inline, formset, fieldsets)
        inline_admin_formsets.append(inline_admin_formset)
        media = media + inline_admin_formset.media

    ordered_objects = opts.get_ordered_objects()
    if request.POST.get('post'):
        if all_valid(formsets):
            for formset in formsets:
                formset.save()
        # redirect to full request path to make sure we keep filter
        return HttpResponseRedirect(request.get_full_path())

    context = {
        'errors': ActionErrorList(formsets),
        'title': ugettext('Permissions for %s') % force_unicode(opts.verbose_name_plural),
        'inline_admin_formsets': inline_admin_formsets,
        'root_path': modeladmin.admin_site.root_path,
        'app_label': app_label,
        'change': True,
        'ordered_objects': ordered_objects,
        'form_url': mark_safe(''),
        'opts': opts,
        'target_opts': queryset.model._meta,
        'content_type_id': ContentType.objects.get_for_model(queryset.model).id,
        'save_as': False,
        'save_on_top': False,
        'is_popup': False,
        'media': mark_safe(media),
        'show_delete': False,
        'action_checkbox_name': helpers.ACTION_CHECKBOX_NAME,
        'queryset': queryset,
        "object_name": force_unicode(opts.verbose_name),
    }
    template_name = getattr(modeladmin, 'permission_change_form_template', [
        "admin/%s/%s/permission_change_form.html" % (app_label, opts.object_name.lower()),
        "admin/%s/permission_change_form.html" % app_label,
        "admin/permission_change_form.html"
    ])
    return render_to_response(template_name, context,
                              context_instance=template.RequestContext(request))
edit_permissions.short_description = _("Permissions for selected %(verbose_name_plural)s")

class PermissionAdmin(admin.ModelAdmin):
    list_display = ('codename', 'content_type', 'user', 'group')
    list_filter = ('content_type',)
    search_fields = ('user__username', 'group__name', 'codename')
    raw_id_fields = ('user', 'group', 'creator')
    generic_fields = ('content_object',)
    fieldsets = (
        (None, {
            'fields': ('codename', ('content_type', 'object_id'))
        }),
        (_('granted'), {
            'fields': ('creator', ('user', 'group'),)
        }),
    )

    def formfield_for_dbfield(self, db_field, **kwargs):
        # For generic foreign keys marked as generic_fields we use a special widget
        if db_field.name in [f.fk_field for f in self.model._meta.virtual_fields if f.name in self.generic_fields]:
            for gfk in self.model._meta.virtual_fields:
                if gfk.fk_field == db_field.name:
                    return db_field.formfield(
                        widget=GenericForeignKeyRawIdWidget(
                            gfk.ct_field, self.admin_site._registry.keys()))
        return super(PermissionAdmin, self).formfield_for_dbfield(db_field, **kwargs)

    def queryset(self, request):
        user = request.user
        if user.is_superuser or \
                user.has_perm('permissions.change_foreign_permissions'):
            return super(PermissionAdmin, self).queryset(request)
        return super(PermissionAdmin, self).queryset(request).filter(creator=user)

admin.site.register(Permission, PermissionAdmin)

if actions:
    admin.site.add_action(edit_permissions)
