# ballot management (voting, commenting, writeups, ...) for Area
# Directors and Secretariat

import re, os
from datetime import datetime, date, time, timedelta
from django.http import HttpResponse, HttpResponseRedirect, Http404
from django.shortcuts import render_to_response, get_object_or_404, redirect
from django.core.urlresolvers import reverse as urlreverse
from django.template.loader import render_to_string
from django.template import RequestContext
from django import forms
from django.utils.html import strip_tags
from django.conf import settings
from django.core.exceptions import ObjectDoesNotExist

from ietf.utils.mail import send_mail_text, send_mail_preformatted
from ietf.ietfauth.decorators import group_required
from ietf.idtracker.templatetags.ietf_filters import in_group
from ietf.ietfauth.decorators import has_role
from mails import email_secretariat, generate_ballot_writeup, generate_issue_ballot_mail

from utils import *
from ietf.group.models import Group, GroupHistory, GroupEvent
from ietf.group.utils import save_group_in_history
from ietf.name.models import GroupBallotPositionName, GroupStateName
from ietf.doc.models import *

def default_action_text(wg, charter, user, action):
   e = WriteupDocEvent(doc=charter, by=user)
   e.by = user
   e.type = "changed_action_announcement"
   e.desc = "WG action text was changed"

   info = {}
   info['chairs'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
   info['secr'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
   info['techadv'] = [{ 'name': x.email.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
   info['ad'] = {'name': wg.ad.name, 'email': wg.ad.email_address().address } if wg.ad else None,
   info['list'] = wg.list_email if wg.list_email else None,
   info['list_subscribe'] = str(wg.list_subscribe) if wg.list_subscribe else None,
   info['list_archive'] = str(wg.list_archive) if wg.list_archive else None,

   filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
   try:
      charter_text = open(filename, 'r')
      info['charter_txt'] = charter_text.read()
   except IOError:
      info['charter_txt'] = "Error: couldn't read charter text"

   e.text = render_to_string("wgcharter/action_text.txt",
                            dict(wg=wg,
                                 charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
                                 action_type=action,
                                 info=info,
                                 )
                            )

   e.save()
   return e

def default_review_text(wg, charter, user):
   e = WriteupDocEvent(doc=charter, by=user)
   e.by = user
   e.type = "changed_review_announcement"
   e.desc = "WG review text was changed"
   info = {}
   info['chairs'] = [{ 'name': x.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Chair")]
   info['secr'] = [{ 'name': x.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Secr")]
   info['techadv'] = [{ 'name': x.person.name, 'email': x.email.address} for x in wg.role_set.filter(name="Techadv")]
   info['ad'] = {'name': wg.ad.name, 'email': wg.ad.role_email("ad").address } if wg.ad else None,
   info['list'] = wg.list_email if wg.list_email else None,
   info['list_subscribe'] = wg.list_subscribe if wg.list_subscribe else None,
   info['list_archive'] = wg.list_archive if wg.list_archive else None,

   info['bydate'] = (date.today() + timedelta(weeks=1)).isoformat()

   filename = os.path.join(settings.CHARTER_PATH, 'charter-ietf-%s-%s.txt' % (wg.acronym, wg.charter.rev))
   try:
      charter_text = open(filename, 'r')
      info['charter_txt'] = charter_text.read()
   except IOError:
      info['charter_txt'] = "Error: couldn't read charter text"

   e.text = render_to_string("wgcharter/review_text.txt",
                            dict(wg=wg,
                                 charter_url=settings.IDTRACKER_BASE_URL + charter.get_absolute_url(),
                                 info=info,
                                 review_type="new" if wg.state_id == "proposed" else "recharter",
                                 )
                            )
   e.save()
   return e

BALLOT_CHOICES = (("yes", "Yes"),
                  ("no", "No"),
                  ("block", "Block"),
                  ("abstain", "Abstain"),
                  ("", "No Record"),
                  )

def position_to_ballot_choice(position):
    for v, label in BALLOT_CHOICES:
        if v and getattr(position, v):
            return v
    return ""

def position_label(position_value):
    return dict(BALLOT_CHOICES).get(position_value, "")

class EditPositionForm(forms.Form):
    position = forms.ModelChoiceField(queryset=GroupBallotPositionName.objects.all(), widget=forms.RadioSelect, initial="norecord", required=True)
    block_comment = forms.CharField(required=False, label="Blocking comment", widget=forms.Textarea)
    comment = forms.CharField(required=False, widget=forms.Textarea)
    return_to_url = forms.CharField(required=False, widget=forms.HiddenInput)

    def clean_blocking(self):
       entered_blocking = self.cleaned_data["block_comment"]
       entered_pos = self.cleaned_data["position"]
       if entered_pos.slug == "block" and not entered_blocking:
           raise forms.ValidationError("You must enter a non-empty blocking comment")
       return entered_blocking

@group_required('Area_Director','Secretariat')
def edit_position(request, name):
    """Vote and edit comments on Charter as Area Director."""
    try:
        wg = Group.objects.get(acronym=name)
    except ObjectDoesNotExist:
        wglist = GroupHistory.objects.filter(acronym=name)
        if wglist:
            return redirect('wg_edit_position', name=wglist[0].group.acronym)
        else:
            raise Http404

    charter = set_or_create_charter(wg)
    started_process = charter.latest_event(type="started_iesg_process")
    if not started_process:
       raise Http404

    ad = login = request.user.get_profile()

    if 'HTTP_REFERER' in request.META:
        return_to_url = request.META['HTTP_REFERER']
    else:
        return_to_url = charter.get_absolute_url()

    # if we're in the Secretariat, we can select an AD to act as stand-in for
    if not has_role(request.user, "Area Director"):
        ad_id = request.GET.get('ad')
        if not ad_id:
            raise Http404()
        from ietf.person.models import Person
        ad = get_object_or_404(Person, pk=ad_id)

    old_pos = charter.latest_event(GroupBallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)

    if request.method == 'POST':
        form = EditPositionForm(request.POST)
        if form.is_valid():
 
            # save the vote
            clean = form.cleaned_data

            if clean['return_to_url']:
              return_to_url = clean['return_to_url']

            pos = GroupBallotPositionDocEvent(doc=charter, by=login)
            pos.type = "changed_ballot_position"
            pos.ad = ad
            pos.pos = clean["position"]
            pos.comment = clean["comment"].strip()
            pos.comment_time = old_pos.comment_time if old_pos else None
            pos.block_comment = clean["block_comment"].strip() if pos.pos_id == "block" else ""
            pos.block_comment_time = old_pos.block_comment_time if old_pos else None

            changes = []
            added_events = []
            # possibly add discuss/comment comments to history trail
            # so it's easy to see
            old_comment = old_pos.comment if old_pos else ""
            if pos.comment != old_comment:
                pos.comment_time = pos.time
                changes.append("comment")

                if pos.comment:
                    e = DocEvent(doc=charter)
                    e.by = ad # otherwise we can't see who's saying it
                    e.type = "added_comment"
                    e.desc = "[Ballot comment]\n" + pos.comment
                    added_events.append(e)

            old_block_comment = old_pos.block_comment if old_pos else ""
            if pos.block_comment != old_block_comment:
                pos.block_comment_time = pos.time
                changes.append("block_comment")

                if pos.block_comment:
                    e = DocEvent(doc=charter, by=login)
                    e.by = ad # otherwise we can't see who's saying it
                    e.type = "added_comment"
                    e.desc = "[Ballot blocking comment]\n" + pos.block_comment
                    added_events.append(e)

            # figure out a description
            if not old_pos and pos.pos.slug != "norecord":
                pos.desc = u"[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
            elif old_pos and pos.pos != old_pos.pos:
                pos.desc = "[Ballot Position Update] Position for %s has been changed to %s from %s" % (pos.ad.name, pos.pos.name, old_pos.pos.name)

            if not pos.desc and changes:
                pos.desc = u"Ballot %s text updated for %s" % (u" and ".join(changes), ad.name)

            # only add new event if we actually got a change
            if pos.desc:
                if login != ad:
                    pos.desc += u" by %s" % login.name

                pos.save()

                for e in added_events:
                    e.save() # save them after the position is saved to get later id
                        
                charter.time = pos.time
                charter.save()

            if request.POST.get("send_mail"):
                qstr = "?return_to_url=%s" % return_to_url
                if request.GET.get('ad'):
                    qstr += "&ad=%s" % request.GET.get('ad')
                return HttpResponseRedirect(urlreverse("wg_send_ballot_comment", kwargs=dict(name=wg.acronym)) + qstr)
            else:
                return HttpResponseRedirect(return_to_url)
    else:
        initial = {}
        if old_pos:
            initial['position'] = old_pos.pos.slug
            initial['block_comment'] = old_pos.block_comment
            initial['comment'] = old_pos.comment
            
        if return_to_url:
            initial['return_to_url'] = return_to_url
            
        form = EditPositionForm(initial=initial)

    return render_to_response('wgcharter/edit_position.html',
                              dict(charter=charter,
                                   wg=wg,
                                   form=form,
                                   ad=ad,
                                   return_to_url=return_to_url,
                                   old_pos=old_pos,
                                   ),
                              context_instance=RequestContext(request))

@group_required('Area_Director','Secretariat')
def send_ballot_comment(request, name):
    """Email Charter ballot comment for area director."""
    try:
        wg = Group.objects.get(acronym=name)
    except ObjectDoesNotExist:
        wglist = GroupHistory.objects.filter(acronym=name)
        if wglist:
            return redirect('wg_send_ballot_comment', name=wglist[0].group.acronym)
        else:
            raise Http404

    charter = set_or_create_charter(wg)
    started_process = charter.latest_event(type="started_iesg_process")
    if not started_process:
        raise Http404()

    ad = login = request.user.get_profile()

    return_to_url = request.GET.get('return_to_url')
    if not return_to_url:
        return_to_url = charter.get_absolute_url()

    if 'HTTP_REFERER' in request.META:
        back_url = request.META['HTTP_REFERER']
    else:
        back_url = charter.get_absolute_url()

    # if we're in the Secretariat, we can select an AD to act as stand-in for
    if not has_role(request.user, "Area Director"):
        ad_id = request.GET.get('ad')
        if not ad_id:
            raise Http404()
        from ietf.person.models import Person
        ad = get_object_or_404(Person, pk=ad_id)

    pos = charter.latest_event(GroupBallotPositionDocEvent, type="changed_ballot_position", ad=ad, time__gte=started_process.time)
    if not pos:
        raise Http404()
    
    subj = []
    d = ""
    if pos.pos_id == "block" and pos.block_comment:
        d = pos.block_comment
        subj.append("BLOCKING COMMENT")
    c = ""
    if pos.comment:
        c = pos.comment
        subj.append("COMMENT")

    ad_name_genitive = ad.name + "'" if ad.name.endswith('s') else ad.name + "'s"
    subject = "%s %s on %s" % (ad_name_genitive, pos.pos.name if pos.pos else "No Position", charter.name + "-" + charter.rev)
    if subj:
        subject += ": (with %s)" % " and ".join(subj)

    body = render_to_string("wgcharter/ballot_comment_mail.txt",
                            dict(block_comment=d, comment=c, ad=ad.name, charter=charter, pos=pos.pos))
    frm = ad.formatted_email()
    to = "The IESG <iesg@ietf.org>"
        
    if request.method == 'POST':
        cc = [x.strip() for x in request.POST.get("cc", "").split(',') if x.strip()]
        send_mail_text(request, to, frm, subject, body, cc=", ".join(cc))
            
        return HttpResponseRedirect(return_to_url)
  
    return render_to_response('wgcharter/send_ballot_comment.html',
                              dict(charter=charter,
                                   subject=subject,
                                   body=body,
                                   frm=frm,
                                   to=to,
                                   ad=ad,
                                   can_send=d or c,
                                   back_url=back_url,
                                  ),
                              context_instance=RequestContext(request))
        
class AnnouncementTextForm(forms.Form):
    announcement_text = forms.CharField(widget=forms.Textarea, required=True)

    def clean_announcement_text(self):
        return self.cleaned_data["announcement_text"].replace("\r", "")

@group_required('Area_Director','Secretariat')
def announcement_text(request, name, ann):
    """Editing of announcement text"""
    try:
        wg = Group.objects.get(acronym=name)
    except ObjectDoesNotExist:
        wglist = GroupHistory.objects.filter(acronym=name)
        if wglist:
            return redirect('wg_announcement_text', name=wglist[0].group.acronym)
        else:
            raise Http404

    charter = set_or_create_charter(wg)

    login = request.user.get_profile()

    if ann == "action":
        existing = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
    elif ann == "review":
        existing = charter.latest_event(WriteupDocEvent, type="changed_review_announcement")
    if not existing:
        if ann == "action":
           if next_approved_revision(wg.charter.rev) == "01":
              existing = default_action_text(wg, charter, login, "Formed")
           else:
              existing = default_action_text(wg, charter, login, "Rechartered")
        elif ann == "review":
            existing = default_review_text(wg, charter, login)

    form = AnnouncementTextForm(initial=dict(announcement_text=existing.text))

    if request.method == 'POST':
        form = AnnouncementTextForm(request.POST)
        if "save_text" in request.POST and form.is_valid():
            t = form.cleaned_data['announcement_text']
            if t != existing.text:
                e = WriteupDocEvent(doc=charter, by=login)
                e.by = login
                e.type = "changed_%s_announcement" % ann
                e.desc = "WG %s text was changed" % ann
                e.text = t
                e.save()
                
                charter.time = e.time
                charter.save()
            return redirect('wg_view', name=wg.acronym)

        if "regenerate_text" in request.POST:
           if ann == "action":
              if next_approved_revision(wg.charter.rev) == "01":
                 e = default_action_text(wg, charter, login, "Formed")
              else:
                 e = default_action_text(wg, charter, login, "Rechartered")
           elif ann == "review":
              e = default_review_text(wg, charter, login)
           # make sure form has the updated text
           form = AnnouncementTextForm(initial=dict(announcement_text=e.text))

        if "send_text" in request.POST and form.is_valid():
           msg = form.cleaned_data['announcement_text']
           import email
           parsed_msg = email.message_from_string(msg.encode("utf-8"))

           send_mail_text(request, parsed_msg["To"],
                          parsed_msg["From"], parsed_msg["Subject"],
                          parsed_msg.get_payload())
           return redirect('wg_view', name=wg.acronym)

    return render_to_response('wgcharter/announcement_text.html',
                              dict(charter=charter,
                                   announcement=ann,
                                   back_url=charter.get_absolute_url(),
                                   announcement_text_form=form,
                                   ),
                              context_instance=RequestContext(request))

class BallotWriteupForm(forms.Form):
    ballot_writeup = forms.CharField(widget=forms.Textarea, required=True)

    def clean_ballot_writeup(self):
        return self.cleaned_data["ballot_writeup"].replace("\r", "")
        
@group_required('Area_Director','Secretariat')
def ballot_writeupnotes(request, name):
    """Editing of ballot write-up and notes"""
    try:
        wg = Group.objects.get(acronym=name)
    except ObjectDoesNotExist:
        wglist = GroupHistory.objects.filter(acronym=name)
        if wglist:
            return redirect('wg_ballot_writeupnotes', name=wglist[0].group.acronym)
        else:
            raise Http404

    charter = set_or_create_charter(wg)

    started_process = charter.latest_event(type="started_iesg_process")
    if not started_process:
        raise Http404()

    login = request.user.get_profile()

    approval = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
    
    existing = charter.latest_event(WriteupDocEvent, type="changed_ballot_writeup_text")
    if not existing:
        existing = generate_ballot_writeup(request, charter)

    reissue = charter.latest_event(DocEvent, type="sent_ballot_announcement")
        
    form = BallotWriteupForm(initial=dict(ballot_writeup=existing.text))

    if request.method == 'POST' and "save_ballot_writeup" in request.POST or "issue_ballot" in request.POST:
        form = BallotWriteupForm(request.POST)
        if form.is_valid():
            t = form.cleaned_data["ballot_writeup"]
            if t != existing.text:
                e = WriteupDocEvent(doc=charter, by=login)
                e.by = login
                e.type = "changed_ballot_writeup_text"
                e.desc = "Ballot writeup was changed"
                e.text = t
                e.save()

            if "issue_ballot" in request.POST and approval:
                if has_role(request.user, "Area Director") and not charter.latest_event(GroupBallotPositionDocEvent, ad=login, time__gte=started_process.time):
                    # sending the ballot counts as a yes
                    pos = GroupBallotPositionDocEvent(doc=charter, by=login)
                    pos.type = "changed_ballot_position"
                    pos.ad = login
                    pos.pos_id = "yes"
                    pos.desc = "[Ballot Position Update] New position, %s, has been recorded for %s" % (pos.pos.name, pos.ad.name)
                    pos.save()

                msg = generate_issue_ballot_mail(request, charter)
                send_mail_preformatted(request, msg)

                e = DocEvent(doc=charter, by=login)
                e.by = login
                e.type = "sent_ballot_announcement"
                e.desc = "Ballot has been issued by %s" % login.name
                e.save()

                return render_to_response('wgcharter/ballot_issued.html',
                                          dict(charter=charter,
                                               back_url=charter.get_absolute_url()),
                                          context_instance=RequestContext(request))
                        

    return render_to_response('wgcharter/ballot_writeupnotes.html',
                              dict(charter=charter,
                                   back_url=charter.get_absolute_url(),
                                   ballot_issued=bool(charter.latest_event(type="sent_ballot_announcement")),
                                   ballot_writeup_form=form,
                                   reissue=reissue,
                                   approval=approval,
                                   ),
                              context_instance=RequestContext(request))

@group_required('Secretariat')
def approve_ballot(request, name):
    """Approve ballot, changing state, copying charter"""
    try:
        wg = Group.objects.get(acronym=name)
    except ObjectDoesNotExist:
        wglist = GroupHistory.objects.filter(acronym=name)
        if wglist:
            return redirect('wg_approve_ballot', name=wglist[0].group.acronym)
        else:
            raise Http404

    charter = set_or_create_charter(wg)

    login = request.user.get_profile()

    e = charter.latest_event(WriteupDocEvent, type="changed_action_announcement")
    if not e:
       if next_approved_revision(wg.charter.rev) == "01":
          announcement= default_action_text(wg, charter, login, "Formed").text
       else:
          announcement = default_action_text(wg, charter, login, "Rechartered").text
    else:
        announcement = e.text

    if request.method == 'POST':
        new_state = GroupStateName.objects.get(slug="active")
        new_charter_state = State.objects.get(type="charter", slug="approved")

        save_charter_in_history(charter)
        save_group_in_history(wg)

        prev_state = wg.state
        prev_charter_state = charter.get_state()
        wg.state = new_state
        charter.set_state(new_charter_state)

        e = DocEvent(doc=charter, by=login)
        e.type = "iesg_approved"
        e.desc = "IESG has approved the charter"
        e.save()
        
        change_description = e.desc + " and WG state has been changed to %s" % new_state.name
        
        e = log_state_changed(request, charter, login, prev_state)
                    
        wg.time = e.time
        wg.save()

        ch = get_charter_for_revision(wg.charter, wg.charter.rev)

        filename = os.path.join(charter.get_file_path(), ch.name+"-"+ch.rev+".txt")
        try:
           source = open(filename, 'rb')
           raw_content = source.read()

           new_filename = os.path.join(charter.get_file_path(), 'charter-ietf-%s-%s.txt' % (wg.acronym, next_approved_revision(ch.rev)))
           destination = open(new_filename, 'wb+')
           destination.write(raw_content)
           destination.close()
        except IOError:
           raise Http404("Charter text %s" % filename)

        charter.rev = next_approved_revision(charter.rev)
        charter.save()
        
        email_secretariat(request, wg, "state-%s" % new_charter_state.slug, change_description)

        # send announcement
        send_mail_preformatted(request, announcement)

        return HttpResponseRedirect(charter.get_absolute_url())
  
    return render_to_response('wgcharter/approve_ballot.html',
                              dict(charter=charter,
                                   announcement=announcement,
                                   wg=wg),
                              context_instance=RequestContext(request))

