
/*********** start of date utility functions ********************************/

/**
  * compares to another date object and returns true if
  * day and month are the same (year value is ignored!).
  */
Date.prototype.equalsDayOfYear = function(dateObj) {
   return (dateObj.getMonth() == this.getMonth() &&
       dateObj.getDate()  == this.getDate());
}

/**
  * compares to another date object and returns true if
  * day, month and year are the same.
  */
Date.prototype.equalsDate = function(dateObj) {
   return (dateObj.getMonth() == this.getMonth() &&
       dateObj.getDate()  == this.getDate() &&
       dateObj.getFullYear() == this.getFullYear());
}

/**
  * returns a short string for this date: YYYYMMDD (MM: 1 = january!)
  */
Date.prototype.getDateString = function() {
   var str = this.getFullYear();
   var m = String(this.getMonth() + 1);
   str += (m < 10) ? "0" + m : m;
   var d = String(this.getDate());
   str += (d < 10) ? "0" + d : d;
   return str;
}

/**
  * utility: returns the name of the month, using global array monthNames
  */
Date.prototype.getMonthName = function() {
   return monthNames[this.getMonth()];
}

/**
  * utility: returns the two-digit representation of the year
  */
Date.prototype.getShortYear = function() {
   var str = String(this.getFullYear());
   return str.substring(2,4);
}

/**
  * utility: get a date object pointing to the first day of the month of dateObj
  */
Date.prototype.firstOfMonth = function() {
   var dateObj = new Date(this.getFullYear(), this.getMonth(), this.getDate());
   dateObj.setDate(1);
   return dateObj;
}

/**
  * utility: get a date object pointing to the first day of the week of dateObj
  */
Date.prototype.firstOfWeek = function() {
   var dateObj = new Date(this.getFullYear(), this.getMonth(), this.getDate());
   var wday = dateObj.getDay();
   wday = (wday == 0) ? 6 : wday - 1;
   dateObj.setDate(dateObj.getDate() - wday);
   return dateObj;
}

/**
  * utility: returns a date object set to 1st of previous month
  */
Date.prototype.prevMonth = function() {
   return new Date(this.getFullYear(), this.getMonth() - 1, 1, this.getHours(), this.getMinutes(), this.getSeconds());
}

/**
  * utility: returns a date object set to 1st of next month
  */
Date.prototype.nextMonth = function() {
   return new Date(this.getFullYear(), this.getMonth() + 1, 1, this.getHours(), this.getMinutes(), this.getSeconds());
}

Date.prototype.nextDate = function(offset) {
   if (!offset)
      offset = 1;
   return new Date(this.getFullYear(), this.getMonth(), this.getDate() + offset, this.getHours(), this.getMinutes(), this.getSeconds());
}

/**
  * utility: clone a date object
  */
Date.prototype.clone = function() {
   return new Date(this.getFullYear(), this.getMonth(), this.getDate(), this.getHours(), this.getMinutes(), this.getSeconds());
}


/**
  * checks wheter this date object points to a holiday (Austria!)
  */
Date.prototype.isHoliday = function() {
   if (this.getDay() == 0) {
      return true;
   }
   return (holidays[this.getDateString()] == true) ? true : false;
}



var monthNames   = ["Jänner", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"];

var holidays = {};
var fixedHolidays = ["0101", "0106", "0501", "0815", "1026", "1101", "1208", "1225", "1226"];

for (var i=-1; i<3; i++) {
   var y = (new Date()).getFullYear() + i;
   // first, the fixed holidays:
   for (var j=0; j<fixedHolidays.length; j++) {
      holidays[String(y) + fixedHolidays[j]] = true;
   }
   // now, calc easter:
   var d = (y < 1700 ? 10 : y < 1800 ? 11 : y < 1900 ? 12 : y < 2100 ? 13 :
        y < 2200 ? 14 : y < 2300 ? 15 : 16);
   var m = (y < 1700 ? 202 : y < 1900 ? 203 : y < 2200 ? 204 :
        y < 2300 ? 205 : 206);
   var q = y / 4;
   r = Math.round(q);
   if (r > q) r--;
   var q = (q - (q - r));
   var b = (m - 11* (y % 19)) % 30;
   if (b==28) { _b=27; }
   if (b==29) { _b=28; }
   var c = (y - (-q) - (-b) - d) % 7;
   var easter = new Date(y, 2, 28 - (-b) -c);
   holidays[String(easter.getDateString())] = true;
   // now the other spring holidays dependent on easter:
   holidays[String(easter.nextDate(39).getDateString())] = true;
   holidays[String(easter.nextDate(50).getDateString())] = true;
   holidays[String(easter.nextDate(60).getDateString())] = true;
}


/*********** start of node utility functions ********************************/



/**
  * utility: create an <a>-node and a text childnode
  */
function appendLinkNode(parentElement, href, text, className) {
   var linkNode = document.createElement("a");
   linkNode.setAttribute("href", href);
   if (className) {
      linkNode.className = className;
   }
   linkNode.appendChild(document.createTextNode(text));
   parentElement.appendChild(linkNode);
   return linkNode;
}

/**
  * utility: create a child node and append it to given node
  */
function appendElement(parentElement, elementName, attr) {
   var childNode = document.createElement(elementName);
   if (attr) {
      for (var key in attr) {
         childNode[key] = attr[key];
      }
   }
   parentElement.appendChild(childNode);
   return childNode;
}

/**
  * utility: create a text node and append it to given node
  */
function appendText(parentElement, text) {
   var textNode = document.createTextNode(text);
   parentElement.appendChild(textNode);
   return textNode;
}


String.prototype.trim = function() {
   return this.replace(/\s*/g, "");
}



/*********** start of render functions ********************************/


function renderCalendar(dateStr, id) {
   var rootNode = document.getElementById(id + "_calendar");
   var showDate = new Date(dateStr.substring(0,4), (dateStr.substring(4,6) - 1), 1);
   if (rootNode.firstChild != null) {
      rootNode.replaceChild(renderTable(showDate, id), rootNode.firstChild);
   } else {
      rootNode.appendChild(renderTable(showDate, id));
   }
}


function renderTable(showDate, id) {
   var table = document.createElement("table");
   table.className = "calendarTable";
   table = renderCalendarHead(showDate, id, table);
   return renderCalendarRows(showDate, id, table);
}


function renderCalendarHead(showDate, id, table) {
   var thead = appendElement(table, "thead");
   var tr    = appendElement(thead, "tr");

   var th = appendElement(tr, "th", {className:"calendarMonthPrev"});
   var url = "javascript:renderCalendar('" + showDate.prevMonth().getDateString() + "','" + id + "');";
   if (showDate.prevMonth() > (new Date()).prevMonth()) {
      appendLinkNode(th, url, "<", th.className);
   }

   var th = appendElement(tr, "th", {className:"calendarMonthname", colSpan:"5"});
   appendText(th, showDate.getMonthName() + " " + showDate.getShortYear());

   var th = appendElement(tr, "th", {className:"calendarMonthNext"});
   var url = "javascript:renderCalendar('" + showDate.nextMonth().getDateString() + "', '" + id + "');";
   appendLinkNode(th, url, ">", th.className);

   var tr = appendElement(thead, "tr");
   var weekdayChars = ["M", "D", "M", "D", "F", "S", "S"];
   for (var i=0; i<7; i++) {
      var th = appendElement(tr, "th", {className: "calendarWeekday"});
      appendText(th, weekdayChars[i]);
   }
   return table;
}


function renderCalendarRows(showDate, id, table) {
   var tbody = appendElement(table, "tbody");

   var dateObj = showDate.firstOfMonth();
   var showWeeks = (dateObj.getDay() > 5 || dateObj.getDay() == 0) ? 6 : 5;
   dateObj = dateObj.firstOfWeek();
   for (var i=0; i<showWeeks; i++) {
      var tr = appendElement(tbody, "tr");
      for (var j=0; j<7; j++) {
         if (dateObj.getMonth() == showDate.getMonth()) {
            var className = (dateObj.isHoliday()) ? "calendarHolidayCurrentMonth" : "calendarDayCurrentMonth";
         } else {
            var className = (dateObj.isHoliday()) ? "calendarHolidayOtherMonth" : "calendarDayOtherMonth";
         }
         var td = appendElement(tr, "td", {className:className});
         var str = String(dateObj.getDate());
         var url = "javascript:setDateValue('" + id + "', " + dateObj.getDateString() + ");";
         appendLinkNode(td, url, str, className);
         dateObj = dateObj.nextDate();
      }
   }
   return table;
}



/*********** start of form functions ********************************/



function switchCalendar(id) {
   var rootNode = document.getElementById(id + "_calendar");
   if (rootNode.firstChild != null) {
      // switch off calendar
      rootNode.removeChild(rootNode.firstChild);
      return true;
   }
   // switch on calendar:
   if (id == "enddate") {
      var dateObj = (isValidDate(id)) ? getDateValue(id) : getDateValue("startdate");
   } else {
      var dateObj = getDateValue(id);
   }
   renderCalendar(dateObj.getDateString(), id);
   return true;
}


function setDateValue(id, dateStr) {
   dateStr = String(dateStr);
   document.getElementsByName(id + "_year")[0].value  = dateStr.substring(0,4);
   document.getElementsByName(id + "_month")[0].value = dateStr.substring(4,6);
   document.getElementsByName(id + "_date")[0].value  = dateStr.substring(6,8);
   switchCalendar(id);
}


function getDateValue(id) {
   var now = new Date();
   var year  = document.getElementsByName(id + "_year")[0].value.trim();
   year = (year == "" || isNaN(year)) ? now.getFullYear() : year;
   var month = document.getElementsByName(id + "_month")[0].value.trim();
   month = (month == "" || isNaN(month)) ? now.getMonth() : parseInt(month) - 1;
   var date  = document.getElementsByName(id + "_date")[0].value.trim();
   date = (date == "" || isNaN(date)) ? now.getDate() : date;
   return new Date(year, month, date);
}


function isValidDate(id) {
   var now = new Date();
   var year  = document.getElementsByName(id + "_year")[0].value.trim();
   var month = document.getElementsByName(id + "_month")[0].value.trim();
   var date  = document.getElementsByName(id + "_date")[0].value.trim();
   if (date == "" || isNaN(date) || month == "" || isNaN(month) ||
       date < 1 || date > 31 || month < 1 || month > 12) {
      return false;
   }
   if (year != "") {
      if (isNaN(year) || year < (now.getFullYear() - 1) || year > 2035) {
         return false;
      }
   }
   return true;
}


function checkDate(formEl) {
   formEl.value = formEl.value.trim();
   if (formEl.value == "" || isNaN(formEl.value)) {
      formEl.value = "";
      return;
   }
   formEl.value = (formEl.value < 1) ? "01" : formEl.value;
   formEl.value = (formEl.value > 31) ? "31" : formEl.value;
   formEl.value = (formEl.value < 10) ? "0" + parseInt(formEl.value) : formEl.value;
}

function checkMonth(formEl) {
   formEl.value = formEl.value.trim();
   if (formEl.value == "" || isNaN(formEl.value)) {
      formEl.value = "";
      return;
   }
   formEl.value = (formEl.value < 1) ? "01" : formEl.value;
   formEl.value = (formEl.value > 12) ? "12" : formEl.value;
   formEl.value = (formEl.value < 10) ? "0" + parseInt(formEl.value) : formEl.value;
}

function checkYear(formEl) {
   formEl.value = formEl.value.trim();
   if (formEl.value == "" || isNaN(formEl.value)) {
      formEl.value = "";
      return;
   }
   var now = new Date();
   formEl.value = (formEl.value < (now.getFullYear() - 1)) ? now.getFullYear() : formEl.value;
   formEl.value = (formEl.value > 2035) ? now.getFullYear() : formEl.value;
}
