info343/lib/courseweb/js/scripts-old.js

var DEBUG = localStorage['debug'];
var NAV;
var SRCDATA = [];
var DOMDATA = [];
var SUBPAGES = [];
var INITIALIZED = [];
var NOW = new Date();
var GRACE_PERIOD = 60 * 15 * 1000; // 15 minutes
var PENDING_REQUESTS = 0;
var SECTION;
var DROPBOX;
var FILES;
var SECTIONS = ['a', 'b'];
var COURSE = 'info344';
var QUARTER = '13sp';

// why cookies...?
// if (!Cookies.exists('section')) {
//    Cookies.set('section', 'a', 14); // TODO: ask!
// } else {
//    SECTION = Cookies.get('section');
// }

// var UPDATESFILE = 'updates.xml?section=' + SECTION;
var DEPS = {
   home: ['updates.xml', 'calendar.xml', 'lectures.xml', 'labs.xml'],
   calendar: ['updates.xml', 'calendar.xml', 'lectures.xml', 'labs.xml'],
   lectures: ['calendar.xml', 'lectures.xml', 'labs.xml', 'video.html'],
   labs: ['calendar.xml', 'lectures.xml', 'labs.xml'],
   homework: ['homeworks.xml', 'assignments.xml'],
   syllabus: ['syllabus.html'],
   software: ['software.html'],
   staff: ['staff.html'],
   dropbox: ['dropbox.html', 'calendar.xml', 'assignments.xml', 'lectures.xml', 'labs.xml', 'homeworks.xml', 'tags.txt', 'dropbox_feedback.html']
};

document.observe('dom:loaded', function() {
   // $('main_content').hide();
   // addSectionToFeedLink();
   drawDropBoxArrows();
   drawWeekNavArrows();
   
   $$('.subpage').each(function(subpage) {
      var name = subpage.className.replace(/subpage/g, '').trim();
      SUBPAGES[name] = subpage;
      subpage.remove();
   });
   
   // determine section
   if (!localStorage['section']) {
      new Ajax.Request('html/section.html', {
         method: 'GET',
         onSuccess: function(ajax) {
            ajaxDialog(ajax, sectionInit);
         },
         onFailure: ajaxFailure,
         onException: ajaxFailure
      });
   } else {
      SECTION = localStorage['section'];
      goToSubpagePath();
      createSectionSelector();
   }
});

function ajaxDialog(ajax, initFunction) {
   showDialog(ajax.responseText);
   initFunction();
}

function showDialog(contents) {
   var dialog = $(document.createElement('div'));
   dialog.id = 'dialog';
   dialog.innerHTML = '<div id="screen"></div>' + contents;
   $(document.body).insert(dialog);
}

function sectionInit() {
   $('section_dialog').select('li').each(function(li) {
      li.observe('click', function(event) {
         SECTION = localStorage['section'] = event.findElement('li').innerHTML.trim().toLowerCase();
         // SECTION = event.findElement('li').innerHTML.trim().toLowerCase();
         dialogDismiss(function() {
            goToSubpagePath();
            createSectionSelector();
         });
      });
   });
}

function dialogDismiss(postFunction) {
   $('dialog').remove();
   if (postFunction)
      postFunction();
}

function loading() {
   PENDING_REQUESTS++;
}

function resolveDependenciesForSubpage(subpage) {
   // enforce https on dropbox
   if (subpage == 'dropbox' && location.protocol != 'https:') {
      location.replace(location.href.replace(/^http:/, 'https:'));
   }
   
   var complete = true;
   DEPS[subpage].each(function(depfile) {
      if (!SRCDATA[depfile]) {
         complete = false;
         loading();
         var dir = depfile.substring(depfile.lastIndexOf('.') + 1);
         dir = dir == 'txt' ? '.' : dir; // for tags.txt
         // console.log(depfile + " > " + dir);
         var requestfile = dir + '/' + depfile;
         new Ajax.Request(requestfile, {
            method: 'GET',
            onSuccess: function(ajax) {
               SRCDATA[depfile] = ajax.responseXML ? ajax.responseXML : ajax.responseText;
               // console.log("Loaded depfile '" + depfile + "' for subpage '" + subpage + "': " + SRCDATA[depfile]);
               depsLoadedForSubpage(subpage);
            },
            onFailure: ajaxFailure,
            onException: ajaxFailure
         })
      } else {
         // console.log("Depfile '" + depfile + "' for subpage '" + subpage + "' already loaded: " + SRCDATA[depfile]);
      }
   });
   
   if (complete) {
      initContentForSubpage(subpage);
   }
}

function depsLoadedForSubpage(subpage) {
   if (!--PENDING_REQUESTS) {
      initContentForSubpage(subpage);
   }
}

function initContentForSubpage(subpage) {
   if (!INITIALIZED[subpage]) {
      // console.log("Going to initialize subpage '" + subpage + "'.");
      switch (subpage) {
         case 'lectures':
         case 'labs':
         case 'homework':
         case 'dropbox':
            window[subpage + 'Init']();
            break;
         case 'calendar':
         case 'home':
            calendarInit();
            updatesInit();
            break;
         case 'syllabus':
         case 'staff':
         case 'software':
            SUBPAGES[subpage].insert(SRCDATA[subpage + '.html']);
            break;
      }

      if (['home', 'staff', 'syllabus'].indexOf(subpage) != -1) {
         createEmailLinks(SUBPAGES[subpage]);
      }
      
      INITIALIZED[subpage] = true;
   }
   displaySubpage(subpage);
}

function dateDiff(date1, date2) {
   return date2.getTime() - date1.getTime();
}

function prettyTime(ms) {
   function pluralize(val, singular, plural) {
      return val + ' ' + (val == 1 ? singular : plural);
   }
   
   var ary = [], days, hours, minutes, seconds = Math.abs(parseInt(ms / 1000));
   if (seconds >= 84600) {
      days = parseInt(seconds / 84600);
      seconds %= 84600;
      ary.push(pluralize(days, "day", "days"));
   }
   if (seconds >= 3600) {
      hours = parseInt(seconds / 3600);
      seconds %= 3600;
      ary.push(pluralize(hours, "hour", "hours"));
   }
   if (seconds >= 60) {
      minutes = parseInt(seconds / 60);
      seconds %= 60;
      ary.push(pluralize(minutes, "minute", "minutes"));
   }
   return ary.join(', ') + (ms < 0 ? ' ago' : ' from now');
}

function dropboxInit() {
   SUBPAGES['dropbox'].insert(SRCDATA['dropbox.html']);
   SUBPAGES['dropbox'].select('form#dropbox')[0].observe('submit', dropboxSubmit);
   SUBPAGES['dropbox'].select('div.submit input[type="submit"]')[0].disabled = true;
   SUBPAGES['dropbox'].select('img#submitting')[0].style.visibility = 'hidden';
   SUBPAGES['dropbox'].select('div.submit .checkbox_advisory')[0].style.visibility = 'hidden';
   SUBPAGES['dropbox'].select('li.authenticate input[type="text"]#username')[0].observe('keydown', dropboxValidate);
   SUBPAGES['dropbox'].select('li.authenticate input[type="password"]#password')[0].observe('keydown', dropboxValidate);
   SUBPAGES['dropbox'].select('li.authenticate input[type="checkbox"]')[0].observe('change', dropboxValidate);
   SUBPAGES['dropbox'].select('li.authenticate input[type="checkbox"]')[0].observe('change', dropboxShowHideAcademicIntegrityMessage);
   SUBPAGES['dropbox'].select('form#dropbox')[0].observe('submit', dropboxSubmit);
   
   var dropbox = SUBPAGES['dropbox'];
   dropbox.select('ol > li').each(function(step, i) {
      if (step.hasClassName('selectassignment')) {
         var ul = step.select('ul.assignments')[0];
         var assignments = $x('assignments/*', SRCDATA['assignments.xml']);
         var open_assignments = false;
         assignments.each(function(assignment, i) {
            var type = assignment.nodeName;
            var id = $x('@id', assignment)[0].textContent;
            console.log('[' + i + '] ' + id + ', ' + SECTION);
            var turnin = $x('turnin[@section="' + SECTION + '"] | turnin[@section="all"]', assignment)[0];
            var opens = new Date($x('@opens', turnin)[0].textContent);
            console.log('[' + i + '] NOW: ' + NOW);
            console.log('[' + i + '] opens: ' + opens + ' (' + prettyTime(dateDiff(NOW, opens)) + ')');
            
            if (NOW >= opens) {
               var closes = new Date($x('@closes', turnin)[0].textContent);
               console.log('[' + i + '] closes: ' + closes + ' (' + prettyTime(dateDiff(NOW, closes)) + ')');
               var cutoff = new Date(closes.getTime() + GRACE_PERIOD);
               console.log('[' + i + '] with grace period: ' + cutoff + ' (' + prettyTime(dateDiff(NOW, cutoff)) + ')');
               if ($x('@latedays', turnin)[0]) {
                  var latedays = parseInt($x('@latedays', turnin)[0].textContent);
                  console.log('[' + i + '] late days: ' + latedays);
                  cutoff = new Date(cutoff.getTime() + (latedays * 60 * 60 * 24 * 1000));
               }
               console.log('[' + i + '] cutoff: ' + cutoff + ' (' + prettyTime(dateDiff(NOW, cutoff)) + ')');
               
               if (NOW <= cutoff) {
                  open_assignments = true;
                  console.log('[' + i + '] ' + type);
                  var assignmentData = SRCDATA[(type == 'minilab' ? 'lecture' : type) + 's.xml'];
                  console.log('[' + i + '] ' + assignmentData);
                  var path = '//' + type + '[@id="' + id + '"]';
                  console.log('[' + i + '] ' + path);
                  var title = $x(path + '/title', assignmentData)[0].textContent;
                  console.log('[' + i + '] ' + title);
                  var label = $x(path + '/label', assignmentData);
                  if (label.length) {
                     label = label[0].textContent;
                  } else {
                     label = $x(path + '/description', assignmentData)[0].textContent;
                  }
                  console.log('[' + i + '] ' + label);
                  var li = $(document.createElement('li'));
                  li.className = 'assignment';
                  li.insert(['<a data-type="' + type + '" data-id="' + id + '">',
                             '<h4>', title, '</h4>',
                             '<p>', label, '</p>',
                             '</a>'].join(''));

                  li.select('a')[0].observe('click', selectAssignment);
                  ul.appendChild(li);
               }
            }
         });
         if (!open_assignments) {
            SUBPAGES['dropbox'].select('form#dropbox')[0].remove();
            SUBPAGES['dropbox'].appendChild($(document.createElement('p')).update('There are currently no open assignments to submit.'));
         }
      } else {
         // step.style.visibility = 'hidden';
      }
   });
}

function dropboxShowHideAcademicIntegrityMessage() {
   if ($('academic_integrity_chk').checked) {
      SUBPAGES['dropbox'].select('.submit .checkbox_advisory')[0].style.visibility = 'hidden';
   } else {
      SUBPAGES['dropbox'].select('.submit .checkbox_advisory')[0].style.visibility = 'visible';
   }
}

function dropboxFilesValidate() {
   var complete = true;
   FILES.each(function(f, i) {
      // console.log("dropcomplete[" + i + "] wasdroppedon(): " + f.wasdroppedon());
      if (f.isrequired() && !f.wasdroppedon()) {
         complete = false;
      }
   });
   return complete;
}

function dropboxValidate(event) {
   var valid = dropboxFilesValidate();
   if (valid) {
      [$('username'), $('password'), $('academic_integrity_chk')].each(function(elem) {
         if ((elem.type == 'checkbox' && !elem.checked) || !elem.value) {
            valid = false;
         }
      });
   }
   SUBPAGES['dropbox'].select('.submit input[type="submit"]')[0].disabled = !valid;
}

function dropboxSubmit(event) {
   event.stop();
   var xhr = new XMLHttpRequest();
   xhr.open("POST", "submit.php");
   xhr.onload = function() {
      dropboxSubmitted(xhr, $('username').value);
   };
   DROPBOX.append('username', $('username').value);
   DROPBOX.append('password', $('password').value);
   DROPBOX.append('timestamp', Date.now());
   xhr.send(DROPBOX);
   dropboxSubmitting();
}

function dropboxSubmitted(xhr) {
   var url, message, latedays;
   switch (xhr.status) {
      case 200:
         url = 'html/dropbox_accepted.html';
         var matches = xhr.responseText.match(/accepted. Receipt: ([a-f0-9]+)\n(?:on time|(\d) days? late)/);
         message = matches[1];
         if (matches[2]) {
            url = 'html/dropbox_late.html';
            latedays = matches[2];
         }
         break;
      case 401:
         url = 'html/dropbox_authentication.html';
         // message = xhr.responseText;
         break;
      case 400:
      case 500:
      default:
         url = 'html/dropbox_error.html';
         message = xhr.responseText;
         break;
   }
   new Ajax.Request(url, {
      method: 'GET',
      onSuccess: function(ajax) { 
         var dropboxsection = $('dropbox').parentNode;
         // console.log("in dropboxFeedbackInit");
         var assignmenttype = $('dropbox').select('input[name="assignmenttype"]')[0].value;
         var assignmentid = $('dropbox').select('input[name="assignmentid"]')[0].value;
         var username = $('dropbox').select('input[name="username"]')[0].value;
         // var section = $F('dropbox')['section'];
         $('dropbox').remove();
         $('main_content').scrollTo();
         dropboxsection.hide();
         dropboxsection.innerHTML += ajax.responseText;
         if (message) {
            $$('.subpage.dropbox div.outcome section.response code')[0].update(message);
         }
         if (latedays) {
            $('latedays').update(latedays == 1 ? '1 day late' : latedays + ' days late');
         }
         if (xhr.status == 200) {
            dropboxsection.select('div.outcome')[0].innerHTML += SRCDATA['dropbox_feedback.html'];
            new Ajax.Request('feedback.php', {
               method: 'GET',
               synchronous: true,
               parameters: {
                  'username': username,
                  'assignmenttype': assignmenttype,
                  'assignmentid': assignmentid
               },
               onSuccess: function(ajax) {
                  dropboxFeedbackInit(username, assignmenttype, assignmentid, ajax.responseText);
               },
               onFailure: function(ajax) {
                  dropboxFeedbackInit(username, assignmenttype, assignmentid);
               }
            })
            // dropboxFeedbackInit(username, assignmenttype, assignmentid);
         }
         dropboxsection.show();
      },
      onFailure: ajaxFailure,
      onException: ajaxFailure
   });
}

function dropboxFeedbackInit(username, assignmenttype, assignmentid, existingdata) {
   var form = SUBPAGES['dropbox'].select('div.outcome section.feedback form#feedback')[0];
   form.select('input[name="username"]')[0].value = username;
   form.select('input[name="assignmenttype"]')[0].value = assignmenttype;
   form.select('input[name="assignmentid"]')[0].value = assignmentid;
   
   var existing;
   if (existingdata) {
      existing = [];
      var parts = existingdata.split(/\n/);
      existing['rating'] = parts[0];
      existing['time'] = parts[1];
      existing['tags'] = parts[2].split(/, ?/);
      existing['comments'] = parts[3];
   }
   
   // create rating elements
   var rating_ul = form.select('dl.feedback dd.rating ul')[0];
   // console.log(rating_ul);
   
   for (var i = 1; i <= 5; i++) {
      var li = $(document.createElement('li'));
      li.innerHTML = ['<label>', '<input type="radio" name="rating" value="' + i + '" />', i, '</label>'].join('');
      li.observe('click', function(event) {
         var ul = event.findElement('ul');
         ul.addClassName('rated');
         ul.select('li').each(function(elem) {
            if (elem == event.findElement('li')) {
               // elem.select('input[type="radio"]')[0].checked = true;
               elem.addClassName('selected');
            } else {
               // elem.select('input[type="radio"]')[0].checked = false;
               elem.removeClassName('selected');
            }
         });
      });
      // console.log(i + ' =? ' + existing['rating']);
      if (existing && i == existing['rating']) {
         rating_ul.addClassName('rated');
         li.select('input')[0].checked = true;
         li.addClassName('selected');
      }
      rating_ul.appendChild(li);
   }
   
   // create tags
   var tags_ul = form.select('dl.feedback dd.tags ul')[0];
   var tags = SRCDATA['tags.txt'].split(/\n/);
   for (var i = 0; i < tags.length; i++) {
      var tag = tags[i];
      var li = $(document.createElement('li'));
      li.innerHTML = ['<label>', '<input type="checkbox" name="tags[]" value="' + tag + '" />',
                       tag, '</label>'].join('');
      li.observe('click', function(event) {
         // console.log(event.element());
         if (event.element().checked) {
            event.findElement('li').addClassName('selected');
         } else {
            event.findElement('li').removeClassName('selected');
         }
      });
      if (existing && existing['tags'].indexOf(tag) != -1) {
         li.select('input')[0].checked = true;
         li.addClassName('selected');
      }
      tags_ul.appendChild(li);
   }
   
   // prep time for assignment type
   var time = form.select('#time')[0];
   var timeunits = form.select('#timeunits')[0];
   switch (assignmenttype) {
      case 'homework':
         time.value = '2';
         time.step = '0.5';
         // time.max = '15';
         timeunits.innerHTML = 'hrs.';
         break;
      case 'lab':
         time.value = '50';
         time.step = '5';
         // time.max = '60';
         timeunits.innerHTML = 'mins.';
         break;
      case 'minilab':
         time.value = '25';
         time.step = '5';
         // time.max = '45';
         timeunits.innerHTML = 'mins.';
         break;
   }
   
   // update time, comments
   if (existing) {
      var p = $(document.createElement('p')).update('Your previous feedback is pre-populated below. Thanks!');
      p.addClassName('notice');
      form.childElements()[0].insert(p, { position: 'after' });
      form.select('input[name="time"]')[0].value = existing['time'];
      form.select('textarea')[0].innerHTML = existing['comments'];
      form.select('input[type="submit"]')[0].addClassName('submitted');
      form.select('input[type="submit"]')[0].value = 'Update';
   }
   
   // prepare for submission
   form.observe('submit', function(event) {
      event.stop();
      var data = new FormData(form);
      var xhr = new XMLHttpRequest();
      xhr.open(form.method, form.action);
      xhr.onload = function() {
         form.select('input[type="submit"]')[0].addClassName('submitted');
         form.select('input[type="submit"]')[0].value = 'Update';
         form.select('p#feedback_thankyou')[0].update('<strong>Thanks for the feedback!</strong> Now go look at <a href="http://cuteroulette.com/">something cute</a>.');
      };
      xhr.send(data);
   });
}

function dropboxSubmitting() {
   SUBPAGES['dropbox'].select('.submit input[type="submit"]')[0].disabled = true;
   $$('.subpage.dropbox .submit input[type="submit"]')[0].value = "Submitting…";
   $('submitting').style.visibility = 'visible';
}

function homeworkInit() {
   function dateString(date) {
      var weekday = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][date.getDay()];
      var month = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][date.getMonth()];
      var day = date.getDate();
      var year = date.getFullYear();
      var hours = date.getHours();
      var minutes = zeroPad(date.getMinutes());
      var am_pm = (hours >= 0 && hours < 12) ? 'AM' : 'PM';
      hours %= 12;
      return [weekday, ', ', month, ' ', day, ', ', year, ', ', hours, ':', minutes, ' ', am_pm].join('');
   }
   
   var now = new Date();
   var homeworks = $x('//homework', SRCDATA['homeworks.xml']);
   if (homeworks.length) {
      // alert(homeworks.length);
      homeworks.reverse().each(function(homework, i) {
         // get date-related stuff from calendar
         var id = $x('@id', homework)[0].textContent;
         var turnin = $x('//homework[@id="' + id + '"]/turnin', SRCDATA['assignments.xml'])[0];
      
         var opens = new Date($x('@opens', turnin)[0].textContent);
         var closes = new Date($x('@closes', turnin)[0].textContent);
         var shown = (opens <= now);
      
         if (shown) {
            var article = $(document.createElement('article'));
            var title = $x('title', homework)[0].textContent;
            var description = $x('description', homework)[0] ? $x('description', homework)[0].textContent : '';
         
            var open = (now <= closes);
            var closemonth = zeroPad(closes.getMonth() + 1);
            var closeday = zeroPad(closes.getDate());
            var content = ['<hgroup>',
                              '<h3 id="', id, '">', title, '</h3>',
                              '<h4>', closemonth + '.' + closeday, '</h4>',
                           '</hgroup>',
                           '<p>', description, '</p>',
                           '<p class="duedate">Due: <time>' + dateString(closes) + '</time></p>'].join('');
         
            // if (!open) {
            //    article.addClassName('closed');
            //    article.innerHTML = content;
            // } else {
               article.innerHTML = linkNode(homework, id, content, false);
            // }
            SUBPAGES['homework'].appendChild(article);
         }
      });
   } else {
      SUBPAGES['homework'].appendChild(noneYet('homework assignments'));
   }
}

function noneYet(type) {
   var p = $(document.createElement('p'));
   p.innerHTML = 'No ' + type + ' posted yet.';
   return p;
}

function dropcomplete() {
   if (dropboxFilesValidate()) {
      // $$('.submit input[type="submit"]')[0].disabled = false;
      // dropboxSubmit();
      $$('.subpage.dropbox li.step.authenticate')[0].removeClassName('disabled');
      $('username').disabled = false;
      $('password').disabled = false;
      $('academic_integrity_chk').disabled = false;
      $$('.subpage.dropbox div.submit')[0].removeClassName('disabled');
      // $$('.subpage.dropbox div.submit input[type="submit"]')[0].disabled = false;
      // $$('.subpage.dropbox div.submit')[0].style.visibility = 'visible';
      dropboxShowHideAcademicIntegrityMessage();
   }
}

function selectAssignment(event) {
   // console.log("clicked on: " + event.element());
   var a = event.element().up('a');
   if (!a) a = event.element();
   // console.log("found a: " + a);
   var type = a.dataset['type'];
   var id = a.dataset['id'];
   // console.log('assignment type: ' + type);
   // console.log('assignment id: ' + id);
   
   FILES = [];
   DROPBOX = new FormData();
   DROPBOX.append('assignmenttype', type);
   DROPBOX.append('assignmentid', id);
   DROPBOX.append('section', localStorage['section']);
   // console.log("assignmenttype: " + type + "; assignmentid: " + id);
   $('dropbox').select('input[name="assignmenttype"]')[0].value = type;
   $('dropbox').select('input[name="assignmentid"]')[0].value = id;
   $('dropbox').select('input[name="section"]')[0].value = localStorage['section'];
   
   $$('.subpage.dropbox li.step.selectassignment ul.assignments li').each(function(assignment) {
      if (assignment == a.up('li')) {
         assignment.removeClassName('deselected');
         assignment.addClassName('selected');
      } else {
         assignment.removeClassName('selected');
         assignment.addClassName('deselected');
      }
   });
   
   $$('li.uploadfiles ul.files')[0].update();
   
   // $('dropbox')['assignmenttype'].value = type;
   // $('dropbox')['assignmentid'].value = id;
   // console.log('type: ' + $('dropbox')['assignmenttype'].value);
   // console.log('id: ' + $('dropbox')['assignmentid'].value);
   var path = '//' + type + '[@id="' + id + '"]';
   // console.log('path: ' + path);
   var files = $x(path + '/file', SRCDATA['assignments.xml']);
   // console.log('files: ' + files.length);
   files.each(function(file, i) {
      var type = $x('@type', file)[0].textContent;
      var name = $x('@name', file)[0].textContent;
      var required = $x('@required="true"', file);
      // console.log('File "' + name + '" is required: ' + required);
      var li = dropboxCreateNewFileDrop(name, type, required);
      $$('li.uploadfiles ul.files')[0].insert(li);
   });
   $$('.subpage.dropbox li.step.uploadfiles')[0].removeClassName('disabled');
}

function dropboxCreateNewFileDrop(name, type, required) {
   var li = $(document.createElement('li'));
   
   var label;
   li.addClassName('filedrop');
   if (name == '*' && type == '*') {
      label = '(another file)';
      li.addClassName('another');
      li.addClassName('variable');
   } else if (name == '*') {
      label = '(a file of type ' + type + ')';
      li.addClassName('variable');
   } else if (name.indexOf('*') != -1) {
      label = '(a file named ' + name + ')';
      li.addClassName('variable');
   } else if (required) {
      label = name;
      li.addClassName('required');
   }
   li.innerHTML = label;
   
   FILES.push(new DNDFileController(li, name, type, required, dropcomplete));
   
   return li;
}

function calendarInit() {
   function getContent(entry, date) {
      var title = $x('title', entry)[0].textContent;
      var description = $x('description', entry)[0] ? $x('description', entry)[0].textContent : '';
      return cell_content = ['<hgroup>',
                                '<h3>', title, '</h3>',
                                '<h4>', date.substring(4, 6) + '.' + date.substring(6), '</h4>',
                             '</hgroup>',
                             '<p>', description, '</p>'].join('');
      // cell.innerHTML = linkNode(entry, id, cell_content);
   }
   
   DOMDATA['calendar'] = [];
   $x('//week', SRCDATA['calendar.xml']).each(function(week, i) {
      var tr = $(document.createElement('tr'));
      var entries = $x('//week[@number="' + (i+1) + '"]/section[@id="' + localStorage['section'] + '"]/*', week);
      entries.each(function(calendar_entry, j) {
         var cell = $(document.createElement('td'));
         cell.className = ['lecture-a', 'lab', 'lecture-b'][j];
         var date = $x('@date', calendar_entry)[0].textContent;
         if ($x('@blank="true"', calendar_entry)) {
            cell.innerHTML = '';
         } else if ($x('@id', calendar_entry)[0]) {
            var id = $x('@id', calendar_entry)[0].textContent;
            var type = calendar_entry.nodeName;
            var doc = SRCDATA[type + 's' + '.xml'];
            var source_entry = $x('//*[@id="' + id + '"]', doc)[0];
            if ($x('@tentative="true"', calendar_entry)) {
               cell.addClassName('tentative');
               cell.innerHTML = getContent(source_entry, date);
            } else {
               cell.innerHTML = linkNode(source_entry, id, getContent(source_entry, date), type == 'lecture');
            }
            if (type == 'lecture') {
               var minilab = getMinilabContent(source_entry);
               if (!minilab) {
                  cell.addClassName('noaside');
               } else {
                  cell.innerHTML += minilab;
               }
            }
         } else {
            cell.innerHTML = getContent(calendar_entry, date);
            cell.addClassName('noaside');
            if ($x('@tentative="true"', calendar_entry)) {
               cell.addClassName('tentative');
            }
         }
         cell.innerHTML = '<div class="wrapper">' + cell.innerHTML + '</div>';
         tr.appendChild(cell);
      });
      DOMDATA['calendar'].push(tr);
   });
}

function showWeekInElement(week, element) {
   var calendar = element.select('table.calendar')[0];
   calendar.select('tr').each(function(tr) { // TODO: save the one to show if it's there
      tr.remove();
   });
   calendar.insert(DOMDATA['calendar'][week - 1]);
}

function zeroPad(num) {
   return num < 10 ? '0' + num : num;
}

function updatesInit() {
   // console.log("Initializing updates.");
   var items = $x('//item', SRCDATA['updates.xml']);
   [SUBPAGES['home'], SUBPAGES['calendar']].each(function(subpage) {
      var dl = subpage.select('dl')[0];
      // console.log("Initializing dl: " + dl);
      dl.update();
      var charcount = 0;
      items.each(function(item, i) {
         // limit to 6 entries on home page, regardless of character count
         if (i > 5 && subpage.hasClassName('home')) {
            throw $break;
         }
         
         // console.log('[' + i + '] Parsing item: ' + item.textContent);
         var date = new Date($x('pubDate', item)[0].textContent);
         // console.log('[' + i + '] Date: ' + date);
         var month = zeroPad(date.getMonth() + 1);
         var day = zeroPad(date.getDate());
         var dateelem = new Element('dt').update(month + '.' + day);
         
         var title = $x('title', item)[0].textContent;
         var description = $x('description', item)[0].textContent;
         var link = $x('link', item)[0];
         if (link) {
            title = ['<a href="' + link.textContent + '">', title, '</a>'].join('');
         }
         var content = title;
         
         // add title only if it's significantly different (non-alphabetic characters and tags)
         if (title.replace(/<[^>]+>/g, '').replace(/[^\w]/g, '') != description.replace(/<[^>]+>/g, '').replace(/[^\w]/g, '')) {
            content = ['<p>', title, '</p>', '<aside>', description, '</aside>'].join('');
         }
         
         var message = new Element('dd').update(content);
         
         // ensure home page updates list isn't too long (≤500 chars and ≤5 entries)
         var text = message.textContent.replace(/\s+/g, ' ');
         console.log("[item " + i + "]: message: '" + text + "' (" + text.length + " / " + charcount + ")");
         if (i > 0 && subpage.hasClassName('home') && (charcount + text.length > 500 || i > 5)) {
            throw $break;
         } else {
            dl.appendChild(dateelem);
            dl.appendChild(message);
            charcount += text.length;
         }
      });
   });
}

function getMinilabContent(container) {
   var minilab = $x('minilab', container)[0];
   if (minilab) {
      var ml_title = $x('title', minilab)[0].textContent;
      var ml_description, ml_id;
      ['description', '@id'].each(function(field, k) {
         var elem = $x(field, minilab)[0];
         if (elem) {
            eval('ml_' + field.replace(/@/, '') + ' = "' + elem.textContent + '";');
         }
      });
      var ml_content = ['<strong>', ml_title, ':</strong>'].join('');
      if (ml_description) ml_content += ['<span class="space"> </span>', ml_description].join('');
      ml_content = ['<aside>', ml_content, '</aside>'].join('');
      if (ml_id) {
         return linkNode(minilab, ml_id, ml_content, false);
      } else {
         return ml_content;
      }
   } else {
      return '';
   }
}

function lecturesInit() {
   $x('//lecture', SRCDATA['lectures.xml']).each(function(lecture, i) {
      var article = $(document.createElement('article'));
      var id = $x('@id', lecture)[0].textContent;
      var title = $x('title', lecture)[0].textContent;
      var description = $x('description', lecture)[0] ? $x('description', lecture)[0].textContent : '';
      
      // get date-related stuff from calendar
      var calendar = SRCDATA['calendar.xml'];
      var section = localStorage['section'];
      var path = '//section[@id="' + section + '"]/lecture[@id="' + id + '"]';
      var date = $x(path + '/@date', calendar)[0].textContent;
      var tentative = $x(path + '/@tentative="true"', calendar);
      
      // get mini-lab
      var minilab = getMinilabContent(lecture);
      var content = ['<hgroup>',
                        '<h3 id="', id, '">', title, '</h3>',
                        '<h4>', date.substring(4, 6) + '.' + date.substring(6), '</h4>',
                     '</hgroup>',
                     '<p>', description, '</p>'].join('');
      
      var files = '';
      var filesets = $x(path + '/fileset', calendar);
      if (filesets.length) {
         files = ['<dl class="fileset">'];
         filesets.each(function(fileset, i) {
            var label = $x('@label', fileset)[0].textContent;
            files.push('<dt>', label, ':</dt>', '<dd>', '<ul>');
            var ul = $(document.createElement('ul'));
            $x('file', fileset).each(function(file, i) {
               var li = $(document.createElement('li'));
               var a = $(document.createElement('a'));
               var name = $x('@name', file)[0].textContent;
               var label;
               if ($x('@label', file).length) {
                  label = $x('@label', file)[0].textContent;
               }
               
               if (/^(\w+:)?\/\/?/.test(name)) {
                  a.href = name;
               } else {
                  a.href = 'lectures/' + id + '/files/' + section + '/' + name;
               }
               a.innerHTML = name.split('/').pop();
               
               li.appendChild(a);
               if (label)
                  li.innerHTML += " " + label;
               if ($x('@supporting="true"', file)) {
                  li.addClassName('supporting');
               }
               ul.appendChild(li);
            });
            files.push(ul.innerHTML, '</ul>', '</dd>');
         });
         files.push('</dl>');
         files = files.join('');
         console.log('Files: ' + files);
      }
      
      var video = '';
      if ($x(path + '/@recorded="true"', calendar)) {
         var datestr = date.substring(0, 4) + '-' + date.substring(4, 6) + '-' + date.substring(6);
         var link = $(document.createElement('a'));
         link.className = 'videolink';
         link.href = "javascript:playlecture('" + id + "','" + datestr + "','" + section + "')";
         link.innerHTML = '<span class="play">▶</span> <span class="textonly">Play lecture</span>';
         var temp = $(document.createElement('div'));
         temp.appendChild(link);
         video = temp.innerHTML;
      }
      
      if (tentative) {
         article.addClassName('tentative');
         article.innerHTML = content + minilab + video + files;
      } else {
         article.innerHTML = linkNode(lecture, id, content, false) + minilab + video + files;
      }
      
      SUBPAGES['lectures'].appendChild(article);
   });
}

function playlecture(id, date, section) {
   var urlbase = location.protocol + '//' + COURSE + '.ischool.uw.edu/lectures/' + QUARTER + '/' + date + '-' + id + '-section-' + section;
   console.log("Going to play to video: " + urlbase + ".*");
   var template = SRCDATA['video.html'];
   $H({
      'URLBASE': urlbase,
      'WIDTH': 768,
      'HEIGHT': 432,
      'VIDEOID': 'video_lecture_' + date.replace(/-/g, '') + '_' + section
   }).each(function(pair) {
      template = template.replace(new RegExp("%%" + pair.key + "%%", "g"), pair.value);
   });
   console.log("video HTML: " + template);
   showDialog(template);
   videojs('video_lecture_' + date.replace(/-/g, '') + '_' + section, {
      "controls": true,
      "autoplay": false,
      "preload": "auto"
   });
   $('screen').observe('click', dialogClick);
}

function dialogClick(event) {
   dialogDismiss();
}

function labsInit() {
   var labs = $x('//lab', SRCDATA['labs.xml']);
   if (labs.length) {
      labs.each(function(lab, i) {
         var article = $(document.createElement('article'));
         var id = $x('@id', lab)[0].textContent;
         
         // get date-related stuff from calendar
         var calendar = SRCDATA['calendar.xml'];
         var section = localStorage['section'];
         var path = '//section[@id="' + section + '"]/lab[@id="' + id + '"]';
         var date = $x(path + '/@date', calendar)[0].textContent;
         var tentative = $x(path + '/@tentative="true"', calendar);
         
         var title = $x('title', lab)[0] ? $x('title', lab)[0].textContent : '';
         var description = $x('description', lab)[0] ? $x('description', lab)[0].textContent : '';
         var content = ['<hgroup>',
                           '<h3 id="', id, '">', title, '</h3>',
                           '<h4>', date.substring(4, 6) + '.' + date.substring(6), '</h4>',
                        '</hgroup>',
                        '<p>', description, '</p>'].join('');
         
         if (tentative) {
            article.addClassName('tentative');
            article.innerHTML = content;
            SUBPAGES['labs'].appendChild(article);
            throw $break; // only display first tentative lab
         } else {
            article.innerHTML = linkNode(lab, id, content, false);
            SUBPAGES['labs'].appendChild(article);
         }
      });
   } else {
      SUBPAGES['labs'].appendChild(noneYet('labs'));
   }
}

function getWeekNum(date) {
   date = date ? date : new Date();
   // debug('getting week num for date: ' + date);
   
   var startdate = new Date($x('//start/@date', SRCDATA['calendar.xml'])[0].textContent);
   // debug('start date: ' + startdate.getTime());
   var enddate = new Date($x('//end/@date', SRCDATA['calendar.xml'])[0].textContent);
   // debug('end date: ' + enddate.getTime());
   var weeks = $x('//week', SRCDATA['calendar.xml']);
   // debug('weeks: ' + weeks);
   
   if (date.getTime() < startdate.getTime()) {
      return 1;
   } else if (date.getTime() > enddate.getTime()) {
      return weeks.length;
   } else {
      var weekinseconds = 60 * 60 * 24 * 7; // !!! WARNING: DST !!!
      for (var i = 0; i < weeks.length; i++) {
         var startofweek = startdate.getTime() + (weekinseconds * i * 1000);
         if (date.getTime() <= startofweek) {
            return i;
         }
      }
      return i;
   }
   
   // if (date.getFullYear() < 2012)
   //    return 1;
   // else if (date.getFullYear() > 2012)
   //    return 11;
   
   // var m = date.getMonth();
   // var d = date.getDate();
   // switch (m) {
   //    case 0:
   //    case 1:
   //    case 2: // march
   //       return Math.max(Math.floor((d - 4) / 7) - 2, 1);
   //       break;
   //    case 3: // april
   //       return Math.floor((d - 2) / 7) + 2;
   //       break;
   //    case 4: // may
   //       return Math.floor((d + 1) / 7) + 6;
   //       break;
   //    case 5: // june
   //    default:
   //       return Math.min(Math.floor((d + 3) / 7) + 10, 11);
   //       break;
   // }
}

function goToSubpagePath() {
   // parse hash path
   if (location.hash.substring(0, 2) == '#!') {
      NAV = location.hash.split(/\//).slice(1, location.hash.lastIndexOf('/'));
   } else {
      NAV = [];
   }
   
   // populate empty spaces up to 3
   for (var i = 0; i <= 2; i++) {
      if (NAV.length <= i) {
         NAV[i] = null;
      }
   }
   
   if (!NAV[0])
      NAV[0] = 'home'
   
   // load subpage content, resolving any depencencies
   resolveDependenciesForSubpage(NAV[0]);
}

function displaySubpage(name) {
   var content = SUBPAGES[name];
   content.style.display = 'none';
   
   // console.log("Going to display subpage '" + name + "': " + SUBPAGES[name]);
   
   // remove previous target
   $$('.targeted').each(function(elem) {
      elem.removeClassName('targeted');
   });
   
   // populate page with subpage content
   $('main_content').update(content);
   
   // revert to a previous WEEK if new data is not posted yet
   if (name == 'home' || name == 'calendar') {
      var WEEK = getWeekNum(NOW);
      debug('WEEK: ' + WEEK);
      // console.log("Looking for most current week (currently " + WEEK + ')...');
      while (WEEK && !$x('//week[@number="' + WEEK + '"]', SRCDATA['calendar.xml'])[0]) {
         // console.log("Week " + WEEK + ' not found, decrementing');
         WEEK--;
      }
      // console.log("Settled on week " + WEEK);
   }
   
   // configure and display the appropriate page
   switch (name) {
      case 'home':
         showWeek('home', WEEK);
         break;
      case 'calendar':
         var showall = $$('.subpage.calendar header menu.show a.showall')[0];
         switch (NAV[1]) {
            case 'full':
               showall.innerHTML = 'show only this week';
               showall.href = '#!/calendar/';
               showFullCalendar();
               break;
            case 'week':
               showall.innerHTML = 'show entire quarter';
               showall.href = '#!/calendar/full/';
               showWeek('calendar', parseInt(NAV[2]));
               break;
            default:
               showall.innerHTML = 'show entire quarter';
               showall.href = '#!/calendar/full/';
               showWeek('calendar', WEEK);
               break;
         }
         break;
      case 'lectures':
      case 'labs':
      case 'homework':
      case 'syllabus':
      case 'staff':
      case 'software':
      case 'dropbox':
      default:
   }
   
   content.style.display = 'block';
   $('main_content').style.visibility = 'visible';
   document.body.className = name;
   window.onhashchange = goToSubpagePath;
   
   // setTimeout(function() {
      // handle target scrolling
      switch (name) {
         case 'calendar':
            var showall = $$('.subpage.calendar header menu.show a.showall')[0];
            switch (NAV[1]) {
               case 'full':
                  if (NAV[2]) {
                     $('week_' + NAV[2]).scrollTo();
                  }
                  break;
               case 'week':
               default:
            }
            break;
         case 'home':
         case 'lectures':
         case 'labs':
         case 'homework':
         case 'syllabus':
         case 'staff':
         case 'software':
         default:
            if (NAV[1]) {
               $(NAV[1]).addClassName('targeted');
               $(NAV[1]).scrollTo();
            }
      }
   // }, 50);
}

function showWeek(subpage, week) {
   var subpageClass = '.subpage.' + subpage;
   var cal = $$(subpageClass + ' table.calendar')[0];
   cal.childElements().each(function(elem) {
      elem.remove();
   });
   
   cal.appendChild(DOMDATA['calendar'][week-1]);
   if (subpage == 'calendar') {
      $$(subpageClass + ' nav.week span.number')[0].innerHTML = 'Week ' + week;
      updateWeekNavArrowsForWeek(week);
   }
}

function updateWeekNavArrowsForWeek(week) {
   $$('.subpage.calendar nav.week')[0].show();
   $$('.subpage.calendar header nav.week a.week').each(function(link, i) {
      var chg = i == 0 ? -1 : 1;
      var newweek = week + chg;
      if (newweek > 0 && newweek < DOMDATA['calendar'].length + 1) {
         link.href = '#!/calendar/week/' + newweek;
         link.stopObserving('click', nullClick);
      } else {
         link.href = 'javascript:';
         link.observe('click', nullClick);
      }
   });
}

function nullClick(event) {
   event.stop();
}

function showFullCalendar() {
   var cal = $$('.subpage.calendar table.calendar')[0];
   cal.innerHTML = '';
   DOMDATA['calendar'].each(function(row, i) {
      cal.appendChild(row);
   });
   $$('.subpage.calendar nav.week')[0].hide();
   // updateWeekNavArrows();
}

function linkNode(node, identifier, content, internal) {
   var type = node.nodeName;
   var target = type == 'lecture' || /^mini-homework/.test(identifier) ? identifier : identifier.replace(type + '-', '');
   var label = type == 'homework' ? type : type + 's';
   if (internal) {
      return ['<a href="#!/', label, '/', target, '/">', content, '</a>'].join('');
   } else {
      return ['<a href="', label, '/', target, '/">', content, '</a>'].join('');
   }
}

function $x(path, node, type) {
   if (DEBUG) {
      debug("path: " + path);
      debug("node: " + node);
   }
   if (!type)
      type = XPathResult.ANY_TYPE;
   var xpe = node.ownerDocument || node;
   if (DEBUG) {
      debug("xpe: " + xpe);
   }
   var nsResolver = xpe.createNSResolver(xpe.documentElement);
   var result = xpe.evaluate(path, node, nsResolver, type, null);
   switch (result.resultType) {
      case XPathResult.NUMBER_TYPE:
         return result.numberValue;
         break;
      case XPathResult.STRING_TYPE:
         return result.stringValue;
         break;
      case XPathResult.BOOLEAN_TYPE:
         return result.booleanValue;
         break;
      default:
         var found = new Array();
         var res;
         while (res = result.iterateNext()) {
            found.push(res);
         }
         return found;
   }
}

function debug(msg) {
   var caller = debug.caller;
   if (caller) {
      caller = caller.caller;
   }
   caller = caller ? caller.name : 'anonymous';
   console.log("[debug@" + caller + "] " + msg);
}

function ajaxFailure(ajax, e) {
   // alert('failure!');
   console.log(e);
}

function createSectionSelector() {
   $('course_id').insert(new Element('ul', {
      'id': 'section',
   }));
   SECTIONS.each(function(section, i) {
      var a = new Element('a');
      a.update(section.toUpperCase());
      a.observe('click', sectionChange);
      var li = new Element('li');
      li.insert(a);
      if (SECTION == section) {
         li.addClassName('selected');
      }
      $('section').insert(li);
   });
   var offset = SECTIONS.indexOf(SECTION);
   $('section').firstChild.style.marginTop = (-offset * .85) + 'em';
}

function createEmailLinks(section) {
   section.select('.email').each(function(elem, i) {
      var username = elem.innerHTML;
      var text = elem.nodeName == 'ADDRESS' ? username + '@uw.edu' : username;
      elem.update(new Element('a', { href: "mailto:" + username + "@uw.edu" }).update(text));
   });
}

function sectionChange(event) {
   event.stop();
   localStorage['section'] = this.innerHTML.toLowerCase();
   location.reload();
   // TODO: soft refresh
}

function drawWeekNavArrows() {
   $$('.subpage.calendar header nav.week a.week').each(function(link, i) {
      link.innerHTML = '';
      
      var s = parseInt($(document.body).getStyle('font-size')) / 18;
      
      var canvas = $(document.createElement('canvas'));
      canvas.width = 51 * s;
      canvas.height = 13 * s;
      // c.lineCap = 'square';
      
      var c = canvas.getContext('2d');
      c.lineWidth = 1 * s;
      c.strokeStyle = '#000000';
      
      c.beginPath();
      
      if (i == 0) {
         c.moveTo( 0.5 * s, 12.5 * s);
         c.lineTo(25.5 * s,  0.5 * s);
         c.lineTo(50.5 * s, 12.5 * s);
         c.lineTo( 0.5 * s, 12.5 * s);
      } else {
         c.moveTo( 0.5 * s,  0.5 * s);
         c.lineTo(50.5 * s,  0.5 * s);
         c.lineTo(25.5 * s, 12.5 * s);
         c.lineTo( 0.5 * s,  0.5 * s);
      }
      
      c.stroke();
      
      link.appendChild(canvas);
   });
}

function drawDropBoxArrows() {
   var s = parseInt($(document.body).getStyle('font-size')) / 18;
   
   var canvas = $(document.createElement('canvas'));
   canvas.width = 45 * s;
   canvas.height = 115 * s;
   // c.lineCap = 'square';
   
   var labs = $('nav_labs');
   var homework = $('nav_homework');
   $('main_nav').appendChild(canvas);
   
   var c = canvas.getContext('2d');
   c.lineWidth = .5 * s;
   c.strokeStyle = '#000000';
   
   c.beginPath();
   
   c.moveTo( 0.5 * s,  0.0 * s);
   c.lineTo( 0.5 * s, 84.5 * s);
   c.lineTo(37.5 * s, 84.5 * s);
   
   c.moveTo(37.5 * s,  35.0 * s);
   c.lineTo(37.5 * s, 110.5 * s);
   
   c.moveTo(32.5 * s, 105.5 * s);
   c.lineTo(37.5 * s, 110.5 * s);
   c.lineTo(42.5 * s, 105.5 * s);
   
   c.stroke();
}