/**
* An AJAX-updating calendar
*
* @author Josh, v1.0, 21-08-2008
*
* @param Element calendar_node The node to insert the TRs of the calendar into
* @param string request_url The URL to make the AJAX request to when doing an update
* @param AjaxHandler ajax_handler The AJAX handler to use for handling the request.
*    This handler should call the AjaxCalendar::draw method.
**/
function AjaxCalendar (calendar_node, request_url, ajax_handler) {
  // Check dependencies
  if (queue == null) {
    alert ('ERROR: No Autodev AJAX queue found.');
    return;
  }
  
  // Initial variables
  var today = new Date();
  this.calendar_month = today.getMonth() + 1;
  this.calendar_year = today.getFullYear();
  this.request_url = request_url;
  this.ajax_handler = ajax_handler;
  this.calendar_node = calendar_node;
  
  /**
  * Sets the internal date pointer to an arbitary date and redraws the calendar
  **/
  this.changeDate = function (new_month, new_year) {
    this.calendar_month = new_month;
    this.calendar_year = new_year;
    
    // If the handler has the 'setCalendarDetails (month, year)' method, call it.
    if (this.ajax_handler.setCalendarDetails) {
      this.ajax_handler.setCalendarDetails (new_month, new_year);
    }
    
    queue.request ('GET', this.request_url + '?month=' + new_month + '&year=' + new_year, this.ajax_handler);
  }
  
  /**
  * Sets the internal date pointer to the previous month and redraws the calendar
  **/
  this.prevMonth = function () {
    if (this.calendar_month == 1) {
      this.changeDate (12, this.calendar_year - 1);
    } else {
      this.changeDate (this.calendar_month - 1, this.calendar_year);
    }
  }
  
  /**
  * Sets the internal date pointer to the next month and redraws the calendar
  **/
  this.nextMonth = function () {
    if (this.calendar_month == 12) {
      this.changeDate (1, this.calendar_year + 1);
    } else {
      this.changeDate (this.calendar_month + 1, this.calendar_year);
    }
  }
  
  
  /**
  * This is the main function that draws the calendar
  *
  * @param day_defs An array of day definitions. The position in the array derermines what day it is for,
  *   so [ 5 => DayDefinition{id:'blah'}, 10 => DayDefinition{class:'whee'} ]
  *   means day 5 should have an id of 'blah', and day 10 should have a class of 'whee'.
  *
  * If you want to change how days are displayed, you probably want to override the getDayElement() method
  * rather than this one.
  **/
  this.draw = function (day_defs) {
    var tr;
    var td;
    var td_count = 0;
    
    if (day_defs == null) day_defs = [];
    
    // clear existing elements
    while (this.calendar_node.firstChild) {
      this.calendar_node.removeChild (this.calendar_node.firstChild);
    }
    
    // work out the first day of the month, and how many days in this month
    var date = new Date();
    date.setDate (1);
    date.setMonth (this.calendar_month - 1);
    date.setFullYear (this.calendar_year);
    var start_day = date.getDay ();
    var month_days = days_per_month (this.calendar_month, this.calendar_year);
    
    // create the first row
    tr = document.createElement ('tr');
    this.calendar_node.appendChild (tr);
    
    // create blanks before the first day
    for (i = 0; i < start_day; i++) {
      td = document.createElement ('td');
      td.appendChild (document.createTextNode ('\u00a0')); // &nbsp;
      tr.appendChild (td);
      td_count++;
    }
    
    for (i = 1; i <= month_days; i++) {
      // if the row is full, add another row
      if (td_count == 7) {
        this.calendar_node.appendChild (document.createTextNode ("\n"));
        tr = document.createElement ('tr');
        this.calendar_node.appendChild (tr);
        td_count = 0;
      }
      
      // Add this day
      var node = this.getDayElement (i, day_defs[i]);
      tr.appendChild (node);
      td_count++;
    }
    
    // Fill extra tds at the end
    while (td_count < 7) {
      td = document.createElement ('td');
      td.appendChild (document.createTextNode ('\u00a0'));
      tr.appendChild (td);
      td_count++;
    }
  }
  
  /**
  * Gets a TD element for a single day.
  *
  * @param string label The text to display in the TD
  * @param DayDefinition definition Additional information about this day
  **/
  this.getDayElement = function (label, definition) {
    var td_node = document.createElement ('td');
    
    // definition.href
    if (definition && definition.href) {
      var node = document.createElement ('a');
      node.setAttribute ('href', definition.href);
      td_node.appendChild(node);
      
      // not really a link
      if (definition.href == '#') {
        node.style.textDecoration = 'none';
        node.onclick = function () { return false; };
      }
      
    } else {
      var node = td_node;
    }
    node.appendChild (document.createTextNode (label));
    
    // definition.id
    if (definition && definition.id) {
      td_node.id = definition.id;
    }
    
    // definition.className
    if (definition && definition.className) {
      td_node.className = definition.className;
    }
    
    // definition.mouseEnter
    if (definition && definition.mouseEnter) {
      td_node.onmouseover = definition.mouseEnter;
    }
    
    // definition.mouseLeave
    if (definition && definition.mouseLeave) {
      td_node.onmouseout = definition.mouseLeave;
    }
    
    return td_node;
  }
}


/**
* This is used to provide extra data for a day, such as a link URL, id or class for the TD.
**/
function DayDefinition () {
  this.id = null;
  this.className = null;
  this.href = null;
  this.mouseEnter = null;
  this.mouseLeave = null;
}



//////  Utility functions  //////

/**
* Returns the number of days in a month
**/
function days_per_month (month, year) {
  var res;
  
  switch (Number (month)) {
    case 1:  res = 31; break;
    case 2:
      if (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)) {
        res = 29; // leap year
      } else {
        res = 28; // common year
      }
      break;
      
    case 3:  res = 31; break;
    case 4:  res = 30; break;
    case 5:  res = 31; break;
    case 6:  res = 30; break;
    case 7:  res = 31; break;
    case 8:  res = 31; break;
    case 9:  res = 30; break;
    case 10: res = 31; break;
    case 11: res = 30; break;
    case 12: res = 31; break;
    default:
      alert ('Error: ' + month + ' is not between 1 and 12');
  }
  
  return res;
}
