"""schedule system views
"""
from django.conf import settings
from django.core.cache import cache
from django.shortcuts import *
from django.template import loader
from django.http import HttpResponseRedirect, HttpResponseForbidden
from django.http import HttpResponse, Http404
from django.template import RequestContext
from django.utils import simplejson
from django.db.models import Q
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
from datetime import datetime, timedelta
from urllib import quote, unquote
from pycon.features.decorators import *
from pycon.features.models import *
from pycon.propmgr.models import Proposal
from pycon.core import safe_ascii_encode
from templatetags.schedule import *
from models import *
from forms import *
import django.newforms
from pycon.core import safe_ascii_encode

import itertools
from operator import attrgetter

def _getdefault(name, default=None):
    try:
        default = getattr(settings, name)
    except: pass
    return default

COOKIE_NAME = _getdefault('SCHEDULE_COOKIE_NAME', 'pycon2009talksched')
COOKIE_PATH = _getdefault('SCHEDULE_COOKIE_PATH', '/2009/conference/schedule/')

EventTypeCSSMap = {'E': 'event30',
                   'P': 'plenary',
                   'B': 'break',
                   'M': 'meeting',
                   'T': 'tutorial',
                   'S': 'social',
                   'X': 'sprint',
                   'O': 'opensp'}

EventTypesAllRooms = ('P', 'B',)

class TimeTableCell(object):
    def __init__(self, time=None, sched=None, cls=None,
                 selected=SELECTED_EVENT_STATUS_CHOICES.find('De-Selected'),
                 rowspan=1, colspan=1):
        self.selected = selected
        self.time  = time
        self.sched = sched
        self.event = None
        if self.sched: self.event = sched.event
        self.colspan = colspan
        self.rowspan = rowspan
        if cls is None:
            self.cls = 'empty'
            if self.time is not None: self.cls='time'
            if self.event is not None:
                et = self.event.type
                self.cls = EventTypeCSSMap.get(et, 'event30')
                if et == 'E' and self.event.duration >= 45: self.cls = 'event45'

    def __repr__(self):
        if self.is_time: return '<' + str(self.time) +'>'
        if self.event is None: return 'empty'
        return '<' + safe_ascii_encode(unicode(self.event)) +'>'

    @property
    def is_time(self):
        ## because midnight can evaluate to False in the template >.<
        return self.time is not None

    @property
    def span(self):
        res = ''
        if self.colspan > 1: res += " colspan='%d'" % self.colspan
        if self.rowspan > 1: res += " rowspan='%d'" % self.rowspan
        return res

    @property
    def id(self):
        if self.event:
            id = 'E%.3d' % self.event.id
            if self.event.type == 'T': id += 'T%d' % self.sched.id
            return id
        return ''

    @property
    def css_name(self):
        if self.event and self.event.type == 'T':
            return "name='E%.3d'" % self.event.id
        return ''

    @property
    def css_id(self):
        if self.id: return "id='%s'" % self.id
        return ''

    @property
    def css_class(self):
        if not self.cls: return ''
        return 'class="%s"' % self.cls

    @property
    def js_events(self):
        events = ''
        if self.event:
            if self.event.type == 'T':
                return 'onclick="mouseclick_tutorial(this);" onmouseover="mouseover_tutorial(this);" onmouseout="mouseout_tutorial(this);"'
            if self.event.type != 'B':
                return 'onclick="mouseclick(this);"'
        return events

def timetable_to_rooms(table):
    room_divisions = table.room_divisions_set.all().select_related()
    header_ids = [ division.room_id for division in room_divisions ]
    all_ids = set(header_ids)
    room_map = dict( (division.room_id, (ind+1, 1))
                     for ind, division in enumerate(room_divisions) )
    header_rooms = [ division.room for division in room_divisions ]
    for ind, room in ( (ind, division.room)
                            for ind, division in enumerate(room_divisions)
                                if division.include_parents ):
        for parent in room.parents.all():
            if parent.id in all_ids: continue
            children = [ child.id for child in parent.room_divisions_set.all() ]
            all_ids.add(parent.id)
            colspan=1
            for roomid in header_ids[ind+1:]:
                if roomid not in children: break
                colspan+=1
            room_map[parent.id] = (ind+1, colspan)
    return all_ids, header_rooms, room_map

def generate_table_cells(table, selected={}):

    desel = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')

    all_rooms, header_rooms, room_map = timetable_to_rooms(table)

    query_date = Q(start__year=table.date.year,
                   start__month=table.date.month,
                   start__day=table.date.day)
    if table.before and table.after:
        query_date = Q(start__range=(
            datetime(table.date.year, table.date.month, table.date.day,
                     table.after.hour, table.after.minute, table.after.second),
            datetime(table.date.year, table.date.month, table.date.day,
                     table.before.hour, table.before.minute, table.before.second)))
    elif table.before:
        query_date &= Q(start__lte=datetime(
            table.date.year, table.date.month, table.date.day,
            table.before.hour, table.before.minute, table.before.second))
    elif table.after:
        query_date &= Q(start__lte=datetime(
            table.date.year, table.date.month, table.date.day,
            table.after.hour, table.after.minute, table.after.second))
    query_rooms = Q(room__in = all_rooms)
    if table.nullroom:
        query_rooms |=  Q(room__isnull=True)
    talks = ScheduledEvent.objects.filter(query_date, query_rooms).order_by('start')
    times = sorted(set(itertools.chain((event.start for event in talks),
                                       (event.stop  for event in talks))))

    ## init table
    tablebody = [ None ] * len(times)
    empty_cell = TimeTableCell()
    for ind, row in enumerate(tablebody):
        tablebody[ind] = [ empty_cell ] * (len(header_rooms)+1)
    ## iter over the events by thier start times
    lastrow = -1
    for start, events in itertools.groupby(talks, lambda x: x.start):
        ## Init time col and update the previous time cell rowspan
        row = times.index(start)
        rowcells = tablebody[row]
        rowcells[0] = TimeTableCell(time=start)
        tablebody[lastrow][0].rowspan = row-lastrow
        for spanned in xrange(lastrow+1, row): tablebody[spanned][0] = None
        lastrow = row

        ## iter over the events at this start time, assigning cells & spanning
        for event in events:
            endrow = times.index(event.stop)
            if event.event.type in EventTypesAllRooms and event.room_id is None:
                col, colspan = 1, len(header_rooms)
            else:
                col, colspan = room_map.get(event.room_id, (1, len(header_rooms)))
            rowcells[col] = cell = TimeTableCell(sched=event,
                                                 rowspan=endrow-row,
                                                 colspan=colspan,
                                                 selected=selected.get(event.event.id, desel))
            if colspan > 1:
                rowcells[col+1:col+colspan] = [None]*(colspan-1)
            if cell.rowspan > 1:
                span = [None] * colspan
                for spanrow in itertools.islice(tablebody, row+1, endrow):
                    spanrow[col:col+colspan] = span

    ## update the last time entry spanning
    for spanned in xrange(lastrow+1, len(times)): tablebody[spanned][0] = None
    tablebody[lastrow][0].rowspan = len(times) - lastrow

    ## clear the spanned None entries and return result
    return header_rooms, [ filter(None, row) for row in tablebody ]


def sync_selected_events(events, onpage, actual, state, sessionid="", user=None):
    if user is not None and not user.is_anonymous():
        baseQuery = SelectedEvent.objects.filter(user=user)
        sessionid = ""
    else:
        baseQuery = SelectedEvent.objects.filter(sessionid=sessionid)
        user = None

    clearids = onpage - actual
    tosetids = actual - onpage
    if clearids:
        ### assignments have been removed or events re-scheduled.
        for id in clearids:
            #events[id] = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')
            del events[id]
        for entry in baseQuery.filter(event__in=clearids):
            #entry.status = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')
            #entry.save()
            entry.delete()
    if tosetids:
        ### assignments have been added or events re-scheduled
        for id in tosetids: events[id] = state
        haveset = set()
        for entry in baseQuery.filter(event__in=tosetids):
            entry.status = state
            entry.save()
            haveset.add(entry.event_id)
        for id in tosetids-haveset:
            try: Event.objects.get(pk=id)
            except Event.DoesNotExist:
                ## this event has been removed from the system, html cache out of date.
                #events[id] = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')
                del events[id]
            else:
                entry = SelectedEvent(sessionid=sessionid, event_id=id,
                                  status=state, user=user)
                entry.save()
    return bool(clearids) or bool(tosetids)

def really_get_sessionid(request):
    if request.session.session_key is not None:
        return request.session.session_key
    res = request.COOKIES.get('sessionid', None)
    if res is None:
        request.session['sched']=True
    return request.session.session_key

def sync_cookie(request, name, path, events=None):
    if name not in request.COOKIES and request.user.is_anonymous():
        return quote(simplejson.dumps({ 'events': {}, 'resync': 0 }))

    sessionid = really_get_sessionid(request)

    if sessionid is None and request.user.is_anonymous(): return None

    if events is None: events = {}
    bSave = False
    bReSync = name not in request.COOKIES


    if request.user.is_anonymous():
        baseQuery = SelectedEvent.objects.filter(sessionid=sessionid)
    else:
        baseQuery = SelectedEvent.objects.filter(user=request.user)

    if name in request.COOKIES:
        ## update the DB for those not matching
        try:
            base = simplejson.loads(unquote(request.COOKIES[name]))
            if base is None or (not request.user.is_anonymous() and not base.get('resync',1)):
                raise SelectedEvent.DoesNotExist
            events.update((int(key,10), val) for key, val in base['events'].iteritems())
            bSave = bool(base.get('resync', 0))
        except SelectedEvent.DoesNotExist: bReSync=True
        except:
            if settings.DEBUG: raise
            bReSync = True ## something is broken in the cookie, trash it!
        else:
            selst = SELECTED_EVENT_STATUS_CHOICES.find('Selected')
            desst = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')
            selids = set(int(key) for key, val in events.iteritems() if val == selst)

            ## set all de-selected selected events to selected
            if selids:
                for sel in baseQuery.filter(status=desst, event__in=selids):
                    sel.status = selst
                    sel.save()

            ## set all selected events which are NOT set in cookie to deselected
            query = baseQuery.filter(status=selst)
            if selids: query = query.exclude(event__in=selids)
            for sel in query:
                #sel.status = desst
                #sel.save()
                sel.delete()

            ## create new selected event rows for thise not already in the DB
            sess, user = sessionid, None
            if not request.user.is_anonymous():
                sess, user = "", request.user
            dbselids = set(sel['event'] for sel in baseQuery.filter(
                                status=selst).values('event'))
            for id in selids - dbselids:
                try: Event.objects.get(pk=id)
                except Event.DoesNotExist:
                    ## this event has been removed from the system, html cache out of date.
                    #events[id] = SELECTED_EVENT_STATUS_CHOICES.find('De-Selected')
                    del events[id]
                    bSave = True
                else:
                    sel = SelectedEvent(sessionid=sess, event_id=id,
                                        status=selst, user=user)
                    sel.save()

    if bReSync:
        bSave = True
        for sel in baseQuery.exclude(status=SELECTED_EVENT_STATUS_CHOICES.find(
                                                'De-Selected')).distinct():
            events[sel.event_id] = sel.status



    if not request.user.is_anonymous():
        #### session chairing events:
        sech = SELECTED_EVENT_STATUS_CHOICES.find('Session Chair')
        actual = set(sch['event'] for sch in
                        request.user.events_session_chairing_set.values('event'))
        onpage = set(int(key) for key, val in events.iteritems() if val == sech)
        bSave |= sync_selected_events(events, onpage, actual, sech,
                                      sessionid, request.user)

        seru = SELECTED_EVENT_STATUS_CHOICES.find('Session Runner')
        actual = set(sch['event'] for sch in
                        request.user.events_session_runner_set.values('event'))
        onpage = set(int(key) for key, val in events.iteritems() if val == seru)
        bSave |= sync_selected_events(events, onpage, actual, seru,
                                      sessionid, request.user)

        #### authoring events:
        auth = SELECTED_EVENT_STATUS_CHOICES.find('Author')
        onpage = set(int(key) for key, val in events.iteritems() if val == auth)
        props  = set(ent['id'] for ent in
                        request.user.proposals_submitted_set.filter(
                            status='A', published=True).values('id'))
        props |= set(ent['id'] for ent in
                        request.user.proposals_coauthored_set.filter(
                            status='A', published=True).values('id'))
        actual = set(ent['id'] for ent in
                        Event.objects.filter(presenters__user=request.user).values('id'))
        if props:
            actual |= set(ent['id'] for ent in
                            Event.objects.filter(proposal__in=props).values('id'))
        bSave |= sync_selected_events(events, onpage, actual, auth,
                                      sessionid, request.user)

    return bSave and quote(simplejson.dumps({ 'events': events, 'resync': 0 })) or None

def get_assignable_scheds(user, event):
    failure = (False, False, None, None, None)
    if (user.is_anonymous() or event.type in EVENT_TYPES_NON_CHAIRED):
        return failure
    scheds = event.scheduledevent_set.filter(session_chair__isnull=True)
    if not scheds:
        scheds = event.scheduledevent_set.filter(session_runner__isnull=True)
        if not scheds: return failure
    start, end = scheds[0].get_session()

    if user.events_session_chairing_set.filter(stop__gt=start, start__lt=end).count() != 0:
        return failure
    if user.events_session_runner_set.filter(stop__gt=start, start__lt=end).count() != 0:
        return failure

    allscheds = ScheduledEvent.objects.filter(stop__gt=start, start__lt=end)
    try:
        pres = Presenter.objects.get(user=user)
        eids = [ sched.event_id for sched in allscheds if sched.event.proposal_id is None ]
        if pres.event_set.filter(pk__in=eids).count() != 0: return failure
    except Presenter.DoesNotExist: pass
    pids = [sched.event.proposal_id for sched in allscheds if sched.event.proposal_id is not None]
    if user.proposals_submitted_set.filter(pk__in=list(pids)).count() != 0: return failure
    if user.proposals_coauthored_set.filter(pk__in=list(pids)).count() != 0: return failure

    return (bool(scheds[0].same_session_set.filter(session_chair__isnull=True).count()),
            bool(scheds[0].same_session_set.filter(session_runner__isnull=True).count()),
            start, end, scheds[0].same_session_set)

def get_unassignable_scheds(user, event):
    failure = (False, False, None, None, None)
    if (user.is_anonymous() or event.type in EVENT_TYPES_NON_CHAIRED
        or not user.has_perm('schedule.can_unassign_self_as_chair')):
        return failure
    can_unchair, can_run = False, False
    chair_base = event.scheduledevent_set.filter(session_chair=user)
    can_unchair = bool(chair_base.count())
    allscheds = []
    if can_unchair:
        start, stop = chair_base[0].get_session()
        allscheds = chair_base[0].same_session_set
        can_unchair = bool(allscheds.filter(session_chair=user).count())
    else:
        run_base = event.scheduledevent_set.filter(session_runner=user)
        if not run_base: return failure
        start, stop = run_base[0].get_session()
        if not allscheds:
            allscheds = run_base[0].same_session_set
    can_unrun = bool(allscheds.filter(session_runner=user).count())
    return can_unchair, can_unrun, start, stop, allscheds

def HttpResponseRedirectSched(request, eid):
    if getattr(request.META, 'HTTP_REFERER', ''):
        ref=request.META.HTTP_REFERER[request.META.HTTP_REFERER.find('/', 7):]
        uri = reverse('schedule-event',None,(),{'eid': eid})
        if uri == ref:
            return HttpResponseRedirect(uri)
    return HttpResponseRedirect(reverse('schedule',None,(),{}))

def prop_redirect(request, pid):
    p = get_object_or_404(Proposal, pk=pid)
    es = p.event_set.all()
    if not len(es): raise Http404
    return HttpResponseRedirect(reverse('schedule-event',None,(),{'eid': es[0].id}))

@login_required
@feature_required("ViewSchedule", auto_create_feat=True)
def assign_chair(request, eid, runner=False):
    try:
        event = Event.objects.get(pk=eid)
    except Event.DoesNotExist: pass
    else:
        can, can_run, start, stop, scheds = get_assignable_scheds(request.user, event)
        if runner: can=can_run
        if can:
            for sched in scheds:
                if runner:
                    sched.session_runner = request.user
                else:
                    sched.session_chair = request.user
                sched.save()
            try:
                if runner:
                    request.user.groups.get(name="Session Runners")
                else:
                    request.user.groups.get(name="Session Chairs")
            except Group.DoesNotExist:
                if runner:
                    request.user.groups.add(Group.objects.get(name="Session Runners"))
                else:
                    request.user.groups.add(Group.objects.get(name="Session Chairs"))
                request.user.save()
            if (getattr(settings,'SCHEDULE_EMAIL', False) and
                getattr(settings,'SCHEDULE_CHAIR_NOTIFY', False)):
                t = loader.get_template("schedule/email/chair_change.txt")
                c = {'start': start,
                     'stop': stop,
                     'room': scheds[0].room,
                     'scheds': scheds,
                     'assigned': True,
                     'unassigned': False,
                }
                n = 'Chair'
                if runner: n = 'Runner'
                subject = 'Session %s %s assigned to slot %s to %s %s' % (n,
                    safe_ascii_encode(request.user), start.strftime('%a %m:%M%p'),
                    stop.strftime('%m:%M%p'), scheds[0].room.name)
                message = t.render(RequestContext(request, c))
                from pycon.core import mail
                #print subject
                #print message # because dev does not have sendmail enabled.
                mail.send_mail(subject,
                               message, settings.SCHEDULE_FROM_EMAIL,
                               settings.SCHEDULE_CHAIR_NOTIFY)
    return HttpResponseRedirectSched(request, eid)

@login_required
@feature_required("ViewSchedule", auto_create_feat=True)
def unassign_chair(request, eid, runner=False):
    try:
        event = Event.objects.get(pk=eid)
    except Event.DoesNotExist: pass
    else:
        can, can_unrun, start, stop, scheds = get_unassignable_scheds(request.user, event)
        if runner: can = can_unrun
        if can:
            for sch in scheds:
                if runner:
                    sch.session_runner = None
                else:
                    sch.session_chair = None
                sch.save()
            if (getattr(settings, 'SCHEDULE_EMAIL', False) and
                getattr(settings, 'SCHEDULE_CHAIR_NOTIFY', False)):
                t = loader.get_template("schedule/email/chair_change.txt")
                c = {'start': start,
                     'stop': stop,
                     'room': scheds[0].room,
                     'scheds': scheds,
                     'assigned': False,
                     'unassigned': True,
                }
                n = 'Chair'
                if runner: n = 'Runner'
                subject = 'Session %s %s unassigned from slot %s to %s %s' % (n,
                    safe_ascii_encode(request.user), start.strftime('%a %m:%M%p'),
                    stop.strftime('%m:%M%p'), scheds[0].room.name)
                message = t.render(RequestContext(request, c))
                from pycon.core import mail
                #print subject
                #print message # because dev does not have sendmail enabled.
                mail.send_mail(subject,
                               message, settings.SCHEDULE_FROM_EMAIL,
                               settings.SCHEDULE_CHAIR_NOTIFY)
    return HttpResponseRedirectSched(request, eid)

def assign_runner(request, eid):
    return assign_chair(request, eid, True)

def unassign_runner(request, eid):
    return unassign_chair(request, eid, True)

@feature_required("ViewSchedule", auto_create_feat=True)
def help(request, **extra_context):
    help = loader.render_to_string('schedule/help.html', extra_context,
                                   context_instance=RequestContext(request))
    return HttpResponse(help)

@feature_required("ViewSchedule", auto_create_feat=True)
def event(request, eid, template='schedule/event.html', **extra_context):
    event = get_object_relations_or_404(Event,pk=eid)
    extra_context['event'] = event
    extra_context['title'] = unicode(event)
    extra_context['css_class'] = EventTypeCSSMap.get(event.type, 'event30')

    can_chair, can_run, start, end, scheds = get_assignable_scheds(request.user, event)
    can_unchair = False
    can_unrun = False
    if not can_chair or not can_run:
        can_unchair, can_unrun, start, end, scheds = get_unassignable_scheds(request.user, event)


    extra_context['can_unassign_self_as_chair'] = can_unchair
    extra_context['can_assign_self_as_chair'] = can_chair
    extra_context['can_unassign_self_as_runner'] = can_unrun
    extra_context['can_assign_self_as_runner'] = can_run
    extra_context['session_start'] = start
    extra_context['session_end'] = end
    extra_context['session_scheds'] = scheds
    if scheds:
        extra_context['session_room'] = scheds[0].room

    if event.type in ['T', 'E', 'P' ] or event.attachedfile_set.count():
        extra_context['can_view_attachments'] = True
        filetable = loader.render_to_string('schedule/attached_files.html',
                        {'files': event.public_attached_files(request.user)})
        extra_context['attached_files_table'] = filetable

        if (not request.user.is_anonymous() and
            Feature.objects.get(name="UploadEventMaterials").is_enabled and
            (request.user.has_perm('schedule.can_upload_material') or
             request.user in event.authors)):
            extra_context['can_add_attachment'] = True
            manipulator = AttachFileForm(str(int(eid, 10)))
            new_data = manipulator.flatten_data()
            form = forms.FormWrapper(manipulator, new_data, {})
            formfields = loader.render_to_string('schedule/attach_fields.html',
                {'form': form, 'extensions': ', '.join(AttachedFile.EXTENSIONS)})
            extra_context['attach_file_fields'] = formfields

    tooltip = loader.render_to_string(template, extra_context,
                                      context_instance=RequestContext(request))
    return HttpResponse(tooltip)

def cannot_upload(request, eid, **extra_context):
    if request.user.is_anonymous(): return False
    if request.user.has_perm('schedule.can_upload_material'): return True
    event = get_object_relations_or_404(Event,pk=eid, type__in=['T', 'E', 'P' ])
    return request.user in event.authors

@login_required
@feature_required("UploadEventMaterials", extra_func=cannot_upload, auto_create_feat=True)
def attach_file_view(request, eid, **extra_context):
    event = get_object_relations_or_404(Event,pk=eid, type__in=['T', 'E', 'P' ])
    extra_context['event'] = event
    extra_context['css_class'] = EventTypeCSSMap.get(event.type, 'event30')

    manipulator = AttachFileForm(str(int(eid, 10)))
    errors = {}
    if request.POST:
        new_data = request.POST.copy()
        new_data.update(request.FILES)
        errors = manipulator.get_validation_errors(new_data)
        if not errors:
            attfile = manipulator.save(new_data)
            return HttpResponseRedirect(reverse('schedule-event',None,(),{'eid': eid}))
    else:
        new_data = manipulator.flatten_data()
    extra_context['can_view_attachments'] = True
    filetable = loader.render_to_string('schedule/attached_files.html',
                                        {'files': event.public_attached_files(request.user)})
    extra_context['attached_files_table'] = filetable
    extra_context['can_add_attachment'] = True

    form = forms.FormWrapper(manipulator, new_data, errors)
    formfields = loader.render_to_string('schedule/attach_fields.html',
        {'form': form, 'extensions': ', '.join(AttachedFile.EXTENSIONS)})
    extra_context['attach_file_fields'] = formfields

    tooltip = loader.render_to_string('schedule/upload.html', extra_context,
                                      context_instance=RequestContext(request))
    return HttpResponse(tooltip)


@feature_required("ViewSchedule", auto_create_feat=True)
def timetables(request,**extra_context):
    name = COOKIE_NAME
    path = COOKIE_PATH
    cookie_data = sync_cookie(request, name, path)
    page_cache = None
    page_cache_name = '_'.join([str(request.user.is_anonymous() and 'anon_' or request.user.id),
                                str(bool(int(request.session.get('minimal_view', False)))),
                                'timetables'])
    if not settings.DEBUG:
        page_cache = cache.get(page_cache_name)
    if not page_cache:
        tables = TimeTable.objects.filter(public=True).select_related()

        for table in tables:
            table.header, table.cells = generate_table_cells(table)

        extra_context['sessionid'] = really_get_sessionid(request)
        extra_context['title'] = settings.CONFERENCE_NAME + ' Events Schedule'
        extra_context['tables'] = tables
        extra_context['cookie'] = {'name': name,
                                   'path': path,
                                   'expire': settings.SCHEDULE_COOKIE_MAX_AGE/(60*60*24)}
        page_cache = loader.render_to_string('schedule/timetables.html', extra_context,
                                             context_instance=RequestContext(request))
        cache_age = 60*60*5
        if not request.user.is_anonymous():
            if request.user.has_perm('schedule.change_event'):
                cache_age = 60*2
            elif request.user.has_perm('schedule.can_unassign_self_as_chair'):
                cache_age = 60*60
        if settings.DEBUG: cache_age = 1
        cache.set(page_cache_name, page_cache, cache_age)
    response = HttpResponse(page_cache)
    if cookie_data:
        age, exp = settings.SCHEDULE_COOKIE_MAX_AGE, None
        if age:
            dt = datetime.now() + timedelta(seconds=age)
            exp = dt.strftime("%a, %d-%b-%y %H:%M:%S %Z")
        response.set_cookie(name, cookie_data, max_age=age, expires=exp, path=path)
    return response

@feature_required("ViewSchedule")
def printsched(request,**extra_context):
    name = COOKIE_NAME
    path = COOKIE_PATH
    select_stat = {}
    tablenames = []
    options = {'floorplan': False, 'color': False,
               'selected': False, 'pagebreak': False}
    if request.method == "POST":
        tablenames = request.POST.getlist('tables')
        setoptions = request.POST.getlist('options')
        for opt in setoptions: options[opt] = True

    if options['selected']:
        cookie_data = sync_cookie(request, name, path, select_stat)

    if tablenames:
        tables = TimeTable.objects.filter(name__in=tablenames).select_related()
    else:
        tables = TimeTable.objects.filter(public=True).select_related()

    for table in tables:
        table.header, table.cells = generate_table_cells(table, select_stat)

    extra_context['sessionid'] = really_get_sessionid(request)
    extra_context['title'] = settings.CONFERENCE_NAME + ' Events Schedule'
    extra_context['tables'] = tables
    extra_context['options'] = options
    extra_context['cookie'] = {'name': name,
                               'path': path,
                               'expire': settings.SCHEDULE_COOKIE_MAX_AGE/(60*60*24)}
    page_cache = loader.render_to_string('schedule/print.html', extra_context,
                                         context_instance=RequestContext(request))
    response = HttpResponse(page_cache)
    return response


@feature_required("ScheduleFeed", auto_create_feat=True)
def ical(request, sessionid=None, uid=None, **extra_context):

    cookie_data = None
    user = None

    if uid is not None:
        if not request.user.is_anonymous():
            if int(request.user.id) != int(uid):
                return HttpResponseForbidden
            else:
                cookie_data = sync_cookie(request, COOKIE_NAME, COOKIE_PATH)
                user = request.user
        else:
            try:
                user = User.objects.get(pk=uid)
            except User.DoesNotExist:
                return HttpResponseForbidden

    if (sessionid is not None):
        try:
            Session.objects.get(pk=sessionid)
        except Session.DoesNotExist:
            ## rare case where session id was the temp session test cookie
            ## only an issue if uid is NOT supplied.
            if uid is None: raise HttpResponseForbidden
        if (sessionid == request.COOKIES.get('sessionid', None) and
            request.user.is_anonymous()):
            cookie_data = sync_cookie(request, COOKIE_NAME, COOKIE_PATH)

    scheds = []
    filename = 'pycon08_schedule.ics'
    name = settings.CONFERENCE_NAME + ' Schedule'
    title = settings.CONFERENCE_NAME + ' Schedule for all events.'
    if user is not None:
        evtids = [ sel['event'] for sel in user.selectedevent_set.exclude(
                                    status=0).values('event') ]
        filename = 'pycon08_' + user.username + '_schedule.ics'
        name = settings.CONFERENCE_NAME + ' Personal Schedule'
        title = settings.CONFERENCE_NAME + ' Personal Schedule for ' + str(user) + '. Including only selected events.'
        if evtids:
            scheds = ScheduledEvent.objects.filter(event__in=evtids).order_by('start')
    elif sessionid is not None:
        evtids = [ sel['event'] for sel in SelectedEvent.objects.filter(
                        sessionid=sessionid).exclude(status=0).values('event') ]
        filename = 'pycon08_' + sessionid + '_schedule.ics'
        name = settings.CONFERENCE_NAME + ' Personal Schedule'
        title = settings.CONFERENCE_NAME + ' Personal Schedule including only selected events.'
        if evtids:
            scheds = ScheduledEvent.objects.filter(event__in=evtids).order_by('start')
    else:
        scheds = ScheduledEvent.objects.all().order_by('start')

    tourl = lambda link: 'http://' + request.META['HTTP_HOST'] + link

    extra_context['name']      = name
    extra_context['title']     = title
    extra_context['userid']    = uid
    extra_context['sessionid'] = sessionid
    extra_context['utcnow']    = datetime.utcnow()
    extra_context['feedurl']   = tourl(request.path)
    extra_context['schedurl']  = tourl(settings.ROOT_URL + 'schedule/')
    extra_context['schedule']  = scheds

    icalfile = loader.render_to_string('schedule/sched.ics', extra_context,
                                       context_instance=RequestContext(request))
    icalfile = icalfile.replace("\r", "")

    response = HttpResponse(icalfile, mimetype="text/calendar")
    response['Content-Disposition']='attachment; filename="%s"' % filename
    if (cookie_data and really_get_sessionid(request) is not None and
        request.COOKIES.get(COOKIE_NAME, None) is not None):
        age, exp = settings.SCHEDULE_COOKIE_MAX_AGE, None
        if age:
            dt = datetime.now() + timedelta(seconds=age)
            exp = dt.strftime("%a, %d-%b-%y %H:%M:%S %Z")
        response.set_cookie(COOKIE_NAME, cookie_data, max_age=age, expires=exp,
                            path=COOKIE_PATH)
    return response


@login_required
def event_interest_counts(request, **extra_context):
    if not request.user.has_perm('schedule.can_view_selected_events'):
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view schedule interest stats',
                                       'title': 'Schedule Interest Stats'},
                              context_instance=RequestContext(request))
    def sort_count(lst):
        lst.sort(lambda (xc, xe), (yc, ye): cmp(yc, xc))
        return lst

    ### get the number of people being tracked
    selected = SelectedEvent.objects.all()
    num_selected = selected.count()
    num_people = len(set((se.user_id, se.sessionid) for se in selected))

    ### get the number of non-author, non-chair counts for each event
    event_counts = [(event.selectedevent_set.filter(status=1).count(), event)
                            for event in Event.objects.all() if event.type != 'B']

    ### sort by # counts
    sort_count(event_counts)

    ### for each room get the count, event list
    room_queue = {}
    for count, event in event_counts:
        if count == 0: break
        roomids = set(ent['room'] for ent in event.scheduledevent_set.exclude(
                                          room__isnull=True).values('room'))
        for id in roomids:
            room_queue.setdefault(id, []).append((count, event))

    ### turn the room id's into room objects, sort sublists of count, event
    room_counts = [ (Room.objects.get(pk=roomid), sort_count(counts))
                        for roomid, counts in room_queue.iteritems() ]

    ### sort the room counts by max count
    room_counts.sort(lambda (xr, xcl), (yr, ycl): cmp(ycl[0][0], xcl[0][0]))

    extra_context['num_selected'] = num_selected
    extra_context['num_people']   = num_people
    extra_context['event_counts'] = event_counts
    extra_context['room_counts']  = room_counts
    extra_context['title'] = 'Event Interest Counts'
    return render_to_response('schedule/event_counts.html', extra_context,
                              context_instance=RequestContext(request))

@login_required
def event_session_chairs(request, **extra_context):
    if not request.user.has_perm('schedule.view_session_chairs'):
        return render_to_response('propmgr/lack_permissions.html',
                                      {'operation': 'view session chairs',
                                       'title': 'Session Chairs'},
                              context_instance=RequestContext(request))
    slot_starts, slot_stops = ScheduledEvent.get_session_slots()
    bysession = []
    lacking = []
    for start, stop in itertools.izip(slot_starts, slot_stops):
        scheds = ScheduledEvent.objects.filter(start__gte=start, stop__lte=stop).exclude(
            event__type__in=EVENT_TYPES_NON_CHAIRED).order_by('room', 'start')
        if not scheds: continue
        bysession.append({ 'day': start.date(), 'start': start, 'end': stop, 'scheds': scheds})
    bychair = ScheduledEvent.objects.filter(session_chair__isnull=False).exclude(
                event__type__in=EVENT_TYPES_NON_CHAIRED).order_by('session_chair', 'start')
    for sch in bychair: sch.user = sch.session_chair
    byrunner = ScheduledEvent.objects.filter(session_runner__isnull=False).exclude(
                event__type__in=EVENT_TYPES_NON_CHAIRED).order_by('session_runner', 'start')
    for sch in byrunner: sch.user = sch.session_runner
    bystaff = sorted(itertools.chain(bychair, byrunner), key=lambda x: (x.user.id, x.start))
    extra_context['bysession'] = bysession
    extra_context['bychair']   = bychair
    extra_context['byrunner']  = byrunner
    extra_context['bystaff']   = bystaff
    extra_context['title']     = 'Session Chairs'
    return render_to_response('schedule/session_chairs.html', extra_context,
                              context_instance=RequestContext(request))

def no_attaching(*args, **kwdargs):
    jsondict = {}
    jsondict['status'] = 'error'
    jsondict['message'] = 'We are not currently accepting uploads to this event.'
    jsondict['table'] = ''
    jsondict['form_fields'] = ''
    jsondata = simplejson.dumps(jsondict)
    return HttpResponse('<textarea>' + jsondata + '</textarea>')

@feature("UploadEventMaterials",  auto_create_feat=True,
         disabled_func=no_attaching)
def attach_file(request, eid, **extra_context):
    event, status, message, table, fields = None, '', '', '', ''

    try:
        event = Event.objects.get(pk=eid)
    except Event.DoesNotExist:
        status='error'
        message='Error: Could not load event information.'
    else:
        if event.type not in ['T', 'E', 'P']:
            status='error'
            message='Error: Uploads are not allowed for this type of event'
        elif request.user.is_anonymous():
            status='error'
            message='Error: You must be logged in to upload files'
        elif (not request.user.has_perm('schedule.can_upload_material') and
              request.user not in event.authors):
            status='error'
            message='Error: You dont have permissions to upload files'

    if status != 'error':
        manipulator = AttachFileForm(str(int(eid, 10)))
        if request.POST:
            new_data = request.POST.copy()
            new_data.update(request.FILES)
            errors = manipulator.get_validation_errors(new_data)
            if not errors:
                attfile = manipulator.save(new_data)
                status = 'success'
                message = ("Success: attached file %s" %
                           attfile.get_file_short_name())
                manipulator = AttachFileForm(str(int(eid, 10)))
                errors = {}
                new_data = manipulator.flatten_data()
            else:
                status  = 'error'
                message = "Failure: Fix validation errors below"
        else:
            errors = {}
            new_data = manipulator.flatten_data()

        table = loader.render_to_string('schedule/attached_files.html',
                                        {'files': event.public_attached_files(request.user)})

        form = forms.FormWrapper(manipulator, new_data, errors)
        fields = loader.render_to_string('schedule/attach_fields.html',
                {'form': form, 'extensions': ', '.join(AttachedFile.EXTENSIONS)})

    jsondict = {}
    jsondict['status'] = status
    jsondict['message'] = message
    jsondict['table'] = table
    jsondict['form_fields'] = fields
    jsondata = simplejson.dumps(jsondict)
    return HttpResponse('<textarea>' + jsondata + '</textarea>')


class SignForm(django.newforms.Form):
    def abbr(name):
        return ''.join([ch for ch in name if ch.isupper()])

    scheduled_events = ScheduledEvent.objects.all()
    _days = sorted(set([(int(e.start.strftime("%j")), e.start.strftime("%a"), e.start.strftime("%A"))
                       for e in scheduled_events]))
    day_choices = [(d[1], d[2]) for d in _days]
    _rooms = sorted(set([e.room.name for e in scheduled_events if getattr(e, 'room')]))
    room_choices = [(abbr(rm), rm) for rm in _rooms]
    assert(len(_rooms)==len(room_choices))

    days = django.newforms.MultipleChoiceField(day_choices, widget=django.newforms.CheckboxSelectMultiple,
                                               required=True, initial=[c[0] for c in day_choices])
    rooms = django.newforms.MultipleChoiceField(room_choices, widget=django.newforms.CheckboxSelectMultiple,
                                                required=True, initial=[c[0] for c in room_choices])


@feature_required("ViewSchedule", auto_create_feat=True)
def select_signs(request, **extra_context):
    extra_context['title'] = settings.CONFERENCE_NAME + 'Room Schedule Signs-Selector'
    if not request.GET:
        sign_form = SignForm()
        return render_to_response('schedule/select_signs.html', dict(extra_context, form=sign_form),
                                  context_instance=RequestContext(request))
    sign_form = SignForm(request.GET)
    if not sign_form.is_valid():
        return render_to_response('schedule/select_signs.html', dict(extra_context, form=sign_form),
                                  context_instance=RequestContext(request))
    url_pieces = [settings.ROOT_URL + 'schedule/signs/']
    days = sign_form.clean_data['days']
    if len(days) != len(SignForm.day_choices):
        url_pieces.append('days%s/' % '.'.join(days))
    rooms = sign_form.clean_data['rooms']
    if len(rooms) != len(SignForm.room_choices):
        url_pieces.append('rms%s/' % '.'.join(sign_form.clean_data['rooms']))
    url = ''.join(url_pieces)
    return HttpResponseRedirect(safe_ascii_encode(url))

@feature_required("ViewSchedule", auto_create_feat=True)
def signs(request, rooms=None, days=None, **extra_context):
    if rooms is None:
        rooms = '.'.join([room[0] for room in SignForm.room_choices])
    if days is None:
        days = '.'.join([day[0] for day in SignForm.day_choices])
    extra_context['title'] = settings.CONFERENCE_NAME + ' Room Schedule Signs'
    room_lkup = dict(SignForm.room_choices)
    room_list = []
    for room in Room.objects.filter(name__in=[room_lkup[rm] for rm in rooms.split('.')]):
        room_events = [se for se in room.scheduledevent_set.all() if se.start.strftime("%a") in days]
        if not room_events:
            continue
        room_list.append(room)
        for parent_room in room.parents.all():
            room_events.extend([se for se in parent_room.scheduledevent_set.all() if se.start.strftime("%a") in days])
        id_set = set(se.id for se in room_events)
        assert(len(id_set)==len(room_events))
        conference_events = [se for se in ScheduledEvent.objects.all()
                             if se.start.strftime("%a") in days
                             and se.event.type in ['P', 'B']
                             and se.id not in id_set]
        all_events = []
        def event_with_attrs(event, this_room):
            event.day = event.start.strftime("%A")
            event.this_room = this_room
            return event
        for event in room_events:
            all_events.append(event_with_attrs(event, True))
        for event in conference_events:
            all_events.append(event_with_attrs(event, False))
        room.events_to_print = sorted(all_events, key=attrgetter('start'))
    return render_to_response('schedule/signs.html', dict(extra_context, room_list=room_list),
                              context_instance=RequestContext(request))


def get_speaker_data():
    speakers = list(Group.objects.get(name=u'Presenters').user_set.all())
    for spk in speakers:
        pids = set(x['id'] for x in spk.proposals_submitted.values('id'))
        pids |= set(x['id'] for x in spk.proposals_coauthored.values('id'))
        evs = list(Event.objects.filter(proposal__in=list(pids)))
        evs.extend(Event.objects.filter(presenters__user=spk.id))
        spk.events = evs
    speakers.extend(Presenter.objects.filter(user__isnull=True))


    speakers = {}
    info = {}
    ids = set()
    for sevent in ScheduledEvent.objects.filter(event__type__in=['E', 'P']).order_by('start'):
        event = sevent.event
        if event.id in ids: continue
        ids.add(event.id)
        for sprk in event.authors:
            speakers.setdefault(unicode(sprk),[]).append(event)
            info.setdefault(unicode(sprk),{})['speaker']=True
        chair = get_session_chair(event)
        if chair != '(None)':
            event.chair = unicode(chair)
            speakers.setdefault(event.chair,[]).append(event)
            info.setdefault(event.chair,{})['chair']=True
        runner = get_session_runner(event)
        if runner != '(None)':
            event.runner = unicode(runner)
            speakers.setdefault(event.runner,[]).append(event)
            info.setdefault(event.runner,{})['runner']=True
    speakers = [ (x, info[x], y) for x, y in sorted(speakers.iteritems(), key=lambda x: x[0].split()[-1]) ]
    return speakers

@feature_required("ViewSchedule", auto_create_feat=True)
def speaker_notes(request):

    extra_context = { 'speakers': get_speaker_data() }
    return render_to_response('schedule/speaker_notes.html', extra_context,
                              context_instance=RequestContext(request))
