from django.shortcuts import HttpResponse, get_object_or_404, render
from django.http import Http404
from django.db.models import Count
from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.views.generic import View, DetailView, RedirectView, TemplateView
from django.views.generic.base import ContextMixin
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.core.urlresolvers import reverse
from django.core.exceptions import ObjectDoesNotExist
from django.contrib.contenttypes.models import ContentType
from django.contrib.sites.models import Site
from django.conf import settings
from notifications.models import Notification
from iconolab.models import Annotation, AnnotationRevision, Collection, Item, Image, IconolabComment, MetaCategory, MetaCategoryInfo
from iconolab.forms.annotations import AnnotationRevisionForm
import logging

logger = logging.getLogger(__name__)

class GlobalHomepageView(View):
    def get(self, request, *args, **kwargs):
        context = {}
        context['collections'] = Collection.objects
        return render(request, 'iconolab/home.html', context)

class TestView(View):
    template_name = 'iconolab/compare.html'

    def get(self, request, *args, **kwargs):
        return render(request, self.template_name)



class UserHomeView(DetailView):
    model = User
    slug_field = 'id'

    def get_context_data(self, **kwargs):
        context = super(UserHomeView, self).get_context_data(**kwargs)
        return context

    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        context = self.get_context_data()
        profile_user = self.object
        context['profile_user'] = profile_user
        context['user_annotations'] = Annotation.objects.filter(author=profile_user).prefetch_related(
            'current_revision',
            'revisions',
            'image',
            'image__item',
            'image__item__collection'
        )
        context['user_revisions_annotations'] = Annotation.objects.filter(revisions__author=profile_user).exclude(author=profile_user).prefetch_related(
            'current_revision',
            'revisions',
            'image',
            'image__item',
            'image__item__collection'
        ).distinct()
        comments_annotations_str_id = IconolabComment.objects.filter(user=profile_user, content_type__app_label='iconolab', content_type__model='annotation').values_list('object_pk', flat=True)
        comments_annotations_id = [int(str_id) for str_id in comments_annotations_str_id]
        context['user_comments_annotations'] = Annotation.objects.filter(id__in=comments_annotations_id).exclude(author=profile_user).exclude(annotation_guid__in=context['user_revisions_annotations'].values_list('annotation_guid', flat=True)).prefetch_related(
            'current_revision',
            'revisions',
            'image',
            'image__item',
            'image__item__collection'
        ).distinct()
        if request.user.is_authenticated() and self.object == request.user:
            if request.GET.get('clear_notifications', False):
                Notification.objects.filter(recipient=request.user).mark_all_as_read()
            context['notifications'] = Notification.objects.filter(recipient=request.user)
        return render(request, 'iconolab/user_home.html', context)

class UserNotificationsView(View):

    def get(self, request, *args, **kwargs):
        context = {}
        notifications = Notification.objects.filter(recipient=request.user)
        context['notifications_unread_ids'] = notifications.unread().values_list('id', flat=True)
        page = request.GET.get('page', 1)
        paginator = Paginator(notifications, 50)
        try:
            notifications_list = paginator.page(page)
        except PageNotAnInteger:
            notifications_list = paginator.page(1)
        except EmptyPage:
            notifications_list = paginator.page(paginator.num_pages)
        context['notifications'] = notifications_list
        return render(request, 'iconolab/user_notifications.html', context)

# Class with check_kwargs method to fetch objects from database depending on what level in the app we're currently at
class IconolabObjectView(object):
    def check_kwargs(self, kwargs):
        '''
            Returns a boolean depending on wether (True) or not (False) the objects were found and a tuple containing the objects, with a select_related/prefetch_related on relevant related objects
            following this ordering: (collection, item, image, annotation, revision)
        '''

        objects_tuple = ()
        if 'collection_name' in kwargs.keys():
            try:
                objects_tuple += (Collection.objects.prefetch_related('items', 'items__images').get(name=kwargs.get('collection_name')),)
            except (ValueError, Collection.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        if 'item_guid' in kwargs.keys():
            try:
                objects_tuple += (Item.objects.prefetch_related('images', 'metadatas', 'images__stats').get(item_guid=kwargs.get('item_guid')),)
            except (ValueError, Item.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        if 'image_guid' in kwargs.keys():
            try:
                objects_tuple += (Image.objects.prefetch_related('annotations', 'item', 'stats').get(image_guid=kwargs.get('image_guid')),)
            except (ValueError, Image.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        if 'annotation_guid' in kwargs.keys():
            try:
                objects_tuple += (Annotation.objects.select_related('current_revision', 'stats', 'image').get(annotation_guid=kwargs.get('annotation_guid')),)
            except (ValueError, Annotation.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        if 'revision_guid' in kwargs.keys():
            try:
                objects_tuple += (AnnotationRevision.objects.select_related('parent_revision').get(revision_guid=kwargs.get('revision_guid')),)
            except (ValueError, AnnotationRevision.DoesNotExist):
                return False, RedirectView.as_view(url=reverse('404error'))
        return True, objects_tuple

class CollectionHomepageView(View, ContextMixin, IconolabObjectView):
    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection,) = result
        else:
            return result(request)
        context = super(CollectionHomepageView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['collection'] = collection
        
        # get Pagination and navigation query args
        try:
            items_page = int(request.GET.get('items_page', '1'))
        except ValueError:
            items_page = 1
        try:
            items_per_page = int(request.GET.get('items_perpage', '12'))
        except ValueError:
            items_per_page = 12
        
        try:
            recent_page = int(request.GET.get('recent_page', '1'))
        except ValueError:
            recent_page = 1
        try:
            recent_per_page = int(request.GET.get('recent_perpage', '10'))
        except ValueError:
            recent_per_page = 10
        
        try:
            revised_page = int(request.GET.get('revised_page', '1'))
        except ValueError:
            revised_page = 1
        try:
            revised_per_page = int(request.GET.get('revised_perpage', '10'))
        except ValueError:
            revised_per_page = 10
        
        try:
            contributions_page = int(request.GET.get('contributions_page', '1'))
        except ValueError:
            contributions_page = 1
        try:
            contributions_per_page = int(request.GET.get('contributions_perpage', '10'))
        except ValueError:
            contributions_per_page = 10
        
        active_list = request.GET.get('show', 'items')
        if active_list not in ['items', 'recent', 'revised', 'contributions']:
            active_list = 'items'
        context["active_list"] = active_list
        
        # Paginated objects list
        context["items_page"] = items_page
        context["items_perpage"] = items_per_page
        items_paginator = Paginator(collection.items.all(), items_per_page)
        try:
            context["items_list"] = items_paginator.page(items_page)
        except PageNotAnInteger:
            context["items_list"] = items_paginator.page(1)
        except EmptyPage:
            context["items_list"] = items_paginator.page(items_paginator.num_pages)
        
        # Paginated recent annotations list
        context["recent_page"] = recent_page
        context["recent_perpage"] = recent_per_page
        recent_annotations = Annotation.objects.filter(image__item__collection__name=collection.name).prefetch_related(
            'current_revision',
            'stats'
        ).order_by('-current_revision__created')
        recent_paginator = Paginator(recent_annotations, recent_per_page)
        try:
            context["recent_list"] = recent_paginator.page(recent_page)
        except PageNotAnInteger:
            context["recent_list"] = recent_paginator.page(1)
        except EmptyPage:
            context["recent_list"] = recent_paginator.page(recent_paginator.num_pages)
        
        # Paginated revised annotations list
        context["revised_page"] = revised_page
        context["revised_perpage"] = revised_per_page
        revised_annotations = Annotation.objects.filter(image__item__collection__name=collection.name).prefetch_related(
            'current_revision',
            'stats'
        ).annotate(revision_count=Count('revisions')).order_by('-revision_count')
        revised_paginator = Paginator(revised_annotations, revised_per_page)
        try:
            context["revised_list"] = revised_paginator.page(revised_page)
        except PageNotAnInteger:
            context["revised_list"] = revised_paginator.page(1)
        except EmptyPage:
            context["revised_list"] = revised_paginator.page(revised_paginator.num_pages)
        
        # Paginated contribution calls annotation list
        context["contributions_page"] = contributions_page
        context["contributions_perpage"] = contributions_per_page
        contrib_calls_annotations_ids = list(set(MetaCategoryInfo.objects.filter(
            metacategory__collection__name=collection.name,
            metacategory__triggers_notifications=MetaCategory.CONTRIBUTORS
        ).order_by('comment__submit_date').values_list('comment__object_pk', flat=True)))
        collection_annotations = Annotation.objects.filter(id__in=contrib_calls_annotations_ids).all()
        collection_ann_dict = dict([(str(annotation.id), annotation) for annotation in collection_annotations])
        contributions_annotations = [collection_ann_dict[id] for id in contrib_calls_annotations_ids]
        contributions_paginator = Paginator(contributions_annotations, contributions_per_page)
        try:
            context["contributions_list"] = contributions_paginator.page(contributions_page)
        except PageNotAnInteger:
            context["contributions_list"] = contributions_paginator.page(1)
        except EmptyPage:
            context["contributions_list"] = contributions_paginator.page(contributions_paginator.num_pages)
            
        return render(request, 'iconolab/collection_home.html', context)



class ShowItemView(View, ContextMixin, IconolabObjectView):
    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, item) = result
        else:
            return result(request)
        context = super(ShowItemView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['item_guid'] = self.kwargs.get('image_guid', '')
        context['collection'] = collection
        context['item'] = item
        for image in item.images.all():
            image.stats.views_count += 1
            image.stats.save()
        return render(request, 'iconolab/detail_item.html', context);

class ShowImageView(View, ContextMixin, IconolabObjectView):
    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image) = result
        else:
            return result(request)
        context = super(ShowImageView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['collection'] = collection
        context['image'] = image
        return render(request, 'iconolab/detail_image.html', context)

class CreateAnnotationView(View, ContextMixin, IconolabObjectView):

    def get_context_data(self, **kwargs):
        context = super(CreateAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image,) = result
        else:
            return result(request)
        annotation_form = AnnotationRevisionForm()
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['tags_data'] = '[]'
        return render(request, 'iconolab/change_annotation.html', context)

    def post(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_form = AnnotationRevisionForm(request.POST)
        if annotation_form.is_valid():
            author = request.user
            title = annotation_form.cleaned_data['title']
            description = annotation_form.cleaned_data['description']
            fragment = annotation_form.cleaned_data['fragment']
            tags_json = annotation_form.cleaned_data['tags']
            new_annotation = Annotation.objects.create_annotation(author, image, title=title, description=description, fragment=fragment, tags_json=tags_json)
            revision_comment = annotation_form.cleaned_data['comment']
            IconolabComment.objects.create(
                comment = revision_comment,
                revision = new_annotation.current_revision,
                content_type = ContentType.objects.get(app_label='iconolab', model='annotation'),
                content_object = new_annotation,
                site = Site.objects.get(id=settings.SITE_ID),
                object_pk = new_annotation.id,
                user = request.user,
                user_name = request.user.username
            )
            return RedirectView.as_view(url=reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': new_annotation.annotation_guid}))(request)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['tags_data'] = '[]'
        return render(request, 'iconolab/change_annotation.html', context)

class ShowAnnotationView(View, ContextMixin, IconolabObjectView):

    def get_context_data(self, **kwargs):
        context = super(ShowAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)
        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        context['tags_data'] = annotation.current_revision.get_tags_json()

        page = request.GET.get('page', 1)
        per_page = request.GET.get('perpage', 10)
        full_comments_list = IconolabComment.objects.for_app_models('iconolab.annotation').filter(object_pk = annotation.pk).order_by('thread_id', '-order')
        paginator = Paginator(full_comments_list, per_page)
        try:
            comments_list = paginator.page(page)
        except PageNotAnInteger:
            comments_list = paginator.page(1)
        except EmptyPage:
            comments_list = paginator.page(paginator.num_pages)
        context['comments'] = comments_list

        if request.user.is_authenticated():
            user_comment_notifications = Notification.objects.filter(
                recipient=request.user,
                action_object_content_type__app_label='iconolab',
                action_object_content_type__model='iconolabcomment',
                target_content_type__app_label='iconolab',
                target_content_type__model='annotation',
                target_object_id=annotation.id
            ).unread()
            context['notifications_comments_ids'] = [int(val) for val in user_comment_notifications.values_list('action_object_object_id', flat=True)]
            comment_list_ids = [comment.id for comment in context['comments'] ]
            for notification in user_comment_notifications.all():
                if int(notification.action_object_object_id) in comment_list_ids:
                    notification.mark_as_read()

        image.stats.views_count += 1
        image.stats.save()
        annotation.stats.views_count += 1
        annotation.stats.save()
        return render(request, 'iconolab/detail_annotation.html', context)


class EditAnnotationView(View, ContextMixin, IconolabObjectView):

    def get_context_data(self, **kwargs):
        context = super(EditAnnotationView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation,) = result
        else:
            return result(request)
        annotation_form = AnnotationRevisionForm(instance=annotation.current_revision)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['annotation'] = annotation
        context['form'] = annotation_form
        context['tags_data'] = annotation.current_revision.get_tags_json()
        return render(request, 'iconolab/change_annotation.html', context)

    def post(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_guid = kwargs['annotation_guid']
        annotation_form = AnnotationRevisionForm(request.POST)
        if annotation_form.is_valid():
            revision_author = request.user
            revision_title = annotation_form.cleaned_data['title']
            revision_description = annotation_form.cleaned_data['description']
            revision_fragment = annotation_form.cleaned_data['fragment']
            revision_tags_json = annotation_form.cleaned_data['tags']
            new_revision = annotation.make_new_revision(revision_author, revision_title, revision_description, revision_fragment, revision_tags_json)
            revision_comment = annotation_form.cleaned_data['comment']
            comment = IconolabComment.objects.create(
                comment = revision_comment,
                revision = new_revision,
                content_type = ContentType.objects.get(app_label='iconolab', model='annotation'),
                content_object = annotation,
                site = Site.objects.get(id=settings.SITE_ID),
                object_pk = annotation.id,
                user = request.user,
                user_name = request.user.username
            )
            return RedirectView.as_view(url=reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': annotation_guid}))(request)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['form'] = annotation_form
        context['annotation'] = annotation
        context['tags_data'] = annotation.current_revision.get_tags_json()
        return render(request, 'iconolab/change_annotation.html', context)


class ShowRevisionView(View, ContextMixin, IconolabObjectView):

    def get_context_data(self, **kwargs):
        context = super(ShowRevisionView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        context['revision_guid'] = self.kwargs.get('revision_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision,) = result
        else:
            return result(request)
        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        context['revision'] = revision
        context['tags_data'] = revision.get_tags_json()
        context['comment'] = revision.creation_comment.first()
        if request.user.is_authenticated() and annotation.author == request.user:
            ann_author_notified = Notification.objects.filter(
                    recipient=request.user,
                    action_object_content_type__app_label='iconolab',
                    action_object_content_type__model='annotationrevision',
                    action_object_object_id=revision.id,
                    target_content_type__app_label='iconolab',
                    target_content_type__model='annotation',
                    target_object_id=annotation.id
                ).unread()
            if ann_author_notified:
                ann_author_notified.first().mark_as_read()
                context['notified_revision'] = True
        if request.user.is_authenticated() and revision.author == request.user:
            rev_author_notified = Notification.objects.filter(
                    recipient=request.user,
                    action_object_content_type__app_label='iconolab',
                    action_object_content_type__model='annotationrevision',
                    action_object_object_id=revision.id,
                    target_content_type__app_label='iconolab',
                    target_content_type__model='annotation',
                    target_object_id=annotation.id
                ).unread()
            if rev_author_notified:
                rev_author_notified.first().mark_as_read()
                context['notified_revision'] = True
        return render(request, 'iconolab/detail_revision.html', context)


class MergeProposalView(View, ContextMixin, IconolabObjectView):

    def get_context_data(self, **kwargs):
        context = super(MergeProposalView, self).get_context_data(**kwargs)
        context['collection_name'] = self.kwargs.get('collection_name', '')
        context['image_guid'] = self.kwargs.get('image_guid', '')
        context['annotation_guid'] = self.kwargs.get('annotation_guid', '')
        context['revision_guid'] = self.kwargs.get('revision_guid', '')
        return context

    def get(self, request, *args, **kwargs):
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision,) = result
        else:
            return result(request)
        # Only show merge form if there is a revision to merge AND the current user is the annotation author
        if revision.state != AnnotationRevision.AWAITING or request.user != annotation.author:
            return RedirectView.as_view(
                url=reverse('revision_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid,
                        'revision_guid': revision.revision_guid
                    }
                )
            )(request)
        # Auto-accepts the revision only if the proper query arg is set and only if the revision parent is the current revision
        if 'auto_accept' in request.GET and request.GET['auto_accept'] in ['True', 'true', '1', 'yes'] and revision.parent_revision == annotation.current_revision:
            annotation.validate_existing_revision(revision)
            return RedirectView.as_view(
                url=reverse('annotation_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid
                    }
                )
            )(request)
        # Auto-reject the revision only if the proper query arg is set
        if 'auto_reject' in request.GET and request.GET['auto_reject'] in ['True', 'true', '1', 'yes']:
            annotation.reject_existing_revision(revision)
            return RedirectView.as_view(
                url=reverse('annotation_detail',
                    kwargs={
                        'collection_name': collection.name,
                        'image_guid': image.image_guid,
                        'annotation_guid': annotation.annotation_guid
                    }
                )
            )(request)

        context = self.get_context_data(**kwargs)
        context['collection'] = collection
        context['image'] = image
        context['annotation'] = annotation
        # Proposal data
        context['proposal_revision'] = revision
        context['proposal_tags_data'] = revision.get_tags_json()
        context['proposal_comment'] = revision.creation_comment.first()
        # Parent data
        context['parent_revision'] = revision.parent_revision
        context['parent_tags_data'] = revision.parent_revision.get_tags_json()
        context['parent_comment'] = revision.parent_revision.creation_comment.first()
        # Current data
        context['current_revision'] = annotation.current_revision
        context['current_tags_data'] = annotation.current_revision.get_tags_json()
        context['current_comment'] = annotation.current_revision.creation_comment.first()

        merge_form = AnnotationRevisionForm(instance=revision)
        context['merge_form'] = merge_form
        return render(request, 'iconolab/merge_revision.html', context)

    def post(self, request, *args, **kwargs):
        # Handle merge form submit here
        success, result = self.check_kwargs(kwargs)
        if success:
            (collection, image, annotation, revision) = result
        else:
            return result(request)
        collection_name = kwargs['collection_name']
        image_guid = kwargs['image_guid']
        annotation_guid = kwargs['annotation_guid']
        revision_guid = kwargs['revision_guid']

        merge_revision_form = AnnotationRevisionForm(request.POST)
        if merge_revision_form.is_valid():
            revision_title = merge_revision_form.cleaned_data['title']
            revision_description = merge_revision_form.cleaned_data['description']
            revision_fragment = merge_revision_form.cleaned_data['fragment']
            revision_tags_json = merge_revision_form.cleaned_data['tags']
            new_revision = annotation.merge_existing_revision(revision_title, revision_description, revision_fragment, revision_tags_json, revision)
            revision_comment = merge_revision_form.cleaned_data['comment']
            comment = IconolabComment.objects.create(
                comment = revision_comment,
                revision = new_revision,
                content_type = ContentType.objects.get(app_label='iconolab', model='annotation'),
                content_object = annotation,
                site = Site.objects.get(id=settings.SITE_ID),
                object_pk = annotation.id,
                user = request.user,
                user_name = request.user.username
            )
            return RedirectView.as_view(url=reverse('annotation_detail', kwargs={'collection_name': collection_name, 'image_guid': image_guid, 'annotation_guid': annotation_guid}))(request)
        context = self.get_context_data(**kwargs)
        context['image'] = image
        context['merge_form'] = merge_revision_form
        context['annotation'] = annotation
        # Proposal data
        context['proposal_revision'] = revision
        context['proposal_tags_data'] = revision.get_tags_json()
        context['proposal_comment'] = revision.creation_comment.first()
        # Parent data
        context['parent_revision'] = revision.parent_revision
        context['parent_tags_data'] = revision.parent_revision.get_tags_json()
        context['parent_comment'] = revision.parent_revision.creation_comment.first()
        # Current data
        context['current_revision'] = annotation.current_revision
        context['current_tags_data'] = annotation.current_revision.get_tags_json()
        context['current_comment'] = annotation.current_revision.creation_comment.first()
        return render(request, 'iconolab/merge_revision.html', context)

