"""proposal system views
"""
from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponseRedirect
from django.http import HttpResponse, Http404
from django.shortcuts import *
from django.template import loader
from django.db.models import Q
from django.template import RequestContext
from django.views.generic.list_detail import object_list, object_detail
from django.views.generic.create_update import create_object, update_object
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group
from django.contrib.contenttypes.models import ContentType
from django import forms
from itertools import groupby, chain
from pycon.features.decorators import *
from pycon.core import get_object_relations_or_404
from models import Proposal, PROPOSAL_LEVEL_CHOICES, generate_proposal_stats
from forms import *
from newforms import ProposalForm, proposal_status_form_filter
from changelog import log_action, all_change_histories, vote_change_histories
from django.contrib.admin.models import LogEntry, CHANGE
from feeds import ProposalChangeHistoryFeed
from tagging.models import Tag, TaggedItem

def catchall(request, url, redirect=""):
    redirect_to = request.path[:len(url)]
    if redirect:
        if redirect_to[-1] != "/":
            redirect_to += "/"
        redirect_to += redirect
    if redirect_to[-1] != "/":
        redirect_to += "/"
    return HttpResponseRedirect(redirect_to)

def is_reviewer(request, *args, **kwdargs):
    return request.user.has_perm('propmgr.can_view_all_reviews')

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def proposal_stats(request, **extra_context):
    if not request.user.can_view_proposal_stats:
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view stats',
                                       'title': 'Proposal Statistics'},
                              context_instance=RequestContext(request))
    page_cache_name = '_'.join([str(request.user.id),
                                'proposal_stats'])
    page_cache = cache.get(page_cache_name)
    if page_cache: return HttpResponse(page_cache)
    extra_context['title'] = 'Proposal System Statistics'
    extra_context['stats'] = generate_proposal_stats()
    page_cache = loader.render_to_string('propmgr/stats.html', extra_context,
                                    context_instance=RequestContext(request))
    cache.set(page_cache_name, page_cache, 120)
    return HttpResponse(page_cache)

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def reviewers_list(request, **extra_context):
    if not request.user.can_view_proposal_stats:
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view reviewers',
                                       'title': 'Proposal Reviewers'},
                              context_instance=RequestContext(request))
    page_cache_name = '_'.join([str(request.user.id),
                                str(bool(int(request.session.get('minimal_view', False)))),
                                'reviewers_list'])
    page_cache = cache.get(page_cache_name)
    if page_cache: return HttpResponse(page_cache)

    extra_context['title'] = 'Proposal Reviewers'
    extra_context['proposal_count'] = Proposal.objects.count()
    extra_context['reviewers'] = Group.objects.get(
        name='Reviewers').user_set.order_by('first_name', 'last_name')
    page_cache = loader.render_to_string('propmgr/reviewers.html', extra_context,
                                         context_instance=RequestContext(request))
    cache.set(page_cache_name, page_cache, 120)
    return HttpResponse(page_cache)

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def authors_list(request, **extra_context):
    if not request.user.can_view_proposal_stats:
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view authors',
                                       'title': 'Proposal Authors'},
                              context_instance=RequestContext(request))
    page_cache_name = '_'.join([str(request.user.id),
                                str(bool(int(request.session.get('minimal_view', False)))),
                                'authors_list'])
    page_cache = cache.get(page_cache_name)
    if page_cache: return HttpResponse(page_cache)

    extra_context['title'] = 'Proposal Authors'
    extra_context['proposal_count'] = Proposal.objects.count()
    extra_context['sub_count'] = Group.objects.get(
        name='Submitters').user_set.count()
    extra_context['authors'] = Group.objects.get(
        name='Authors').user_set.order_by('first_name', 'last_name')
    page_cache = loader.render_to_string('propmgr/submitters.html', extra_context,
                                         context_instance=RequestContext(request))
    cache.set(page_cache_name, page_cache, 120)
    return HttpResponse(page_cache)

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def votes_list(request, **extra_context):
    if not request.user.can_view_proposal_stats and not request.user.has_perm('propmgr.can_view_all_reviews'):
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view votes',
                                       'title': 'Proposal Votes'},
                              context_instance=RequestContext(request))

    if 'feed' in request.GET and request.GET['feed'] == 'rss2':
        limit = 50
        if 'limit' in request.GET:
            try: limit = int(request.GET['limit'])
            except: pass
        query = vote_change_histories()
        feed = ProposalChangeHistoryFeed('proposal_vote_history',
                request, query, limit=limit,
                title='Recent Changes to Talk Proposal Votes',
                description='Latest changes to the talk proposal voting.'
                ).get_feed(request.path)
        response = HttpResponse(mimetype=feed.mime_type)
        feed.write(response, 'utf-8')
        return response

    page_cache_name = '_'.join([str(request.user.id),
                                str(bool(int(request.session.get('minimal_view', False)))),
                                'votes_list'])
    page_cache = cache.get(page_cache_name)
    if page_cache: return HttpResponse(page_cache)

    extra_context['prop_count'] = Proposal.objects.count()
    extra_context['vote_count'] = Review.objects.count()
    extra_context['title'] = 'Proposal Votes'
    proposals = Proposal.objects.select_related()
    ranked = {}
    for name, propiter in groupby(proposals, Proposal.get_review_rank):
        data = ranked.setdefault(name, list())
        data.extend(propiter)
    ranked['needs_champion'] = Proposal.needs_champion_set().select_related()
    extra_context['ranked_proposals'] = ranked
    page_cache = loader.render_to_string('propmgr/votes.html', extra_context,
                                         context_instance=RequestContext(request))
    cache.set(page_cache_name, page_cache, 60)
    return HttpResponse(page_cache)

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def status_list(request, **extra_context):
    if (not request.user.can_view_proposal_stats and
        not request.user.has_perm('propmgr.can_view_all_reviews')):
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view status',
                                       'title': "Proposal Status'"},
                              context_instance=RequestContext(request))

    accepted = Proposal.objects.filter(status='A')
    declined = Proposal.objects.filter(status='D')
    reviewable = Proposal.objects.filter(status='R')
    other = Proposal.objects.exclude(status__in=['A', 'D', 'R'])
    if request.user.has_perm('propmgr.change_proposal_status'):
        data = request.POST if request.POST else None
        all = proposal_status_form_filter(chain(accepted, declined,
                                                reviewable, other),
                                          user=request.user, data=data)
        if '__change' in request.POST:
            for prop in all: prop.form.save()
            return HttpResponseRedirect(request.path)
    extra_context['do_form'] = request.user.has_perm(
        'propmgr.change_proposal_status')
    extra_context['accepted'] = accepted
    extra_context['declined'] = declined
    extra_context['reviewable'] = reviewable
    extra_context['other'] = other
    extra_context['title'] = "Proposal Status'"
    page_cache = loader.render_to_string('propmgr/proposal_status.html',
                    extra_context, context_instance=RequestContext(request))
    return HttpResponse(page_cache)

@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def users_proposal_list(request, **extra_context):
    if ('feed' in request.GET and request.GET['feed'] == 'rss2' and
        not request.user.is_anonymous()):
        limit = 50
        if 'limit' in request.GET:
            try: limit = int(request.GET['limit'])
            except: pass
        query = request.user.myproposals_full_change_history
        feed = ProposalChangeHistoryFeed('mypycon_recent',
                request, query, limit=limit,
                title='Recent Changes to My Talk Proposals',
                description=('Latest changes to the talk proposals '
                             'I am associated with.')).get_feed(request.path)
        response = HttpResponse(mimetype=feed.mime_type)
        feed.write(response, 'utf-8')
        return response
    if request.user.can_view_proposal_stats:
        extra_context['prop_count'] = Proposal.objects.count()
        extra_context['sub_count'] = Group.objects.get(name='Submitters').user_set.count()
        extra_context['auth_count'] = Group.objects.get(name='Authors').user_set.count()
        extra_context['rev_count'] = Group.objects.get(name='Reviewers').user_set.count()
        extra_context['vote_count'] = Review.objects.count()
    return render_to_response('propmgr/mypycon.html', extra_context,
                              context_instance=RequestContext(request))


@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def index(request, *args, **kwdargs):
    if not request.user.can_view_proposal_listing:
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view proposals',
                                       'title': 'All Proposals'},
                              context_instance=RequestContext(request))
    if ('feed' in request.GET and request.GET['feed'] == 'rss2' and
        request.user.has_perm('propmgr.can_view_all_proposals')):
        limit = 50
        if 'limit' in request.GET:
            try: limit = int(request.GET['limit'])
            except: pass
        query = all_change_histories()
        feed = ProposalChangeHistoryFeed('proposal_list_history',
                request, query, limit=limit,
                title='Recent Changes to Talk Proposals',
                description='Latest changes to the talk proposals.'
                ).get_feed(request.path)
        response = HttpResponse(mimetype=feed.mime_type)
        feed.write(response, 'utf-8')
        return response
    context = kwdargs.setdefault('extra_context', {})
    myargs = {'template_object_name' : 'proposal',
              'allow_empty': True}
    myargs.update(kwdargs)
    query = Proposal.objects
    search = []
    if 'search' in request.GET:
        search = request.GET['search'].split()
        if len(search) == 2 and search[0] == '--Search' and search[1] == 'Proposals--':
            search = []
    qset = []
    filter = request.GET.get('filter', None)
    if filter is not None:
        try:
            tag = Tag.objects.get(name=filter)
            ct = ContentType.objects.get_for_model(Proposal)
            props = [ x['object_id'] for x in
                      tag.items.filter(content_type=ct).values('object_id') ]
            if props: query = query.filter(id__in=props)
        except Tag.DoesNotExist:
            filter = None
    if search:
        for term in search:
            qset.append(Q(title__icontains = term)|
                        Q(categories__icontains = term) |
                        Q(summary__icontains = term) |
                        Q(description__icontains = term))
        query = query.filter(*qset).distinct()

    tags = Tag.objects.usage_for_model(Proposal, min_count=1)
    context['as_talks']   = False
    context['add_filter'] = False
    context['num_prop'] = Proposal.objects.count()
    context['filter'] = filter
    context['categories'] = tags
    context['search'] = search
    context.setdefault('title', "Search Talk Proposals")
    return object_list(request, query.select_related(),
                       *args, **myargs)

@login_required
@feature_required("ViewProposal", exception_func=is_reviewer,
                  auto_create_feat=True)
def detail(request, *args, **kwdargs):
    proposal = get_object_relations_or_404(Proposal, pk=kwdargs.get('object_id'))
    if not request.user.can_view_proposal(proposal):
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view this proposal',
                                       'title': 'View Proposal'},
                              context_instance=RequestContext(request))
    if 'feed' in request.GET and request.GET['feed'] == 'rss2':
        limit = 50
        if 'limit' in request.GET:
            try: limit = int(request.GET['limit'])
            except: pass
        feed = ProposalChangeHistoryFeed('proposal_history', request,
                                         proposal=proposal, limit=limit
                                         ).get_feed(request.path)
        response = HttpResponse(mimetype=feed.mime_type)
        feed.write(response, 'utf-8')
        return response
    context = kwdargs.setdefault('extra_context', dict())
    context['prop_and_user'] = (proposal, request.user)
    kwdargs.setdefault('queryset', Proposal.objects.all())
    kwdargs.setdefault('template_object_name', 'proposal')
    return object_detail(request, *args, **kwdargs)

def example_proposal(request, example, examples):
    if example not in examples:
        raise Http404('Example Proposal Not Found.')
    initial = examples[example]
    form = ProposalForm(request, initial=initial)
    extra_context = dict(title=initial['name'] + ': ' + initial['title'],
                         level_choices=PROPOSAL_LEVEL_CHOICES,
                         preview=bool(request.POST),
                         propid=initial['name'],
                         proposal=None,
                         example_author=initial['example_author'],
                         is_new=True,
                         has_errors=True,
                         redirect_to='',
                         form=form)
    return render_to_response('propmgr/proposal_submit.html', extra_context,
                              context_instance=RequestContext(request))


def _proposal_submit(request, prop, title, redirect_to=None):
    extra_context = {'preview': False, 'title': title, 'proposal': prop}
    form = ProposalForm(request, prop)
    is_new = prop is None
    has_errors = form.is_bound and not form.is_valid()
    if request.POST:
        if form.is_valid() and ('__submit' in request.POST):
            prop = form.save()
            if not redirect_to:
                redirect_to = '../%s/' % str(prop.id)
            return HttpResponseRedirect(redirect_to)
        extra_context['preview'] = True
    extra_context['propid'] = prop.id if prop is not None else '##'
    extra_context['level_choices'] = PROPOSAL_LEVEL_CHOICES
    extra_context['is_new'] = is_new
    extra_context['has_errors'] = has_errors
    extra_context['redirect_to']= '../' ## Cancel button
    extra_context['form'] = form
    return render_to_response('propmgr/proposal_submit.html', extra_context,
                              context_instance=RequestContext(request))

@feature_required("SubmitProposal", auto_create_feat=True)
def submit_new_proposal(request):
    if not request.user.can_add_proposal:
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'submit a new proposal',
                                       'title': 'Submit New Talk Proposal'},
                              context_instance=RequestContext(request))
    return _proposal_submit(request, None, 'Submit New Talk Proposal', None)

@login_required
@feature_required("EditProposal", auto_create_feat=True)
def edit_proposal(request, id, redirect_to=".."):
    prop = get_object_relations_or_404(Proposal,pk=id)
    if not request.user.can_edit_proposal(prop):
        return render_to_response('propmgr/lack_permissions.html',
                                  {'title': 'Edit Proposal',
                                   'operation': 'edit this talk proposal'},
                          context_instance=RequestContext(request))
    return _proposal_submit(request, prop, 'Edit Talk Proposal', redirect_to)

@login_required
@feature_required("ReviewProposal", auto_create_feat=True)
def reviewer_opt_out(request, pid):
    prop = get_object_relations_or_404(Proposal,pk=pid)
    if not request.user.can_add_review(prop):
        return render_to_response('propmgr/lack_permissions.html',
                                  {'title': 'Reviewer Opt Out',
                                   'operation':
                                        'opt-out of reviewing this proposal'},
                          context_instance=RequestContext(request))
    reviewer = None
    if prop.reviewers.count() <= 3:
        conflict = [ auth.id for auth in prop.coauthors.all() ]
        conflict.append( prop.submitter.id )
        conflict.extend( rev.id for rev in prop.reviewers.all() )
        try:
            reviewer = Group.objects.get(name='Reviewers').user_set.exclude(
                id__in=conflict).order_by('?')[0]
        except IndexError: pass
    prop.reviewers.remove(request.user)
    if reviewer is not None:
        prop.reviewers.add(reviewer)
        t = loader.get_template("propmgr/email/review_proposal.txt")
        c = {
            'mailto': reviewer,
            'email': reviewer.email,
            'proposal': prop
        }
        message = t.render(RequestContext(request, c))
        from pycon.core import mail
        #print message # because dev does not have sendmail enabled.
        mail.send_mail(settings.CONFERENCE_NAME + ' Reassigned Talk Proposal Submission for Review',
                       message, settings.PROPMGR_FROM_EMAIL,
                       [reviewer.email],
                       replyto_email=settings.PROPMGR_REPLYTO_EMAIL)

    prop.save()
    if reviewer is not None:
        message = ('* Removed self as reviewer.\n* Auto-Assigned Reviewer #' +
                   '%d.\n' % reviewer.id)
        log_action(request.user, prop, CHANGE, message)
    else:
        log_action(request.user, prop, CHANGE, '* Removed self as reviewer.\n')

    return HttpResponseRedirect('../')

@login_required
@feature_required("ReviewProposal", auto_create_feat=True)
def add_or_edit_review(request, pid, rid=None):
    prop = get_object_or_404(Proposal,pk=pid)
    review=None
    if rid is None:
        title = 'Review Proposal'
        if not request.user.can_add_review(prop):
            return render_to_response('propmgr/lack_permissions.html',
                                  {'title': title,
                                   'operation': 'review this talk proposal'},
                          context_instance=RequestContext(request))
        manipulator = NewReviewForm(request.user, prop)
        redirect_to = '../'
    else:
        title = 'Edit Review'
        review = get_object_or_404(Review,pk=rid)
        if not request.user.can_edit_review(review):
            return render_to_response('propmgr/lack_permissions.html',
                                  {'title': title,
                                   'operation': 'edit this talk proposal review'},
                           context_instance=RequestContext(request))
        manipulator = EditReviewForm(request.user, prop, review)
        redirect_to = "../../"

    extra_context = {'preview': False, 'title': title,
                     'redirect_to': redirect_to,
                     'review': review}
    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        if not errors and '__submit' in new_data:
            manipulator.save(new_data)
            return HttpResponseRedirect(redirect_to)
        #if '__preview' in new_data:
        extra_context['proposal'] = prop
        extra_context['preview'] = True
    else:
        errors = {}
        new_data = manipulator.flatten_data()
    extra_context['has_errors'] = bool(errors)
    extra_context['form'] = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('propmgr/review_edit.html', extra_context,
                              context_instance=RequestContext(request))

@login_required
@feature_required("EditProposal", auto_create_feat=True)
def edit_authors(request, pid):
    prop = get_object_relations_or_404(Proposal,pk=pid)
    if not request.user.can_edit_coauthors(prop):
        return render_to_response('propmgr/lack_permissions.html',
                              {'title': 'Edit Co-Authors',
                               'operation': 'edit co-authors for this talk proposal'},
                      context_instance=RequestContext(request))
    manipulator = EditCoAuthorsForm(request.user, prop)

    extra_context = {'title': 'Edit Co-Authors', 'proposal': prop}
    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        if not errors:
            manipulator.save(new_data)
            return HttpResponseRedirect('../')
    else:
        errors = {}
        new_data = manipulator.flatten_data()
    extra_context['form'] = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('propmgr/authors_edit.html', extra_context,
                              context_instance=RequestContext(request))


@login_required
@feature_required("CommentOnProposal", auto_create_feat=True)
def add_comment(request, pid):
    prop = get_object_or_404(Proposal,pk=pid)
    if not request.user.can_add_comment(prop):
        return render_to_response('propmgr/lack_permissions.html',
                              {'title': 'Add Comment',
                               'operation': 'add a comment to this talk proposal'},
                      context_instance=RequestContext(request))
    manipulator = NewCommentForm(prop, request)

    extra_context = {'preview': False, 'title': 'Add Comment',
                     'redirect_to': '../'}
    if request.POST:
        new_data = request.POST.copy()
        errors = manipulator.get_validation_errors(new_data)
        if not errors and '__submit' in new_data:
            manipulator.save(new_data)
            return HttpResponseRedirect('../')
        #if '__preview' in new_data:
        extra_context['proposal'] = prop
        extra_context['preview'] = True
    else:
        errors = {}
        new_data = manipulator.flatten_data()
    extra_context['prop_and_user'] = (prop, request.user)
    extra_context['has_errors'] = bool(errors)
    extra_context['form'] = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('propmgr/comment_add.html', extra_context,
                              context_instance=RequestContext(request))


@login_required
@feature_required("EditProposal", auto_create_feat=True)
def attach_file(request, pid):
    prop = get_object_or_404(Proposal,pk=pid)
    if not request.user.can_edit_proposal(prop):
        return render_to_response('propmgr/lack_permissions.html',
                              {'title': 'Upload File',
                               'operation': 'attach a file to this talk proposal'},
                      context_instance=RequestContext(request))
    manipulator = AttachFileForm(request.user, prop)
    extra_context = {'title': 'Upload File'}
    if request.POST:
        new_data = request.POST.copy()
        new_data.update(request.FILES)
        errors = manipulator.get_validation_errors(new_data)
        if not errors:
            manipulator.save(new_data)
            return HttpResponseRedirect('../')
    else:
        errors = {}
        new_data = manipulator.flatten_data()

    extra_context['form'] = forms.FormWrapper(manipulator, new_data, errors)
    return render_to_response('propmgr/attach_file.html', extra_context,
                              context_instance=RequestContext(request))


def _get_filter_listing(proposals):
    cats = cache.get('propmgr_accepted_published_talk_categories', None)
    if cats is not None: return cats
    cats = sorted(list(set(cat.strip() for prop in proposals for cat in prop.categories.split(','))))
    cache.set('propmgr_accepted_published_talk_categories', cats, 60*60*24)
    return cats


@feature_required("ViewAcceptedProposals",
                  auto_create_feat=True)
def accepted_talks(request):
    query_filter = request.GET.get('filter', '')
    query_search = request.GET.get('search', '')

    # generate cache name
    cache_name = (
        '%s_propmgr_accepted_talks_' % request.user.id
        if not request.user.is_anonymous() else '0')
    if not query_filter and not query_search:
        cache_name += 'full'
    elif query_filter and not query_search:
        cache_name += 'filter_%s' % query_filter.replace(' ', '_')
    else:
        cache_name += 'search_%s' % query_search.replace(' ', '_')

    # return a cached version if non-existant
    page_cache = cache.get(cache_name)
    if page_cache:
        return HttpResponse(page_cache)

    # break into individual terms
    search = (
        query_search.split()
        if query_search not in ['--Search Talks--', ''] else [])
    # filtr != builtin func filter
    filtr = query_filter.replace(' ', '_')

    count = Proposal.objects.filter(status='A', published=True).count()
    tags = Tag.objects.usage_for_model(
        Proposal, min_count=2, filters={'status': 'A', 'published': True})

    # generate query
    query = Proposal.objects.filter(status='A', published=True)
    add_filter = False
    if search:
        qchain = Q()
        for term in search:
            qchain = qchain | (
                Q(title__icontains=term) |
                Q(categories__icontains=term) |
                Q(summary__icontains=term))
        query = query.filter(qchain).distinct()
    elif filtr:
        try:
            tag = Tag.objects.get(name=filtr)
            ct = ContentType.objects.get_for_model(Proposal)
            props = [
                x['object_id'] for x in
                tag.items.filter(content_type=ct).values('object_id')]
            if props:
                query = query.filter(id__in=props)
            add_filter = filtr not in [x.name for x in tags]
        except Tag.DoesNotExist:
            filtr = None

    # generate page title
    title = 'Talks'
    if filtr:
        title += ' (%s)' % filtr
    if search:
        title += ' [%s]' % query_search

    extra_context = {
        'as_talks': True,
        'num_prop': count,
        'filter': filtr,
        'add_filter': add_filter,
        'title': title,
        'proposal_list': query.select_related(),
        'categories': tags,
        'search': search
        }

    page_cache = loader.render_to_string(
        'propmgr/proposal_list.html', extra_context,
        context_instance=RequestContext(request))
    cache.set(cache_name, page_cache, 60*60)

    return HttpResponse(page_cache)
