// calendar.js - A simple calendar widget
// requires jQuery 1.2.3

var SimpleCalendar = function(domId, options) {
  this.domId   = domId;
  this.options = options || {};
  
  this.today        = this.options.today || new Date();
  this.startDate    = this.__dateToInteger(this.options.startDate);
  this.endDate      = this.__dateToInteger(this.options.endDate);
  this.popup        = this.options.popup || true;
  
  this.selectedDate = this.today;
  this.container    = $("<div id='simple_calendar_container'></div>");
  
  // Add a stub for the date selected callback.
  this.dateSelectedCallback = function() {};
  
  // Append the calendar to the domId:
  this.__appendContainerToDom();
  
  // Build the initial month.
  this.thisMonth();
  
  return this;
};

SimpleCalendar.prototype = {
  Config: {
    // Path to the "next month" image:
    NextMonthImage: HG.Config.ImageUrl + "/calendar/calendar_forward.gif",
    
    // Path to the "previous month" image:
    PrevMonthImage: HG.Config.ImageUrl + "/calendar/calendar_back.gif"
  },
  
  Days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
  Months: ["January", "February", "March", "April", "May", "June", "July", 
           "August", "September", "October", "November", "December"],
  
  currentMonth: null,
  currentYear: null,
  
  toggle: function() { this.container.toggle(); },
  
  show: function()   { this.container.show(); },
  
  hide: function()   { this.container.hide(); },
  
  // Select today's date.
  selectToday: function() {
    this.thisMonth();
    this.selectDate(this.today);
  },
  
  // Select the current month in the calendar.
  thisMonth: function() {
    this.__populate(this.today.getMonth(), this.today.getFullYear());
  },
  
  nextMonth: function() {
    if (this.currentMonth < 11) {
      this.__populate((this.currentMonth + 1), this.currentYear);
    } else {
      this.__populate(0, (this.currentYear + 1));
    };
  },
  
  previousMonth: function() {
    if (this.currentMonth >= 1) {
      this.__populate((this.currentMonth - 1), this.currentYear);
    } else {
      this.__populate(11, (this.currentYear - 1));
    };
  },
  
  nextYear: function() {
    this.__populate(this.currentMonth, (this.currentYear + 1));
  },
  
  previousYear: function() {
    this.__populate(this.currentMonth, (this.currentYear - 1));
  },
  
  selectDate: function(dateObject, preventCallback, selectMonth) {
    this.selectedDate = dateObject;
    
    if (!preventCallback) { this.dateSelectedCallback() }
    
    if (selectMonth) { this.__populate(dateObject.getMonth(), dateObject.getFullYear()); }
    
    if (!this.popup) { this.hide(); }
  },
  
  // Allows you to add a callback that will run when a new date is selected
  // by the user.
  onDateSelected: function(callback) {
    this.dateSelectedCallback = function() { callback(this) };
  },
  
  // Formats the date object as a string - e.g. "Saturday 29 July, 2007".
  formatDate: function(dateObject) {
    var day   = dateObject.getDay();
    var date  = dateObject.getDate();
    var month = dateObject.getMonth();
    var year  = dateObject.getFullYear();
    
    return [this.Days[day], date, this.Months[month] + ",", year].join(" ")
  },
  
  __populate: function(month, year) {
    var array = this.__buildMonthArray(month, year);
    var table = this.__updateMonthTable(array, month, year);
    this.currentMonth = month;
    this.currentYear  = year;
    
    $("div#simple_calendar_container").empty().append(table);
    
    this.__asignEventHandlers();
  },
  
  __asignEventHandlers: function() {
    var calendar = this;
    
    this.table.find("a#__sc_next_month").click(function() { calendar.nextMonth()     });
    this.table.find("a#__sc_prev_month").click(function() { calendar.previousMonth() });
    
    this.table.find("a.day").click(function() {
      var dateParts = $(this).attr("href").split("-"),
          date      = new Date(dateParts[0], dateParts[1], dateParts[2]);
      
      calendar.selectDate(date);
      return false;
    });
  },
  
  __buildMonthArray: function(month, year) {
    var dayOfMonth = 1;
    var date       = new Date(year, month, dayOfMonth);
    var thisMonth  = date.getMonth();
    var endOfMonth = false;
    
    var month = [];
    var week  = [];
    
    while (!endOfMonth) {
      // Assign the first date to the correct position in the array
      // depending on the day of the week:
      if (dayOfMonth == 1) {
        week[date.getDay()] = date.getDate();
      } else {
        week.push( date.getDate() );
      };
      
      // Increment the day of the month.
      dayOfMonth += 1;
      date.setDate(dayOfMonth);
      
      if ((week.length % 7) == 0 ) {
        month.push(week);
        week = [];
      };
      
      if (date.getMonth() != thisMonth) {
        if (week.length > 0) { month.push(week) };
        endOfMonth = true;
      };
    };
    
    return month;
  },
  
  __buildMonthTable: function(monthArray, month, year) {
    var header = this.Months[month] + " " + year,
        calendar = this;
    
    // Build the basic table structure:
    this.table = $("<table><thead><tr><td class='arrow'><a href='#' id='__sc_prev_month'><img src='" +
                   this.Config.PrevMonthImage + "'/></a></td><th id='__sc_header' colspan='5'>" + header +
                   "</th><td class='arrow'><a href='#' onclick='return false' id='__sc_next_month'>" +
                   "<img src='" + this.Config.NextMonthImage + "'/></a></td></tr></thead><tbody></tbody></table>");
    
    // Append a header row to the table:
    this.table.children("thead").append(this.__buildHeaderRow());
    
    return this.table;
  },
  
  __updateMonthTable: function(monthArray, month, year) {
    if (!this.table) { this.__buildMonthTable(monthArray, month, year) };
    
    var header   = this.Months[month] + " " + year;
    var newMonth = document.createDocumentFragment();
    
    for (var mIndex in monthArray) {
      var tr       = document.createElement("tr");
      var week     = monthArray[mIndex];
      
      var weekCompleted = false;
      var wIndex = 0;
      
      while (!weekCompleted) {
        var day = week[wIndex];
        var td  = this.__createDayCell(year, month, day);
        tr.appendChild(td);
        
        wIndex += 1;
        
        if (wIndex == 7) {weekCompleted = true};
      };
      
      newMonth.appendChild(tr);      
    };
    
    $(this.table).find("th#__sc_header").empty().append(header);
    $(this.table).find("tbody").empty().append(newMonth);
    
    return this.table;
  },
  
  __buildHeaderRow: function() {
    var tr, td, text, day;
    
    tr = document.createElement("tr");
    
    for (var i in this.Days) {
      day  = this.Days[i];
      td   = document.createElement("td");
      $(td).addClass("day_header");
      text = document.createTextNode( day.substring(0,1) );
      
      td.appendChild(text);
      tr.appendChild(td);
    }
    
    return tr;
  },
  
  __createDayCell: function(year, month, day) {
    var td          = document.createElement("td"),
        a           = document.createElement("a"),
        text        = document.createTextNode(day),
        dayContents = null;
    
    a.href      = [year, month, day].join("-");
    a.className = "day";
    
    if (day) {
      if (this.__withinDateLimits(year, month, day)) {
        a.appendChild(text);
        dayContents = a;
      } else {
        dayContents = text;
      }
      $(td).addClass("valid");    
    } else {
      $(td).addClass("invalid");
    };
    
    if (dayContents) { td.appendChild(dayContents) };
    
    return td;
  },
  
  __appendContainerToDom: function() {
    $("#" + this.domId).append(this.container);
  },
  
  __dateToInteger: function(dateObject) {
    if (typeof dateObject === "object") {
      var dateInteger = this.__ymdToInteger(dateObject.getFullYear().toString(), 
                                            dateObject.getMonth().toString(), 
                                            dateObject.getDate().toString())
      
      return dateInteger;
    }
  },
  
  __withinDateLimits: function(year, month, day) {
    var dateInteger = this.__ymdToInteger(year, month, day);
        
    if (dateInteger >= (this.startDate || 0) && dateInteger <= (this.endDate || 99999999)) {
      return true
    } else {
      return false
    }
  },
  
  __ymdToInteger: function(year, month, day) {
    var year = year.toString(), month = month.toString(), day = day.toString();
    
    month = (month.length === 1) ? ("0" + month) : month;
    date  = (day.length   === 1) ? ("0" + day)   : day;
    
    return +([year, month, date].join(""));
  }
};
