/* 'Magic' date parsing, by Simon Willison (6th October 2003)
   http://simon.incutio.com/archive/2003/10/06/betterDateInput
   Modified: 11 Feb 2004, John Lloyd-Jones
   Added attachment and insertion of message <div>: 17 Mar 2004, John Lloyd-Jones
   Merged in popup calendar : 17 Mar 2004, John Lloyd-Jones
*/
var _us_date_format = true;
var _err_class = 'error';
var _date_format = _us_date_format ? '(mm/dd/yyyy)' : '(dd/mm/yyyy)';
var _msg_suffix = 'Msg';
var _calendar_format = '%m/%d/%Y';
var _init_magicDate = function () {};
if (document.getElementsByTagName) _init_magicDate = function (){is=document.getElementsByTagName('input'); for (n=0;n<is.length;n++) { var i=is[n]; if(hasClass (i, 'date')&&(i.id)&&(! i.readOnly)){_makeDateMagic(i);_makeCalendarPopup (i);}}}
function _makeDateMagic (input){addEvent (input,"blur",_validateDate);addEvent (input,"focus",_selectEntered);var div=document.createElement('div');div.id=input.id+_msg_suffix;var node=document.createTextNode(_date_format);div.appendChild(node);var obj=input;while(obj.tagName!='div'&&(obj.className.indexOf('row')==-1)){obj=obj.parentNode;}obj.appendChild(div);if(input.value&&input.value.length>0)magicDate(input);}
function _makeCalendarPopup(o){var targetId=o.id;var span=o.parentNode;if(span&&span.tagName=='SPAN'){var btn=document.createElement('input');btn.type='button';btn.className='calbtn';btn.title='Pick a date';btn.targetId=targetId;var node=span.firstChild;while(node&&(node.tagName!='INPUT')){node=node.nextSibling;}var nextTag=node;while(nextTag&&nextTag.tagName){nextTag=nextTag.nextSibling;}if(node&&nextTag){span.insertBefore(btn,nextTag);}else{span.appendChild (btn);}addEvent(btn, "click",_popupCalendar);}}
function _popup_selected(cal, date) { var dt = new Date (date); cal.sel.value = date; if (dt != date) { cal.callCloseHandler();  magicDate (cal.sel); }}
function closeHandler(cal) { cal.hide(); calendar = null; }
function _selectEntered (elm){ var target = getEventSource (elm); if (target.className.indexOf ('error') == -1) target.select()}
function _validateDate (elm){  var target = getEventSource (elm);  magicDate (target); }

function _popupCalendar (elm)
{
  var el = getEventSource (elm); // the button
  var inp = document.getElementById (el.targetId); // associated text input field 
  if (calendar != null) 
  {
    calendar.hide();                 // so we hide it first.
  } 
  else 
  {
    // first-time call, create the calendar.
    var cal = new Calendar(true, null, _popup_selected, closeHandler);
    cal.showsOtherMonths = true;

    calendar = cal;                  // remember it in the global var
    cal.setRange(1900, 2070);        // min/max year allowed.
    cal.create();
  }
  calendar.setDateFormat(_calendar_format);    // set the specified date format
  calendar.parseDate(inp.value);      // try to parse the text in field
  calendar.sel = inp;                 // inform it what input field we use

  // the reference element that we pass to showAtElement is the button that
  // triggers the calendar.  In this example we align the calendar bottom-right
  // to the button.
  calendar.showAtElement(el, "Br");        // show the calendar

  return false;
}

/* Finds the index of the first occurence of item in the array, or -1 if not found */
Array.prototype.indexOf = function(item) 
{
    for (var i = 0; i < this.length; i++) 
    {
        if (this[i] == item) 
        {
            return i;
        }
    }
    return -1;
};
/* Returns an array of items judged 'true' by the passed in test function */
Array.prototype.filter = function(test) 
{
    var matches = [];
    for (var i = 0; i < this.length; i++) 
    {
        if (test(this[i])) 
        {
            matches[matches.length] = this[i];
        }
    }
    return matches;
};

var monthNames = "January February March April May June July August September October November December".split(" ");
var weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");

/* Takes a string, returns the index of the month matching that string, throws
   an error if 0 or more than 1 matches
*/
function parseMonth(month) 
{
    var matches = monthNames.filter(function(item) { 
        return new RegExp("^" + month, "i").test(item);
    });
    if (matches.length == 0) {
        throw new Error("Invalid month string");
    }
    if (matches.length > 1) {
        throw new Error("Ambiguous month");
    }
    return monthNames.indexOf(matches[0]);
}
/* Same as parseMonth but for days of the week */
function parseWeekday(weekday) {
    var matches = weekdayNames.filter(function(item) {
        return new RegExp("^" + weekday, "i").test(item);
    });
    if (matches.length == 0) {
        throw new Error("Invalid day string");
    }
    if (matches.length > 1) {
        throw new Error("Ambiguous weekday");
    }
    return weekdayNames.indexOf(matches[0]);
}

/* Array of objects, each has 're', a regular expression and 'handler', a 
   function for creating a date from something that matches the regular 
   expression. Handlers may throw errors if string is unparseable. 
*/
var dateParsePatterns = [
    // Today
    {   re: /^tod/i,
        handler: function() { 
            return new Date();
        } 
    },
    // Tomorrow
    {   re: /^tom/i,
        handler: function() {
            var d = new Date(); 
            d.setDate(d.getDate() + 1); 
            return d;
        }
    },
    // Yesterday
    {   re: /^yes/i,
        handler: function() {
            var d = new Date();
            d.setDate(d.getDate() - 1);
            return d;
        }
    },
    // 4th
    {   re: /^(\d{1,2})(st|nd|rd|th)?$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[1], 10));
            return d;
        }
    },
    // 4th Jan
    {   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+)$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[1], 10));
            d.setMonth(parseMonth(bits[2]));
            return d;
        }
    },
    // 4th Jan 2003
    {   re: /^(\d{1,2})(?:st|nd|rd|th)? (\w+),? (\d{4})$/i,
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[1], 10));
            d.setMonth(parseMonth(bits[2]));
            d.setYear(bits[3]);
            return d;
        }
    },
    // Jan 4th
    {   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?$/i, 
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[2], 10));
            d.setMonth(parseMonth(bits[1]));
            return d;
        }
    },
    // Jan 4th 2003
    {   re: /^(\w+) (\d{1,2})(?:st|nd|rd|th)?,? (\d{4})$/i,
        handler: function(bits) {
            var d = new Date();
            d.setDate(parseInt(bits[2], 10));
            d.setMonth(parseMonth(bits[1]));
            d.setYear(bits[3]);
            return d;
        }
    },
    // next Tuesday - this is suspect due to weird meaning of "next"
    {   re: /^next (\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = newDay - day;
            if (newDay <= day) {
                addDays += 7;
            }
            d.setDate(d.getDate() + addDays);
            return d;
        }
    },
    // last Tuesday
    {   re: /^last (\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = day - newDay;
            if (newDay >= day) {
                addDays += 7;
            }
            d.setDate(d.getDate() - addDays);
            return d;
        }
    },
    // previous Tuesday
    {   re: /^prev (\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = day - newDay;
            // if (newDay >= day) 
            {
                addDays += 7;
            }
            d.setDate(d.getDate() - addDays);
            return d;
        }
    },
    // this Tuesday
    {   re: /^this (\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = day - newDay;
            d.setDate(d.getDate() - addDays);
            return d;
        }
    },
    // Tuesday
    {   re: /^(\w+)$/i,
        handler: function(bits) {
            var d = new Date();
            var day = d.getDay();
            var newDay = parseWeekday(bits[1]);
            var addDays = day - newDay;
            d.setDate(d.getDate() - addDays);
            return d;
        }
    },
    // dd/mm/yyyy (Euro style) or mm/dd/yyyy (American style)
    {   re: /(\d{1,2})\/(\d{1,2})\/(\d{2,4})/,
        handler: function(bits) {
            var d = new Date();
            var yr = parseInt(bits[3], 10);
            yr = (yr < 100 ? (2000+yr) : yr);
            if (_us_date_format)
            {
               d.setFullYear(yr, parseInt(bits[1], 10) - 1, parseInt(bits[2], 10));
            }
            else
            {
               d.setFullYear(yr, parseInt(bits[2], 10) - 1, parseInt(bits[1], 10));
            }
            return d;
        }
    },
    // yyyy-mm-dd (ISO style)
    {   re: /(\d{4})-(\d{1,2})-(\d{1,2})/,
        handler: function(bits) {
            var d = new Date();
            d.setFullYear(parseInt(bits[1], 10), parseInt(bits[2], 10) - 1, parseInt(bits[3], 10))
            return d;
        }
    },
];

function parseDateString(s) {
    for (var i = 0; i < dateParsePatterns.length; i++) {
        var re = dateParsePatterns[i].re;
        var handler = dateParsePatterns[i].handler;
        var bits = re.exec(s);
        if (bits) {
            return handler(bits);
        }
    }
    throw new Error("Invalid date string");
}

function magicDate(input) 
{
   var messagespan = input.id + _msg_suffix;
   var o = document.getElementById (messagespan);
   try 
   {
      var txt = input.value;
      var msg = _date_format;
      input.valid = null;
      if (txt && txt.length > 0)
      {
         var d = parseDateString(input.value);
         if (_us_date_format)
            input.value = (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear();
         else
            input.value = d.getDate() + '/' + (d.getMonth() + 1) + '/' + d.getFullYear();
         msg = d.toDateString();
         input.valid = true;
      }  
      input.className = _elide (input.className, _err_class);
      if (o != null)
      {
         o.firstChild.nodeValue = msg;
         o.className = _elide (o.className, _err_class);
      }
   }
   catch (e) 
   {
       var s = input.className;
       if (s.indexOf (_err_class) == -1)
          input.className += ' error';
       input.valid = false;
       var message = e.message;
        // Fix for IE6 bug
       if (message.indexOf('is null or not an object') > -1) 
       { 
           message = 'Invalid date string';
       }
       if (o != null)
       {
          o.firstChild.nodeValue = message;
          if (o.className.indexOf (_err_class) == -1)
             o.className += ' error';
       }
    }
}

addEvent(window, "load", _init_magicDate);
