# Create your views here.
import re
from django.core.template import Template
from django.core.extensions import DjangoContext as Context
from django.models.core import sessions
from django.core import formfields
from django.models.roomy import schedules, rooms, talks
from django.core.extensions import render_to_response
from django.utils.httpwrappers import HttpResponseRedirect, HttpResponse
from django.core import template_loader
from django.views.decorators.auth import user_passes_test
from django.views.decorators.vary import vary_on_cookie
from django.views.decorators.cache import cache_page

import itertools
import datetime

room_divider = re.compile(" I{1,3}(/II)?(/III)?$")

table_room_divider_map = {
    "Bent Tree I/II/III": ("Bent Tree I", 3),
    "Bent Tree I/II": ("Bent Tree I", 2),
    "Bent Tree II/III": ("Bent Tree II", 2),
    "Bent Tree II/III": ("Bent Tree II", 2),
    "Ballroom A-J": ("Ballroom A-E", 2),
    "Preston Trail I/II/III": ("Preston Trail I", 3),
    "Preston Trail I/II": ("Preston Trail I", 2),
    "Preston Trail II/III": ("Preston Trail II", 2),
    "---" : ("Ballroom A-E", 1),
    "NerdBooks (offsite)" : ("Ballroom A-E", 8),
    "Mesquite": ("Ballroom A-E", 1),
    "Addison": ("Ballroom A-E", 1),
}

day_to_date_dict = { 'thursday': datetime.date(2006, 02, 23),
                     'friday':   datetime.date(2006, 02, 24),
                     'saturday': datetime.date(2006, 02, 25),
                     'sunday':   datetime.date(2006, 02, 26), }
day_to_date = lambda x: day_to_date_dict.get(
    hasattr(x, 'lower') and x.lower() or x, None)


def index(request, **public):
    info = dict((('interface_list',
                    [ dict((('url', url), ('title', title)))
                        for url, title in public.iteritems() ]),))
    return render_to_response("roomy/list", info,
                context_instance=Context(request) )

def do_all(request, dict):
    keylist={}
    for item in dict:
      keylist[item.id] = item.id
    request.session['talk_ids'] = keylist

def do_add(request):
    keylist={}
    if request.GET.has_key('filter') and request.GET['filter']=='True':
      keylist = request.session.get('talk_ids',{})
    for key in request.GET.keys():
      try:
        keylist[int(key)] = request.GET[key]
      except:
        pass # skip over keys that are non-numeric
    request.session['talk_ids'] = keylist

def do_check_coll(request):
    coll_list = []
    keylist = request.session.get('talk_ids',{})
    if len(keylist): # make sure list is not empty
      schedlist = schedules.get_list(id__in=keylist)
      for sc in schedlist:
        cc = schedules.get_count(starttime__gte=sc.starttime,
                                 starttime__lt=sc.stop,
                                 startdate__exact=sc.startdate,
                                 id__ne=sc.id, id__in=keylist,
                                 role__role__in=('T', 'B'))
        if cc:
          coll_list.append(sc.id)
    return coll_list

def do_clear(request):
#    print 'session ',request.session['talk_ids']
    request.session['talk_ids'] = {}
#    del request.session

def edit(request):
    info = {}
    info['filter'] = False
    info['title'] = 'PyCon 2006 - TimeTable App'
    info['conflicts'] = 0
    request.session['talk_ids'] = request.session.get('talk_ids',{})
    searchterm=''
    if request.REQUEST.has_key('search'):
      if ( request.REQUEST['search']!='--Search--' and
           request.REQUEST['search']!='' ):
        searchterm = request.REQUEST['search']
        info['resp'] = "Searched for '%s'"%searchterm
        info['filter'] = True
    sched_list = schedules.get_list(
        role__role__ne = 'H',
        order_by=['startdate', 'starttime','room',],
        talk__title__icontains=searchterm)
    info["sched_list"]=filter(lambda x: not x.is_unassigned_open(), sched_list)
    if request.REQUEST.has_key('add'):#add selected talk ids to session
        do_add(request)
    if request.REQUEST.has_key('all'):#add all talk ids to session
        do_all(request, info["sched_list"])
    if request.REQUEST.has_key('clear'):#clear all talk ids from session
        do_clear(request)
    if request.REQUEST.has_key('done'):#move on to final timetable
        do_add(request)
        return HttpResponseRedirect('..')

    coll_list = do_check_coll(request) #look for collisions

    info["talk_list"] = talks.get_list()
    info["room_list"] = rooms.get_list()
    info["talk_ids"]  = request.session['talk_ids']
    # add a checked/collision flag to each schedule item
    for item in info["sched_list"]:
      if item.talk_id in info["talk_ids"]:
        item.checked='1'
      if item.id in coll_list:
        item.coll='1'
        info['conflicts'] += 1

    return render_to_response("roomy/index", info,
        context_instance=Context(request) )

def new(request):
    info = {}
    info["sched_list"]=[]
    info['title'] = 'PyCon 2006 - TimeTable App'
    template = "roomy/new"
    if request.REQUEST.has_key('print'):
        info['include_floorplan'] = request.REQUEST['print'] == 'floorplan'
        template = "roomy/plain"
    if ((request.session and 'talk_ids' in request.session and
         request.session['talk_ids'].keys()) or (
        request.REQUEST.has_key('ical') and request.REQUEST['ical'] == 'all')):

        if request.REQUEST.has_key('ical'):
            if request.REQUEST['ical'] == 'all':
                sched_list=schedules.get_list(role__role__ne = 'H',
                    order_by=['startdate', 'starttime','room',])
                sched_list=filter(lambda x: not x.is_unassigned_open(),
                                  sched_list)
                filename = "pycon2006.ics"
            else:
                sched_list=schedules.get_list(role__role__ne = 'H',
                    id__in=request.session['talk_ids'].keys(),
                    order_by=['startdate', 'starttime','room',])
                filename = "mysched-pycon2006.ics"
            info["sched_list"]=sched_list
            res = HttpResponse(template_loader.get_template(
                'roomy/ical').render(Context(request, info)),
                               mimetype="text/calendar")
            res['Content-Disposition']='attachment; filename="%s"' % filename
            return res

        sched_list=schedules.get_list(id__in=request.session['talk_ids'].keys(),
                                      role__role__ne = 'H',
                                      order_by=['startdate', 'starttime','room',])
        info["sched_list"]=sched_list

        schind_by_room = {}
        for room in rooms.get_list():
          ind_list = [ ind+1 for ind, sched in enumerate(sched_list)
                                  if sched.room_id == room.id ]
          title = room.title
          match = room_divider.search(title)
          if match is not None: title = title[:match.start()]
          title = title.lower().replace(' ', '_').replace('-','')
          schind_by_room.setdefault(title,[]).extend(ind_list)
        info["schind_by_room"] = schind_by_room
        return render_to_response(template, info,
                                            context_instance=Context(request) )
    return HttpResponseRedirect('edit')

def catchall(request, url, redirect=""):
    #redirect to roomy root...
    redirect_to = request.path[:len(url)]
    if redirect:
        if redirect_to[-1] != "/":
            redirect_to += "/"
        redirect_to += redirect
    if redirect_to[-1] != "/":
        redirect_to += "/"
    return HttpResponseRedirect(redirect_to)

def signs(request):
    sched_list_by_room = schedules.get_list(
        role__role__ne="H",
        order_by=['room', 'startdate', 'starttime',],
        room__title__ne='---')
    sched_list_by_room = filter(lambda x: not x.is_unassigned_open(),
                                sched_list_by_room)
    features = schedules.get_list(role__role__exact="F")
    rdict = dict((room.id, room) for room in rooms.get_list())

    pre_ordered = []
    grid = itertools.groupby(sched_list_by_room, lambda sch: sch.room_id)
    def mkd(sched, isfeat):
        ## this held in crap is to get around the '---' room *sigh*
        ## also added code to work with schedules with no room
        ## for when this is fixed. After that is fixed the held_in code
        ## should be romoved, but someone might forget about signs
        ## and I would hate to have an obscure error crop up for no good
        ## reason.
        held_in = ""
        if isfeat:
            try:
                held_in = sched.get_room().title
                if held_in == '---': held_in = ""
            except rooms.RoomDoesNotExist: pass
        return { 'datetime': (sched.startdate, sched.starttime),
                 'schedule': sched,
                 'held_in': held_in,
                 'is_feature': isfeat}
    for room_id, schediter in grid:
        sched_list = [ mkd(sched, False) for sched in schediter ]
        ## only want features for days with talks.
        days = set(sd['datetime'][0] for sd in sched_list)
        sched_list.extend(mkd(feat, True) for feat in features
                            if (feat.room_id != room_id and
                                feat.startdate in days))
        sched_list.sort(lambda a, b: cmp(a['datetime'],b['datetime']))
        pre_ordered.append({ 'room_name': repr(rdict[room_id]),
                             'list': sched_list })

    return render_to_response("roomy/signs",
        { 'sched_list_by_room': pre_ordered },
        context_instance=Context(request) )

class timeinfo(object):
    def __init__(self, dtime, scheds):
        self.scheds = scheds
        self.time = dtime
        self.starts = len(scheds) > 0
    def has_starts(self):
        return self.starts
    def __cmp__(self, other):
        return cmp(self.time, other.time)
    def __repr__(self):
        return repr(self.time)

class TableCell(object):
    def __init__(self, data):
        self.data = data
        self.colspan = 1
        self.rowspan = 1
        self.isfeat  = False
        self.istime  = False
        self.isempty = False
        self.unassigned = False
    def url(self):
        if self.isfeat:
            return self.data.get_talk().url
        return self.data.url
    def talk_id(self):
        if self.isfeat:
            return self.data.talk_id
        return self.data.id
    def __repr__(self):
        if self.data is None: return ""
        try:
            if self.isfeat and self.data.get_room().title == '---':
                return repr(self.data.get_talk())
            return repr(self.data)
        except Exception, e:
            return str(e)

hardcoded_room_title_sort_table = [
    "Ballroom A-E",
    "Ballroom F-J",
    "Preston Trail I",
    "Preston Trail II",
    "Preston Trail III",
    "Bent Tree I",
    "Bent Tree II",
    "Bent Tree III",
]

def hardcoded_room_title_sort(room1, room2):
    try:
        return cmp(hardcoded_room_title_sort_table.index(room1.title),
                   hardcoded_room_title_sort_table.index(room2.title))
    except:
        return cmp(room1.title, room2.title)

def generate_tablebodies(info, limit_to_date=None):
    ## ok I am a massochist.
    ## I should comment this, as it is about as obvious as a 10K perl module
    ## We get a list of all the start times and stop times and turn that
    ## into a list of timeinfo's. A timeinfo is for a descrete time, and will
    ## contain a list of all talks which start at that time. A time info
    ## with an empty schedule list is only a talk stop time.
    ## This uniq set of times is the number of rows in the table.
    ## The number of columns is the number of rooms (filtering rooms which
    ## consist of multiple divided rooms and '---') plus one for the times.
    ## We then construct a list rows which are lists of 'True', one for each
    ## cell in the times(rows)Xrooms(cols) table. Each 'True' represents an
    ## empty cell. Then we start filling the cells with TableCells.
    ## Times row span down til the next start time. Any cells which are spanned
    ## must then be set to None. Rooms also span down rows til they reach their
    ## stop time. Rooms can also span columns if they take place in multiple
    ## divided rooms, or if they are 'Feature' talks which span all rooms.
    ## For feature talks, they are set in the first room column and span the
    ## rest. All spanned cells must be set to None. When all that is done,
    ## there are three types of objects in the cells of the master tablebody.
    ## TableCells which contain the data, rowspan, colspan, typeinfo. Empty
    ## Cells which contain True and spanned cells which contain 'None'.
    ## All the 'True' cells are replaced with TableCell's with isempty set to
    ## true. All 'None's are removed from the table as they are no longer
    ## needed. All the TableCell objects are ready to be rendered into html.
    ## this is a little wasteful as there will be tons of empty cells. We
    ## could try to identify adjacent empty cells and merge them to generate
    ## cleaner html, but that seems a bit much at this point.
    ##    -DougN
    scheds = schedules.get_list(order_by=['startdate'])
    all_rooms = rooms.get_list(order_by=['title'])
    room_list = filter(lambda x: x.title not in table_room_divider_map,
                       all_rooms)
    room_list.sort(hardcoded_room_title_sort)
    all_room_titles = dict((room.id, room.title) for room in all_rooms)
    roomind = dict((room.id, ind) for ind, room in enumerate(room_list))
    roomtitind = dict((room.title, ind) for ind, room in enumerate(room_list))

    def get_room_col_and_span(room_id):
        title = all_room_titles.get(room_id, "")
        if title in table_room_divider_map:
            real_room, span = table_room_divider_map[title]
            return roomtitind.get(real_room, 0) + 1, span
        return roomind.get(room_id, 0) + 1, 1

    info['tables']= []
    info['room_list'] = room_list
    sgroups = itertools.groupby(scheds, lambda x: x.startdate)
    for day, groupiter in sgroups:
        if limit_to_date is not None and day != limit_to_date: continue
        scheds = tuple(groupiter)
        times = {}
        for sched in scheds:
            times.setdefault(sched.starttime,[]).append(sched)
            times.setdefault(sched.stop,[])
        times = list(itertools.starmap(timeinfo, times.iteritems()))
        times.sort()
        tablebody = [ None ] * len(times)
        for ind, row in enumerate(tablebody):
            tablebody[ind] = [ True ] * (len(room_list)+1)
        tcell = None
        for row, tinfo in enumerate(times):
            rowcells = tablebody[row]
            if tinfo.has_starts():
                tcell = TableCell(tinfo.time)
                tcell.istime = True
                rowcells[0] = tcell
            else:
                rowcells[0] = None
                tcell.rowspan += 1
            for sched in tinfo.scheds:
                col, span = get_room_col_and_span(sched.room_id)
                cell = None
                if sched.is_feature() or sched.is_service():
                    cell = TableCell(sched)
                    cell.colspan = len(room_list)
                    cell.isfeat = True
                    rowcells[1:] = [None] * len(room_list)
                else:
                    cell = TableCell(sched.get_talk())
                    cell.colspan = span
                    cell.unassigned = sched.is_unassigned_open()
                    if span > 1:
                        rowcells[col+1:col+span] = [ None ] * (span-1)
                cell.rowspan = times.index(timeinfo(sched.stop, [])) - row
                for ind in xrange(row+1, row+cell.rowspan):
                    span = cell.colspan
                    tablebody[ind][col:col+span] = [ None ] * span
                rowcells[col] = cell
        ## fill in empty cells and prune out the None cells which are covered
        ## by spanning
        for row, tablerow in enumerate(tablebody):
            for col, cell in enumerate(tablerow):
                if cell is True:
                    cell = TableCell(None)
                    cell.isempty=True
                    tablerow[col] = cell
            tablebody[row] = filter(None, tablerow)
        info['tables'].append({'day': day,
                               'body': tablebody, })


def tables(request, day=None):
    info = { 'base_template': "roomy/base_site"}
    info['title'] = 'PyCon 2006 - TimeTable'
    info['include_image'] = day is None
    if 'print' in request.GET:
        info['printable'] = True
        info['base_template'] = "roomy/base_print"
    elif 'refresh' in request.GET and request.GET['refresh'].isdigit():
        info['base_template'] = "roomy/base_disp"
        info['http_refresh'] = int(request.GET['refresh'])
    else:
        info["includelinks"] = True
    generate_tablebodies(info, day_to_date(day))
    return render_to_response("roomy/tables", info,
        context_instance=Context(request) )
tables = cache_page(tables, 60*15)

@user_passes_test(lambda u: not u.is_anonymous() and u.is_superuser,
                  login_url='/apps/admin/')
def list_questionable_urls(request):
    """Generate a list of talks with no url assigned or a questionable url"""
    all_talks = talks.get_list()
    known_ok = (
        "http://us.pycon.org/zope/talks/",
        "http://us.pycon.org/apps/roomy/assign/",
        "http://us.pycon.org/TX2006/Food")
    unassigned_open = filter(lambda x: x.get_schedule().is_unassigned_open(),
                             all_talks)
    no_urls = filter(lambda x: not x.url and not x.get_schedule().is_unassigned_open(), all_talks)
    non_pycon_url = filter(lambda x: x.url and not
                                reduce(lambda b, s: b or x.url.startswith(s),
                                       known_ok, False), all_talks)
    info = { 'unassigned_open' : unassigned_open,
             'no_url' : no_urls,
             'non_pycon_url': non_pycon_url,
             'known_ok' : known_ok,
             'title': "PyCon 2006 - Odd Talks Admin Helper"
    }
    return render_to_response("roomy/odd_talks", info,
        context_instance=Context(request) )

def validateUnassignedUrlAndTitleExtra(data, errors):
    """Normal admin does not require these extra checks, only the
    open modification of unassigned open talks requires these checks."""
    if not data['url']:
        errors.setdefault('url', []).append("This field is required.")
    if "/apps/" in data['url']:
        errors.setdefault('url', []).append("An apps url is not allowed.")
    if "unassigned" in data['title'].lower():
        errors.setdefault('title', []).extend(
            "Talk title can not contain the word 'Unassigned'.")

def edit_unassigned(request, talk_id):
    bad = False
    talk = None
    try:
        manipulator = talks.ChangeManipulator(talk_id)
    except talks.TalkDoesNotExist:
        bad = True
    if not bad:
        talk = manipulator.original_object
        bad = not talk.get_schedule().is_unassigned_open()
    if bad:
        return render_to_response("roomy/editbad",
            { 'title': "Timeslot Already Assigned." },
            context_instance=Context(request) )

    if request.POST:
        if request.POST.has_key('cancel'):
            return HttpResponseRedirect("/apps/roomy/tables/")
        new_data = request.POST.copy()
        for name, val in talk.__dict__.iteritems():
            if name not in new_data:
                new_data[name] = str(val)
        errors = manipulator.get_validation_errors(new_data)
        validateUnassignedUrlAndTitleExtra(new_data, errors)
        if not errors:
            manipulator.do_html2python(new_data)
            manipulator.save(new_data)

            # Do a post-after-redirect so that reload works, etc.
            return HttpResponseRedirect("/apps/roomy/tables/")
    else:
        errors = {}
        talk.url = ""
        talk.title = ""
        new_data = talk.__dict__

    form = formfields.FormWrapper(manipulator, new_data, errors)
    return render_to_response('roomy/editopen',
                              {'form': form,
                               'data': new_data,
                               'talk': talk,
                               'errors': errors,
                               'title': "Assign Open Space Talk"})
