"""Attendee Registration Forma
"""
from django.contrib.sites.models import Site
from django.contrib.auth.models import User
from django.conf import settings
from django.newforms import BaseForm, Form, ValidationError
from django.newforms import ChoiceField, DecimalField, CharField, EmailField
from django.newforms import ModelMultipleChoiceField
from django.newforms import Textarea, TextInput, Select, RadioSelect
from django.newforms import HiddenInput, BooleanField, MultipleChoiceField
from django.newforms import CheckboxSelectMultiple, IntegerField
from django.newforms import form_for_model, form_for_instance
from django.newforms.widgets import RadioFieldRenderer
from django.utils.translation import ugettext_lazy as _
from django.utils.encoding import force_unicode, smart_str
from django.core.urlresolvers import reverse
from django.template import RequestContext, Context, loader
from django.core.cache import cache
from datetime import datetime
from itertools import chain
from decimal import Decimal
from copy import deepcopy
from models import *
from fields import *
import urllib

## django has two conflicting ValidationError classes!!!
from django.newforms import ValidationError


PAYPAL_LOGIN   = getattr(settings, 'PAYPAL_LOGIN',   'testing')
PAYPAL_PARTNER = getattr(settings, 'PAYPAL_PARTNER', 'testing')
PAYPAL_URL     = getattr(settings, 'PAYPAL_URL',     'testing')

## RED_FLAG: must match data in admin on the number of sessions
reg_costs = ( ## EarlyBirdReg Feature is enabled
              { 'reg':  (('400.00', 'Corporate Early-Bird ($400)'),
                         ('220.00', 'Hobbyist Early-Bird ($220)'),
                         ('125.00', 'Student Early-Bird ($125)'),
                         ( '60.00', 'Tutorial Only Pass ($60.00)')),
                'tut':  ('0.00', '120.00', '200.00', '280.00') },
              ## EarlyBirdReg Feature is disabled
              { 'reg':  (('500.00', 'Corporate ($500)'),
                         ('300.00', 'Hobbyist ($300)'),
                         ('180.00', 'Student ($180)'),
                         ( '80.00', 'Tutorial Only Pass ($80.00)')),
                'tut':  ('0.00', '120.00', '200.00', '280.00') },
              ## at door costs (how to integrate?)
              { 'reg':  (('600.00', 'Corporate ($600)'),
                         ('400.00', 'Hobbyist ($400)'),
                         ('250.00', 'Student ($250)'),
                         ('100.00', 'Tutorial Only Pass ($100.00)')),
                'tut':  ('0.00', '120.00', '200.00', '280.00') },
            )

def donation_fields(field, **kwdargs):
    if field.name.startswith("donation"):
        return field.formfield(**kwdargs)
    return None

FakePayPalAckForm = form_for_model(PayPalAck, fields=PayPalAck.post_fields)
#EditRegForm = form_for_model(Registration, formfield_callback=edit_fields)
_DonationForm = form_for_model(Invoice, formfield_callback=donation_fields)

class DonationForm(_DonationForm):
    def __init__(self, user, *args, **kwdargs):
        self.user = user
        initial = {'donation_name': unicode(user) }
        initial.update(kwdargs.get('initial', dict()))
        kwdargs['initial'] = initial
        super(DonationForm, self).__init__(*args, **kwdargs)
        self.fields['donation'].min_value=Decimal("0.00")
        self.fields['donation'].max_value=Decimal("1000.00")
        self.fields['donation'].help_text=_(u"(up to 1000, in US dollars)")
        self.fields['donation_name'].label=_('Donor name')
        self.fields['donation'].required = False
        self.fields['donation_name'].required = False
    def clean_donation(self):
        if not self.cleaned_data['donation']:
            return Decimal("0.00")
        return self.cleaned_data['donation']
    def clean_donation_name(self):
        if (self.cleaned_data['donation_name'] == u'' and
            self.cleaned_data['donation'] > Decimal("0.00")):
            raise ValidationError, (
            "The name under which the donation is being made must be given.")
        if not self.cleaned_data['donation']:
            return u''
        return self.cleaned_data['donation_name']

def EditRegForm(reg, *args, **kwdargs):
    return form_for_instance(
        reg, fields=Registration.changable)(*args, **kwdargs)

class RemovedRegEntryForm(Form):
    '''We need to keep track of removed entries to keep the data in
    later reg entries intact, and only loose the specific deleted entry.
    To do this, we replace the real entry with a hidden entry, keeping
    the prefix id unique.
    '''
    deleted = BooleanField(widget=HiddenInput, initial=True)

    def save(self, invoice=None):
        pass

class NewRegEntryForm(Form):
    sentinal    = BooleanField(widget=HiddenInput, initial=True)
    not_me      = BooleanField(
                    label="Check if this registration is for someone else:")
    name        = CharField(max_length="60",  help_text=_("(required)"),
                            label=_('Name of attendee'), )
    email       = EmailField(max_length="60", help_text=_("(required)"),
                             label=_('E-mail address'), )
    reg_type    = ChoiceField(
        label=_('Registration type:'),
        widget=RadioSelect(renderer=RadioFieldInlineRenderer))
    sample_badge = HtmlInjectionField()
    badge_name  = CharField(
        max_length="60", required=False, label="Name for badge:",
        help_text=_('(if different from the "Name of attendee" field, above)'))
    badge_text1 = CharField(max_length="60", required=False,
                            label="Badge text, line 1:",
                            help_text=_("(brief information for your badge:"))
    badge_text2 = CharField(max_length="60", required=False,
                            label="Badge text, line 2:",
                            help_text="organization, project, email, etc.)")
    shirt_size  = ChoiceField(choices=SHIRT_SIZES, initial='L',
                              widget=RadioSelect(
                                renderer=RadioFieldInlineRenderer))
    shirt_type  = ChoiceField(choices=SHIRT_TYPES, initial='M',
                              widget=RadioSelect(
                                renderer=RadioFieldInlineRenderer))
    food_options = MultipleChoiceField(
        choices=zip(Registration.food_options, Registration.food_option_labels),
        label=_('Please indicate any special dietary requirements '
                '(we will do our best to accommodate requests, '
                'but cannot guarantee availability):'),
        required=False,
        widget=CheckboxInlineSelectMultiple)
    email_ok    = BooleanField(initial=True,
                label="Check here if we may send email updates about PyCon:")
    listing_ok  = BooleanField(initial=True,
                label="Check here to permit inclusion in the delegate listing:")
    extra_info  = CharField(required=False, widget=Textarea,
                            label=_('Additional information'),
                            help_text=_('(special instructions, etc.)'))
    tutorials   = HtmlInjectionField(
        label=_('Tutorials (fees include lunch and break refreshments):'))

    def __init__(self, early_reg, user=None, sample_badge=False, at_door=False,
                 *args, **kwdargs):
        self.user = user
        self.early_reg = early_reg
        self.at_door = at_door
        if user is not None:
            initial = {}
            initial['name'] = unicode(user)[:60]
            initial['email'] = user.email
            initial.update(kwdargs.get('initial', dict()))
            kwdargs['initial'] = initial
        super(NewRegEntryForm, self).__init__(*args, **kwdargs)
        if user is None: self.fields.pop('not_me')
        mycosts = reg_costs[0 if early_reg else 1]
        if at_door: mycosts = reg_costs[-1]
        self.fields['reg_type'].choices = mycosts['reg']
        self.fields['reg_type'].initial = mycosts['reg'][0][0]
        self.reg_costs = [x for x,y in mycosts['reg']]
        self.tut_costs = mycosts['tut']
        self.sessions = []
        for session, label in SESSIONS:
            choices = [ (t.id, t) for t in
                Tutorial.objects.filter(status='Open', session=session)]
            if len(choices) == 0: continue
            choices.insert(0,('', 'None'))
            self.fields.setdefault(session,
                ChoiceField(label=label, initial='', required=False,
                    choices=choices, widget=RadioSelect(
                        renderer=TutorialRadioFieldRenderer)))
            self.sessions.append(session)
        if not len(self.sessions):
            self.fields.pop('tutorials')
        else:
            t = loader.get_template("attendeereg/tutorial_costs.html")
            self.fields['tutorials'].html = t.render(
                Context({'costs': mycosts['tut'][1:len(self.sessions)+1]}))
        if sample_badge:
            self.update_badge()
        else:
            self.fields.pop('sample_badge')

    def update_badge(self):
        '''update the badge url data from "dirty" form data'''
        sbadge = self.fields['sample_badge']
        data = self.data if self.data else {}
        initial = self.initial if self.initial else {}
        prefix = (self.prefix + '-') if self.prefix else ''
        badge_field_map = Registration.badge_field_map
        get_field = lambda name: data.get(prefix+name,
                                          initial.get(prefix+name, None))
        def set_field(d, n, v):
            if v: d[smart_str(badge_field_map.get(n, n))] = smart_str(v)
        def get_and_set_field(d,n): set_field(d, n, get_field(n))
        name = get_field('name')
        badge_name = get_field('badge_name')
        if not name and self.user: name = unicode(self.user)
        if not badge_name: badge_name = name
        gdict = {}
        ## RED_FLAG: should we special encode the names/text for unicode?
        set_field(gdict, 'name', name)
        set_field(gdict, 'badge_name', badge_name)
        get_and_set_field(gdict, 'badge_text1')
        get_and_set_field(gdict, 'badge_text2')
        if self.user:
            #gdict['id'] = self.user.id # sample badge says 'SAMPLE'
            hgs = [ g['name'] for g in self.user.groups.filter(
                name__in=Registration.group_flag_map.keys()).values('name') ]
            for key, val in Registration.group_flag_map.iteritems():
                if key in hgs: set_field(gdict, val, u'True')
        badge_html  = u'<div class="sample-badge"><img src="'
        badge_html += reverse('badge-preview') + u'?'
        badge_html += urllib.urlencode(gdict) ## RED_FLAG: may raise unicode error!
        badge_html += u'"/></div>'
        self.fields['sample_badge'].html = badge_html

    def clean_reg_type(self):
        """There is an issue with early bound special cross fields validators
        like this one. We have not processed teh tutorial data yet. So
        we are in a real bind here. We must back off to the data object
        for the post. yuck!
        """
        val = self.cleaned_data['reg_type']
        index = self.reg_costs.index(val)
        if index == 3:
            n = sum(1 for s in self.sessions
                    if self.data.get(self.prefix+'-'+s,'') != '')
            if n == 0:
                raise ValidationError, (
                    "You must select at least one tutorial for a tutorial pass")
        return Decimal(val)

    def subtotal(self):
        if not self.is_valid():
            if self.data: return Decimal("0.00")
            return Decimal(self.fields['reg_type'].initial)
        reg = self.cleaned_data['reg_type']
        ind = sum(1 for s in self.sessions if self.cleaned_data[s] != '')
        tut = Decimal(self.tut_costs[ind])
        return reg+tut

    def save(self, invoice):
        if not self.is_valid():
            raise ValidationError, "Something broke"
        reg = Registration()
        reg.invoice = invoice
        if self.user is not None and not self.cleaned_data['not_me']:
            reg.user = self.user
        reg.early_reg = self.early_reg
        reg.at_door = self.at_door
        reg.name = self.cleaned_data['name']
        reg.email = self.cleaned_data['email']
        index = self.reg_costs.index(self.data[self.prefix+'-reg_type'])
        if index == 0: reg.corporate = True
        if index < 3:  reg.conference = True
        if index == 2: reg.student = True
        if index == 3: reg.tutorial = True
        reg.cost = self.subtotal()
        for name in Registration.changable:
            if name in self.cleaned_data:
                setattr(reg, name, self.cleaned_data[name])
        if 'food_options' in self.cleaned_data:
            for option in self.cleaned_data['food_options']:
                setattr(reg, option, True)
        reg.save()
        tut_ids = [ self.cleaned_data[s] for s in self.sessions
                   if self.cleaned_data[s] != '']
        if tut_ids:
            tuts = Tutorial.objects.filter(pk__in=tut_ids)
            #assert(len(tut_ids) == len(tuts))
            reg.tutorial = True
            reg.tutorials = tuts
            reg.save()
        ## red_flag: send e-mail's here!?!? or in view?
        ## in view is safer in case of crash? hmmm...

        return reg

class ManualPayPalAckForm(Form):
    PNREF      = CharField(max_length=13)
    NAME       = CharField(max_length=61)
    EMAIL      = CharField(max_length=41)
    AUTHCODE   = CharField(required=False, widget=Textarea)
    RESULTMSG  = CharField(required=False, widget=Textarea)
    org_notes  = CharField(label=_('organizer notes'), widget=Textarea,
                           required=False)

    def __init__(self, invoice, ip, *args, **kwdargs):
        self.ip = ip
        self.invoice = invoice
        initial = kwdargs.setdefault('initial', dict())
        if 'NAME' not in initial: initial['NAME'] = unicode(invoice.user)
        if 'EMAIL' not in initial: initial['EMAIL'] = invoice.user.email
        super(ManualPayPalAckForm, self).__init__(*args, **kwdargs)

    def clean_PNREF(self):
        val = self.cleaned_data['PNREF']
        if PayPalAck.objects.filter(PNREF=val).count():
            raise ValidationError, (
                    "Ack with this PNREF already exists.")
        return val

    def save(self, commit=True):
        ack = PayPalAck()
        ack.invoice     = self.invoice
        ack.from_ip     = self.ip
        ack.INVOICE     = unicode(self.invoice)
        ack.CUSTID      = self.invoice.user.id
        ack.DESCRIPTION = self.invoice.get_desc()
        ack.RESULT      = 1
        ack.NAME        = self.cleaned_data['NAME']
        ack.EMAIL       = self.cleaned_data['EMAIL']
        ack.AUTHCODE    = self.cleaned_data['AUTHCODE']
        ack.RESULTMSG   = self.cleaned_data['RESULTMSG']
        ack.PNREF       = self.cleaned_data['PNREF']
        ack.org_notes   = self.cleaned_data['org_notes']
        if commit: ack.save()
        return ack

class PayPalCheckoutForm(Form):
    LOGIN        = CharField(max_length=100, widget=HiddenInput,
                             initial=PAYPAL_LOGIN)
    PARTNER      = CharField(max_length=100, widget=HiddenInput,
                             initial=PAYPAL_PARTNER)
    TYPE         = CharField(max_length=1,   widget=HiddenInput,
                             initial='S')
    AMOUNT       = DecimalField(max_digits=4, decimal_places=2,
                                             widget=HiddenInput)
    INVOICE      = CharField(max_length=9,   widget=HiddenInput)
    NAME         = CharField(max_length=60,  widget=HiddenInput, required=False)
    CUSTID       = CharField(max_length=11,  widget=HiddenInput, required=False)
    EMAIL        = EmailField(max_length=40, widget=HiddenInput, required=False)
    DESCRIPTION  = CharField(max_length=255, widget=HiddenInput, required=False)
    COMMENT1     = CharField(max_length=255, widget=HiddenInput, required=False)
    COMMENT2     = CharField(max_length=255, widget=HiddenInput, required=False)

    def __init__(self, invoice, *args, **kwdargs):
        self.invoice = invoice
        self.url = PAYPAL_URL
        initial = {}
        initial['AMOUNT'] = '%.2f' % invoice.total_cost
        initial['INVOICE'] = unicode(invoice)
        ## RED_FLAG: safe ascii_ize?
        #initial['NAME'] = unicode(invoice.user)[:60]
        initial['CUSTID'] = str(invoice.user.id)[:11]
        initial['EMAIL'] = invoice.user.email[:40]
        desc = invoice.get_desc()
        initial['DESCRIPTION'] = desc[:255]
        initial['COMMENT1'] = desc[:255]
        if len(desc) > 510: initial['COMMENT2'] = desc[255:510]
        if 'initial' in kwdargs:
            initial.update(kwdargs['initial'])
        kwdargs['initial'] = initial
        super(PayPalCheckoutForm, self).__init__(*args, **kwdargs)
        if 'COMMENT2' not in initial or not initial['COMMENT2']:
            self.fields.pop('COMMENT2')


class ChangeRequestForm(Form):
    message = CharField(widget=Textarea)

    mail_base = {
        'from_email': settings.ATTREG_REQUEST_FROM_EMAIL,
        'replyto_email': getattr(settings,'ATTREG_REQUEST_REPLYTO_EMAIL',None),
        'recipient_list': getattr(settings, 'ATTREG_REQUEST_CC', []),
    }

    def __init__(self, user, inv, reg, *args, **kwdargs):
        self.reg = reg
        self.inv = inv
        self.user = user
        super(ChangeRequestForm, self).__init__(*args, **kwdargs)
    def get_to_list(self):
        to = [self.user.email]
        if (self.reg and self.reg.invoice and
            self.reg.invoice.user_id != self.user.id):
            to.append(self.reg.invoice.user.email)
        elif self.inv and self.inv.user_id != self.user.id:
            to.append(self.inv.user.email)
        to.extend(self.mail_base['recipient_list'])
        return to
    def save(self, commit=True, sendmail=True):
        cr = ChangeRequest()
        cr.user = self.user
        if self.reg: cr.reg = self.reg
        if self.inv: cr.invoice = self.inv
        cr.text = self.cleaned_data['message']
        to_list = self.get_to_list()
        cr.to = u", ".join(to_list)
        if commit: cr.save()
        if sendmail: self.send_mail(cr, to_list)
        return cr
    def get_subject(self):
        subject = u'Change request for '
        if self.inv:
            subject += u'Invoice ' + unicode(self.inv)
        else:
            if self.reg.invoice:
                subject += u'Invoice ' + unicode(self.reg.invoice) + ' '
            subject += u'Registration R%d ' % (self.reg.id,)
            subject += self.reg.name
        return subject
    def send_mail(self, cr, to_list):
        data = deepcopy(self.mail_base)
        data['recipient_list'] = to_list
        data['subject'] = self.get_subject()
        t = loader.get_template("attendeereg/change_request.txt")
        data['message'] = t.render(Context({
            'settings': settings,
            'message': cr.text,
            'site': Site.objects.get_current(),
            'from_user': self.user,
            'registration': self.reg,
            'invoice': self.inv,
            }))
        from pycon.core.mail import send_mail
        return send_mail(**data)

class OptionalCommentForm(Form):
    mail_reg = BooleanField(initial=False,
                            label=_("Also send e-mails to registrations"),
                            help_text=_("(If reg has multiple registrations)"))
    comment  = CharField(required=False, widget=Textarea,
                label=_('Additional information'),
                help_text=_('(optional additional comments to invoice owner)'))

class ChangeRequestMultiChoiceIdField(MultipleChoiceField):
    widget = CheckboxSelectMultiple
    def __init__(self):
        super(ChangeRequestMultiChoiceIdField, self).__init__(required=False)
    def clean(self, value):
        if not value: return []
        if not isinstance(value, (list, tuple)): return []
        return ChangeRequest.objects.filter(id__in=[ int(x) for x in value ],
                                            handled=False)

class MarkRequestsHandled(Form):
    request = ChangeRequestMultiChoiceIdField()

    def save(self):
        if not self.is_valid(): raise ValidationError, "Something broke"
        for request in self.cleaned_data['request']:
            request.handled = True
            request.save()

class GetRegistration(Form):
    reg = IntegerField(label=_('Registration ID: R'))

    def clean_reg(self):
        try:
            return Registration.objects.get(pk=self.cleaned_data['reg'])
        except Registration.DoesNotExist:
            raise ValidationError, "Not a valid registration ID."

    @property
    def registration(self):
        if not self.is_valid(): return None
        return self.cleaned_data['reg']

class SelectUsersOrRegistrationsForm(Form):
    selected = MultipleChoiceField(
                    label=_('Search Users and Unconnected Registrations'))
    script   = HtmlInjectionField(label=u'',
                    html=(u'<script type="text/javascript"> '
                          u'addEvent(window, "load", function(e) { '
                          u'SelectFilter.init("id_selected", '
                          u'"Users and Unconnected Registrations", 0, '
                          u'"{% admin_media_prefix %}"); }); </script>'))

    def __init__(self, *args, **kwdargs):
        super(SelectUsersOrRegistrationsForm, self).__init__(*args, **kwdargs)
        self.fields['selected'].choices = self.get_choices()

    def clean_selected(self):
        selected = self.cleaned_data['selected']
        if not selected:
            raise ValidationError, (
                    "You must select at least one useror registration.")
        rids = [ ent[1:] for ent in selected if ent.startswith('R') ]
        uids = [ ent for ent in selected if not ent.startswith('R') ]
        users = User.objects.filter(id__in=uids).select_related()
        reges = Registration.objects.filter(id__in=rids).select_related()
        self._users = users
        self._registrations = reges
        return (users, reges)

    @property
    def users(self):
        return self.cleaned_data['selected'][0]

    @property
    def unconnected(self):
        return self.cleaned_data['selected'][1]

    @classmethod
    def regen_choices(cls):
        cache.delete('attendeereg_user_reg_choice_widget')
        return cls.get_choices()

    @classmethod
    def get_choices(cls):
        ## there must be a decorator for this
        choices = cache.get('attendeereg_user_reg_choice_widget')
        if choices is None:
            choices = cls.generate_choices()
            cache.set('attendeereg_user_reg_choice_widget', choices, 60*60*1)
        return choices

    @staticmethod
    def generate_choices():
        def un2data(ent):
            last, first = split_name(ent['name'])
            key = u'R' + unicode(ent['id'])
            val = u'%s <%s> (unconnected)' % (ent['name'], ent['email'])
            return (last, first, key, val)
        def us2data(u):
            val = u'%s <%s>' % (unicode(u), u.email)
            if u.has_reg: val += u' (registered)'
            if not u.is_active: val+=u'*'
            return (u.last_name, u.first_name, unicode(u.id), val)
        users = (us2data(u) for u in User.objects.extra(select={'has_reg':
            '(SELECT COUNT(*) FROM attendeereg_registration '
            ' WHERE attendeereg_registration.user_id = auth_user.id '
            '       AND attendeereg_registration.active = TRUE) > 0'}))
        unconnected = (un2data(r) for r in Registration.objects.filter(
            user__isnull=True).exclude(
            invoice__status='IV').values('name', 'email', 'id'))
        return [c[2:] for c in sorted(chain(users, unconnected))]




class RegSearch(Form):
    query = CharField(label="",required=False)

class UserForm(Form):
    # username=form.CharField(_('username'), max_length=30, required=False,  validator_list=[validators.isAlphaNumeric], help_text=_("Required. 30 characters or fewer. Alphanumeric characters only (letters, digits and underscores)."))
    first_name = CharField(_('first name'), required=False)
    last_name = CharField(_('last name'), required=False)
    # email = EmailField(_('e-mail address'), required=True)
    # password = models.CharField(_('password'), max_length=128, help_text=_("Use '[algo]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))

# class InvosceForm(form.Form):

class RegForm(Form):

    tutorials = ModelMultipleChoiceField(queryset=Tutorial.objects.all(), required=False)
    corporate   = BooleanField(_('corporate registration'))
    student     = BooleanField(_('student registration'))
    # early_reg   = BooleanField(_('early-bird registration'))
    door_reg    = BooleanField(_('door registration'), initial=True)
    conference  = BooleanField(_('conference registration'))
    tutorial    = BooleanField(_('tutorial registration'))

    vendor      = BooleanField()
    sponsor     = BooleanField()
    session     = BooleanField()
    speaker     = BooleanField()
    keynote     = BooleanField()

    cost        = DecimalField(max_digits=6, decimal_places=2)
    paid        = BooleanField()
    active      = BooleanField()

    badge_name  = CharField(required=False)
    badge_text1 = CharField(required=False)
    badge_text2 = CharField(required=False)

    shirt_size  = ChoiceField( choices=SHIRT_SIZES, initial='L')
    shirt_type  = ChoiceField( choices=SHIRT_TYPES, initial='M')

    vegetarian  = BooleanField()
    vegan       = BooleanField()
    kosher      = BooleanField()
    halal       = BooleanField()

    email_ok    = BooleanField(_('ok to e-mail'), initial=True)

    listing_ok  = BooleanField(_('ok to list name'),
                                      initial=True)
    # hotel       = CharField(required=False)
    # checkin     = DateTimeField(required=False)
    extra_info  = CharField(required=False)
