
var dateFieldCalendars = [];

var DateFieldCalendarController = Class.create({
  
  init:function()
  {
    dateFieldCalendars.each(function(fieldName) {
      var inputEl = $(fieldName+'_field');
      
      // setup show calendar event
      var eventCallback = function(clickEvent) { this.toggleCalendarForField(inputEl, clickEvent); }.bind(this);
      $(fieldName+'_field_toggle_calendar').observe('click', eventCallback);
      
    }.bind(this));
  },
  
  toggleCalendarForField:function(inputEl, clickEvent)
  {
    // kill the click event
    if (clickEvent)
      clickEvent.stop();
    
    // if exists, kill it
    if ($(inputEl.id+'_calendar')) {
      $(inputEl.id+'_calendar').remove();
      return;
    }
    
    // calculate the date to show
    var date = new Date(inputEl.value);
    if (!date.getFullYear()) // full year will be NaN if couldn't load date from field
      date = new Date();
    
    // show the calendar
    this.createCalendarElForInput(inputEl, date);
    
    // observe document clicks to close the calendar
    if (!this.inputsWithClickObserver)
      this.inputsWithClickObserver = {};
    
    if (!this.inputsWithClickObserver[inputEl.id]) {
      this.inputsWithClickObserver[inputEl.id] = true;
      document.observe('click', this.documentClicked.bindAsEventListener(this, inputEl));
    }
  },
  
  documentClicked:function(clickEvent, inputEl)
  {
    // if no calendar visible, do nothing
    if (!$(inputEl.id+'_calendar'))
      return;
    
    this.toggleCalendarForField(inputEl, clickEvent);
  },
  
  createCalendarElForInput:function(inputEl, date)
  {
    // create calendar
    var calendarEl = new Element('div', { 'class': 'calendar', 'id': inputEl.id+'_calendar' });
    calendarEl.className = 'calendar';
    
    
    // create header
    var headerEl = new Element('div', { 'class': 'calendar-header' });
    headerEl.className = 'calendar-header';
      var prevMonth = new Element('a', { 'href': '#' }).update('«');
      prevMonth.observe('click', function(clickEvent) {
        clickEvent.stop();
        this.moveCalendarMonth(inputEl, date, -1);
      }.bind(this));
      headerEl.appendChild(prevMonth);
      
      var headerSpan = new Element('span').update(this.displayMonthFromDate(date)+' '+date.getFullYear());
      headerEl.appendChild(headerSpan);
      
      var nextMonth = new Element('a', { 'href': '#' }).update('»');
      nextMonth.observe('click', function(clickEvent) {
        clickEvent.stop();
        this.moveCalendarMonth(inputEl, date, 1);
      }.bind(this));
      headerEl.appendChild(nextMonth);
    calendarEl.appendChild(headerEl);
    
    
    // days of week
    var daysOfWeekDiv = new Element('div', { 'class': 'calendar-days' });
    daysOfWeekDiv.className = 'calendar-days';
      var daySpan = new Element('span').update('M');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('T');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('W');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('T');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('F');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('S');
      daysOfWeekDiv.appendChild(daySpan);
      var daySpan = new Element('span').update('S');
      daysOfWeekDiv.appendChild(daySpan);
    calendarEl.appendChild(daysOfWeekDiv);
    
    
    // insert cells
    var today = new Date();
    var todayIso = this.makeIsoDate(today.getDate(), today.getMonth()+1, today.getFullYear());
    var cellsDiv = new Element('div', { 'class': 'calendar-cells' });
    cellsDiv.className = 'calendar-cells';
      this.buildCalendarCells(date).each(function(cell) {
        
        var cellEl = new Element('a', { 'href': '#' }).update(cell['display_value']);
        
        if (cell['date'] != '') {
          cellEl.observe('click', function(clickEvent) {
            inputEl.value = cell['display_date'];
            $(inputEl.id+'_calendar').remove();
            clickEvent.stop();
            inputEl.fire('calendar:change');
          });
          
          if (cell['display_date'] == inputEl.value) {
            cellEl.addClassName('selected');
          } else if (todayIso == cell['date']) {
            cellEl.addClassName('today');
          }
        } else {
          cellEl.addClassName('other-month');
        }
        
        cellsDiv.appendChild(cellEl);
        
      }.bind(this));
    calendarEl.appendChild(cellsDiv);
    
    
    // insert calendar into page
    inputEl.parentNode.appendChild(calendarEl);
  },
  
  moveCalendarMonth:function(inputEl, date, delta)
  {
    // calcelate date from delta
    var newDate = new Date();
    newDate.setDate(1); // set the "day" of the date to the 1st of the month. If we don't do this, it will be the current day and if the month we are moving too does not have that many days (eg: if it's the 30th of january and we move to february), then we will fail to move the calendar.
    if (date.getMonth() + delta < 0) {
      newDate.setMonth(12 + delta); // if delta is -1, this will set month to 11
      newDate.setYear(date.getFullYear() - 1);
    } else if (date.getMonth() + delta > 11) {
      newDate.setMonth(-1 + delta); // if delta is 1, this will set month to 0
      newDate.setYear(date.getFullYear() + 1);
    } else {
      newDate.setMonth(date.getMonth() + delta);
      newDate.setYear(date.getFullYear()); // for some reason, webkit goes back to the wrong year if you don't do this
    }
    
    // kill the old calendar
    $(inputEl.id+'_calendar').remove();
    
    // insert new calendar
    this.createCalendarElForInput(inputEl, newDate);
  },
  
  displayMonthFromDate:function(date)
  {
    var months = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
      'July',
      'August',
      'September',
      'October',
      'November',
      'December'
    ];
    
    return months[date.getMonth()];
  },
  
  countDaysInMonth:function(year, month) // year is required for leap year support. month must be as a "human" month (ie. january is 1, not 0)
  {
    return 32 - new Date(year, (month - 1), 32).getDate();
  },
  
  buildCalendarCells:function(date)
  {
    // init cells
    var cells = [];
    
    // prepare
    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    var numDaysInMonth = this.countDaysInMonth(year, month);
    
    // add cells from last month
    var firstDate = new Date();
    firstDate.setDate(1);
    firstDate.setMonth(date.getMonth());
    firstDate.setYear(date.getFullYear());
    var startDayOfWeek = firstDate.getDay();
    for (var i = 0; i < startDayOfWeek; i++) {
      cells.push({ 'date': '', 'display_value': '&nbsp;' });
    }
    
    // add cells for this month
    var day = 0;
    for (var i = 0; i < numDaysInMonth; i++) {
      day++;
      
      cells.push({
        'date': this.makeIsoDate(day, month, year),
        'display_date': this.makeDisplayDate(day, month, year),
        'display_value': day
      });
    }
    
    // fill up the last week with blank cells
    while ((cells.length % 7) != 0) {
      cells.push({ 'date': '', 'display_value': '&nbsp;' });
    }
    
    return cells;
  },
  
  makeIsoDate:function(day, month, year)
  {
    if (day < 10)
      day = '0'+day;
    if (month < 10)
      month = '0'+month;
    
    return year+'-'+month+'-'+day;
  },
  
  makeDisplayDate:function(day, month, year)
  {
    var date = new Date();
    date.setDate(day);
    date.setMonth(month-1);
    date.setYear(year);
    
    return date.getDate()+' '+this.displayMonthFromDate(date)+' '+date.getFullYear();
  }
  
});

document.observe('dom:loaded', function() {
  new DateFieldCalendarController().init();
});

