/* calendar utilies */

/* hafas_standard_calendar_utilties.js */
/* initial built: 05.11.2007 by MHEI */

function cEl(name) {return document.createElement(name);}

String.prototype.leftPad = function (l, c) { return new Array(l - this.length + 1).join(c || '0') + this; }

// some more Date object enhancements (see hafas_utilities.js):
Date.prototype.shiftD = function(value) {
   this.setDate(this.getDate()+value);
}

Date.prototype.shiftM = function(value) {
   this.setMonth(this.getMonth()+value);
}

Date.prototype.shiftY = function(value) {
   this.setYear(this.getYear()+value);
}

Date.prototype.getDaysInMonth = function() {
   return new Date(this.getFullYear(),this.getMonth()+1,1,-1).getDate();
}

function getWeek(date) {
  var d = new Date(date);
  var DoW = d.getDay();
  d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
  var ms = d.valueOf(); // GMT
  d.setMonth(0);
  d.setDate(4); // Thu in Week 1
  return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
}

/* parses Strings into date. Uses the global defined dateformat (var: dateFormat, e.g. "%m/%d/%Y") */
function parseUserDateInput(userInput,weekdaysTexts,format) {
   if (typeof(format) == "undefined") {
       format = dateFormat;
   }
   if (isString(userInput) && (userInput.length>0) && !isDate(userInput)) {
       if ((userInput.length > 2)) {
         return dateinterpret(userInput, format);
      }
      else if (userInput.length == 2) {
         return mydoWeekday(userInput);
      }
   } else if (isDate(userInput)) {
      return userInput;
   }
   return new Date();
}

function mydoWeekday(aValue) {
   if ((aValue.length == 2) && (aValue.indexOf("+") == -1)) {
     var wDay = weekdayTexts.join("|").toLowerCase();
     var reg = eval("/^ *(" + wDay + ") */");
     aValue = aValue.toLowerCase();
     var test = aValue.match(reg);
     if (test != null) {
       var date = new Date();
       var cwdidx = date.getDay() - 1;
       cwdidx = (cwdidx == -1) ? cwdidx = 6 : cwdidx;
       var nwdidx = (wDay.indexOf(test[0]) / 3);
       if (nwdidx < cwdidx) {
          nwdidx = nwdidx + 7 - cwdidx;
       } else {
          nwdidx = nwdidx - cwdidx;
       }
        date.shiftD(nwdidx);
        return date;
     } else {
        return aValue;
     }
   } else if (aValue.indexOf("+") == 0) {
       var date = new Date();
       date.shiftD(1*aValue.slice(1));
       return date;
   }
}

function dateinterpret(str, format) {
    // Loop throught the format string, and build the regex pattern
    // for extracting the date elements.
    var pattern = "";
    var position = 1;
    var c = "";
    for (var i = 0, len = format.length; i < len; i++) {
       c = format.charAt(i);
       //if (format.charAt(i-1) == "%") {
       //   continue;
       //}

       if (c == "%") {
          c = format.substring(i,i+2);
          i++;
       }
       switch (c) {
            case '%Y' :
            case '%y' :
                pattern += '(\\d{4}|\\d{2})';
                yearPos = position++;
                break;
            case '%m' :
                pattern += '(\\d{1,2})';
                monthPos = position++;
                monthStyle = 'm'
                break;
            case '%d' :
                pattern += '(\\d{1,2})';
                dayPos = position++;
                break;
            default :
                pattern += (c == '^' ? format.charAt(++i) : c);
        }
    }

    // Pull out the date elements from the input string
    var matches = str.match(new RegExp(pattern));
    if (!matches)
        return null;

     // Now we have to interpret each of those parts...

    if (yearPos > -1) {
        year = parseInt(matches[yearPos], 10);
        year = (year < 50 ? year + 2000 : (year < 100 ? year + 1900 : year));
    }

    if (monthPos > -1) {
        switch (monthStyle) {
            case 'm':
                month = parseInt(matches[monthPos], 10) - 1;    // JavaScript months are zero based, user input generally is not.
                if (month > 11)
                    return null;
                break;
            case 'N':
                month = parseInt(Date.monthNumbers[matches[monthPos]], 10);
                if (isNaN(month))
                    return null;
                break;
            case 'n':
                month = parseInt(Date.shortMonthNumbers[matches[monthPos]], 10);
                if (isNaN(month))
                    return null;
                break;
        }
    }

    if (dayPos > -1) {
        day = parseInt(matches[dayPos], 10);
        var dt = new Date(year, month, 1);
        if ((day < 1) || (day > dt.getDaysInMonth()))
            return null;
    }

    return new Date(year, month, day);
}

function isFunction(o) {
  return (typeof(o)=="function");
}

function isObject(a)
{
  return (typeof a == 'object' && !!a) || isFunction(a);
}

function isArray(a)
{
  return isObject(a) && a.constructor == Array;
}

function isDate(a)
{
  return isObject(a) && a.constructor == Date;
}

function isString(a)
{
  return typeof a == 'string';
}

function inherits(base, extension)
{
   for ( var property in base )
   {
      try
      {
         extension[property] = base[property];
      }
      catch( warning ){}
   }
}

function inheritsPartly(base, extension, properties)
{
   for ( var i = 0; i < properties.length; i++ )
   {
      try
      {
         extension[properties[i]] = base[properties[i]];
      }
      catch( warning ){}
   }
}

function makeObservable (obj, observer) {
   inherits(new Observable(), obj);
   if (observer != undefined) {
      obj.addObserver(observer);
   }
}

Array.prototype.forEach = function(fn, thisObj) {
    var scope = thisObj || window;
    for ( var i=0, j=this.length; i < j; ++i ) {
        fn.call(scope, this[i], i, this);
    }
};

Array.prototype.filter = function(fn, thisObj) {
    var scope = thisObj || window;
    var a = [];
    for ( var i=0, j=this.length; i < j; ++i ) {
        if ( !fn.call(scope, this[i], i, this) ) {
            continue;
        }
        a.push(this[i]);
    }
    return a;
};

Observer = Class.create();
Observer.prototype = {

      initialize: function() {
         // intentionally left blank
      },
      observe: function() {
      }
}

function Observable() {
    this.fns = [];
}

Observable.prototype = {
    addObserver : function(fn) {
        this.fns.push(fn);
    },
    removeObserver : function(fn) {
        this.fns = this.fns.filter(
            function(el) {
                if ( el !== fn ) {
                    return el;
                }
            }
        );
    },
    notify : function(o, thisObj) {
        var scope = thisObj || window;
        this.fns.forEach(
            function(el) {
                //el.call(scope, o);
                el.observe(o);
            }
        );
    }
};

function addEvent( obj, type, fn )
{
      if (obj.addEventListener) {
         obj.addEventListener( type, fn, false );
      } else if (obj.attachEvent) {
         obj["e"+type+fn] = fn;
         obj[type+fn] = function() { obj["e"+type+fn]( window.event ); }
         obj.attachEvent( "on"+type, obj[type+fn] );
      }
}

function removeEvent( obj, type, fn )
{
      if (obj.removeEventListener) {
         obj.removeEventListener( type, fn, false );
      } else if (obj.detachEvent) {
         obj.detachEvent( "on"+type, obj[type+fn] );
         obj[type+fn] = null;
         obj["e"+type+fn] = null;
      }
}

CalUtils = Class.create();
CalUtils.prototype =
{

    initialize : function() { // intentionally left blank
    },

    getDimensions : function(el) {
       var display = el.style.display;
       var ret = new Object();
       var e = el;

       if (e.offsetParent) {
           for ( ret.x = 0, ret.y = 0; e != null; ret.x += e.offsetLeft, ret.y += e.offsetTop, e = e.offsetParent)
             ;
           ret.x = 0;
           ret.y = 0;
           e = el;
           var tmpE = e;
           while((tmpE) && (tmpE.id != "HFS"))
             {
             if(tmpE == e)
               {
               ret.x += e.offsetLeft;
               ret.y += e.offsetTop;
               e = e.offsetParent;
               if((e.style.position == "absolute") || (e.style.position == "relative"))
                 tmpE = null;
               }else{
                 tmpE = tmpE.parentNode;
               }
             }
            e = el;
             ret.x = e.offsetLeft;
             ret.y = e.offsetTop;
       } else {
           ret.x = e.x;
           ret.y = e.y;
       }

       /* Safari bug */
       if (display != 'none' && display != null) {

          ret.w = el.offsetWidth;
          ret.h = el.offsetHeight;
       } else {

          /*
           *  All *Width and *Height properties give 0 on els with display none, so enable the el temporarily
           */
          var els = el.style;
          var orgVisibility = els.visibility;
          var orgPosition = els.position;
          var orgDisplay = els.display;
          els.visibility = 'hidden';
          els.position = 'absolute';
          els.display = 'block';

          ret.w = el.clientWidth;
          ret.h = el.clientHeight;

          els.display = orgDisplay;
          els.position = orgPosition;
          els.visibility = orgVisibility;
       }
       return ret;
    },

  getWindowSize: function (w) {
    var array = [];

    w = w ? w : window;
    array.width = array[0] = w.innerWidth || (w.document.documentElement.clientWidth || w.document.body.clientWidth);
    array.height = array[1] = w.innerHeight || (w.document.documentElement.clientHeight || w.document.body.clientHeight);

    return array;
  }

}

// removes given element from position in DOM tree and appends it to the body
// (good for absolute positioning, ...)
function moveToBody(element) {
    moveTo(element, document.body);
    return element;
}

// removes given element from position in DOM tree and appends it to the parent
// (good for absolute positioning, ...)
function moveTo(element, parent) {
   if (isObject(element) && isObject(parent)) {
      // is object already in DOM tree?
      if (element.parentNode != null) {
         var child = element.parentNode.removeChild(element);
      } else {
         var child = element;
      }
      parent.appendChild(child);
      return child;
   } else {
      return element;
   }
}

function putIFrameBehind(div, iframeid) {
   if (typeof(iframeid) == "undefined") {
       iframeid = div.id+"_iframe";
   }
   if (isObject(div)) {
     var iframe = document.getElementById(iframeid);
     if (iframe == null) {
        iframe = cEl("iframe");
        document.body.appendChild(iframe);
        iframe.id = iframeid;
     }
     iframe.className = div.className;
     iframe.style.position = "absolute";
     calUtils = new CalUtils();

     var divDim = calUtils.getDimensions(div);

     iframe.width = divDim.w + "px";
     iframe.height = (divDim.h - div.style.border)+ "px";
     iframe.style.top = divDim.y + "px";
     iframe.style.left = divDim.x + "px";
     iframe.style.border = "none";
     iframe.style.display = div.style.display;
   }
}

// changes the date (+/- 1) and calls observer
// and updates calendar on change in textfield
function plusMinusDay(calid, shift, value) {
  var calid = "cal"+calid;
  var calcon = calman.calcons[calid];
  var cal    = calcon.calendar;

  if (typeof(value) == "undefined") {
      cal.value.shiftD(shift);
  } else {
      var val = parseUserDateInput(value);
      cal.value = val;
  }
  calcon.observe(cal);

  return false;
}


// Updatelistener for calendar (syncs field and calendar)
function onUpdate (aValue)
{
  // do date fields syncronisations
  var calid = aValue.id.charAt(aValue.id.length-1);
  var calcon = calman.calcons["cal"+calid];
  var savedate = calcon.value;
  var date = parseUserDateInput(aValue.value);
  calcon.setDate(date);

  if (date != savedate) {
      var field = document.getElementsByName(calcon.fieldName)[0];
      field.value = calcon.formatDate(date,calcon.showShortYear,true);
  }


  if (aValue.type == "text") {
    var cal = calcon.calendar;
    cal.value = date;
    cal.jumpToValue();
    cal.draw();
  }

  if (aValue.type == "calendar") {
     var field = document.getElementsByName(calcon.fieldName)[0];
     field.value = calcon.formatDate(date,calcon.showShortYear,true);
     field.focus();
  }
}

// for onclick event:
function createCalendar(currentId) {
   var cal = calman.calcons["cal"+currentId].calendar;

   // no typeof here!
   if (cal.type != "calendar") {
     var calCon = calman.calcons["cal"+currentId];
     var calConf = eval("calendar_config"+currentId);
     cal = calCon.getNewCalendar(calConf);
     cal.value = calCon.value;
   }
   cal.jumpToValue();
   cal.draw();
}

/* de-/activates css class on given element */
function modifyClassName(element, classname, activate) {
  var a = element.className.split(" ");
  var found = false;
  var i = 0;
  for (i = 0; i < a.length ; i++) {
    if (classname == a[i]) {
      found = true;
      break;
    }
  }

  if (!found && activate) {
	a.push(classname);
  } else if (found && !activate) {
    a.splice(i,1);
  }
  classes = a.join(" ");
  element.className = classes;
}

function exchangeClassName(element, token, replace) {
  var a = element.className.split(" ");
  var found = false;
  var i = 0;
  for (i = 0; i < a.length ; i++) {
    if (token == a[i]) {
      found = true;
      break;
    }
  }

  if (found) {
    a.splice(i,1,replace);
  }
  classes = a.join(" ");
  element.className = classes;
}

function toggleClassName(element, token, replace) {
  if (containsClass(element, token)) {
      exchangeClassName(element,token,replace);
  } else {
      exchangeClassName(element,replace,token);
  }
}

function containsClass(element, classname) {
  var a = element.className.split(" ");
  var found = false;

  for (var i = 0; i < a.length ; i++) {
    if (classname == a[i]) {
      found = true;
      break;
    }
  }
  return found;
}

/* calendar control  */

// functions to synchronize and manage datefields
// 2010 MHEI

CalendarControl = Class.create();
CalendarControl.prototype = {
      version : "1.3",
      type : "calendarcontrol",

      value: "",
      today : "",
      periodS: "",
      periodE: "",
      fieldName: "",
      returnFormname: "",
      returnPathDay: "",
      returnPathYear: "",
      returnHiddenField: "",
      viewPeriodS: "",
      viewPeriodE: "",
      dataPeriodS: "",
      dataPeriodE: "",
      monthsTexts: "",
      weekdaysTexts:"",
      def_dateFormat: "%d.%m.%Y",
      calendar: "",

      initialize:function(calconfig) {
//         inherits(new Observer(), this );
         inherits(calconfig, this);
         if (this.weekdaysTexts == "") {
            this.weekdaysTexts = weekdayTexts;
         }
         if (this.monthsTexts == "") {
            this.monthsTexts = monthsTexts;
         }
//         makeObservable(document.getElementsByName(calcon$(tempCurrentJourneyType).fieldName)[0], calcon$(tempCurrentJourneyType));
//         makeObservable(document.getElementsByName(calconfig.fieldName)[0], this);
      },

      getNewCalendar:function(calendar_config) {
          if (calendar_config != "") {
             var c = new Calendar(calendar_config);
          } else {
             var c = new Calendar(this.calendar_config);
          }

          this.returnHiddenField = calendar_config.returnHiddenField;
          c.periodS     = this.periodS,
          c.periodE     = this.periodE,
          c.viewPeriodS = this.viewPeriodS;
          c.viewPeriodE = this.viewPeriodE;
          c.dataPeriodS = this.dataPeriodS;
          c.dataPeriodE = this.dataPeriodE;
          c.monthsTexts = this.monthsTexts;
          c.weekdaysTexts = this.weekdaysTexts;
          c.returnHiddenField = this.fieldName;
          c.value = this.value;
          c.today = this.today;
          c.parent = this;
          c.id = this.id;
          c.dateFormat = (typeof dateFormat == "undefined") ? dateFormat : this.def_dateFormat;

          c.setOneMonthOnly(true);
          inherits(calendar_config, c);
          makeObservable(c, this);
          c.addObserver(this);
          this.calendar = c;
          this.id = c.id;
          return c;
      },

      getStartDateFrom:function(ioField) {
         eval("userInput = "+this.returnWindow+"."+this.returnForm+"."+ioField+".value;");
         this.getStartDateFromString(userInput);
      },

      getStartDateFromString:function(datestring) {
         if (datestring == "today") {
            this.viewPeriodS = new Date();
         } else {
            this.viewPeriodS = parseUserDateInput(datestring);
            if (this.viewPeriodS == undefined) {
               this.viewPeriodS = new Date();
            }
         }
      },

      getEndDateFrom:function (ioField) {
         eval("userInput = "+this.returnWindow+"."+this.returnForm+"."+ioField+".value;");
         this.getEndDateFromString(userInput);
      },


      getEndDateFromString:function(datestring) {
         this.viewPeriodE = parseUserDateInput(datestring);
      },

      formatDate: function(date, showShortYear, showDayNames) {
         // prepare for display
         if (!isDate(date)) {
           date = this.value;
         }
         var format = (this.dateFormat.length >= 0) ? this.dateFormat : this.def_dateFormat;


         var month = date.getMonth()+1;
         format = format.replace("%m",month.toString().leftPad(2,"0"));

         format = (showShortYear) ? format.replace("%Y", "%y") : format.replace("%y", "%Y");

         if (format.indexOf("%Y") > -1)
            format = format.replace("%Y",date.getFullYear().toString());
         else if (format.indexOf("%y") > -1)
            format = format.replace("%y",date.getFullYear().toString().substr(2,2));

         format = format.replace("%d",date.getDate().toString().leftPad(2,"0"));

         if (showDayNames) {
           var dayT = date.getDay()-1;
           dayT = (dayT == -1) ? dayT = 6 : dayT;
            format = this.weekdaysTexts[dayT]+", "+format;
         }

         return format;
      },

      printMonths: function (field, preselection) {
         var input = e(field);
         var dateS = new Date(this.periodS);
         var dateE = new Date(this.periodE);

         while (dateS.getTime() <= dateE.getTime()) {
            if ( (dateS.getMonth() == preselection.getMonth()) && (dateS.getFullYear() == preselection.getFullYear()) ) {
               input.options[input.options.length] = new Option(monthsTextsShort[dateS.getMonth()]+' '+dateS.getFullYear(),dateS.getMonth()+"_"+dateS.getFullYear(),true,true);
               input.options[input.options.length-1].selected = true;
            } else {
               input.options[input.options.length] = new Option(monthsTextsShort[dateS.getMonth()]+' '+dateS.getFullYear(),dateS.getMonth()+"_"+dateS.getFullYear());
            }
            dateS.setDate(1);
            dateS.setMonth(dateS.getMonth()+1);
         }
      },

      setDate:function(date) {
        if (!isDate(date)) {
            date = parseUserDateInput(date);
        }
        this.value = date;
        if (isObject(this.calendar)) {
            this.calendar.value = date;
            this.calendar.jumpToValue();
            this.calendar.draw();
        }
      },

      // synchronizes day with days of month and period.
      syncDays:function (field,value,firstcall) {

          if (isString(value)) {
             value = value.split("_");
             var tempdate = new Date(value[1],value[0],1);
          } else if (isDate(value)) {
             var tempdate = new Date(value);
          } else {
             return;
          }

          var input = e(field);
          var saveit = (firstcall) ? tempdate.getDate() : input.value;

          if((this.periodS) && (this.periodE)) {
             this.periodS = parseUserDateInput(this.periodS);
             this.periodE = parseUserDateInput(this.periodE);

             var monthS = this.periodS.getMonth();
             var yearS = this.periodS.getFullYear();

             var monthE = this.periodE.getMonth();
             var yearE = this.periodE.getFullYear();

             var thismonth = tempdate.getMonth();
             var thisyear = tempdate.getFullYear();
             var dayS = 1;
             var dayE = tempdate.getDaysInMonth();

             var mydate = new Date(tempdate);
             mydate.setDate(1);

             // wenn der aktuelle Tag im Monat des this.periodEnanfangs liegt:
             if ( ( (thismonth == monthS) && (thisyear == yearS) ) && (this.periodS.getDate() > 1 )) {
               dayS = this.periodS.getDate();
             }

             // wenn der aktuelle Tag im Monat des this.periodEnendes liegt:
             if ( ( (thismonth == monthE) && (thisyear == yearE)) && (this.periodE.getDate() < mydate.getDaysInMonth() ) ) {
               dayE = this.periodE.getDate();
               saveit = (this.periodE.getDate() < saveit) ? this.periodS.getDate() : saveit;
             }
          }
          var d = -1;
          // empty options
          var o = input.options;
          var anz = o.length;
          for (var i = 0; i < anz;i++) {
             o[0] = null;
          }

          for (var i = 1; i <= tempdate.getDaysInMonth(); i++) {
            if( (i >= dayS) && (i <= dayE) ) {
              d++;
              o[d] = new Option(i,i);
              if (i == saveit) {
                 o[d].selected = true;
              }
            }
          }
      }
}

function toggleDivCal (calid, topflip, shadow) {
   var distyle = "inline";

   var div = e("calendar"+calid);
   var elem = e("callink"+calid);
   div.style.position="absolute";

   var cu = new CalUtils();
   var dims = cu.getDimensions(elem);

    if (!topflip) {
//      div.style.top = (dims.y-1)+"px";
//      div.style.left = (dims.x-1)+"px";
      div.style.display = (div.style.display != "none") ? "none" : distyle;

      if ( (typeof (shadow) != "undefined") && (shadow) ){
         var div = e("calendarshadow"+calid);
//         div.style.position="absolute";
//         div.style.top = (dims.y+1)+"px";
//         div.style.left = (dims.x+1)+"px";

         div.style.display = (div.style.display != "none") ? "none" : distyle;
      }
    } else {
       div.style.left = (dims.x-1)+"px";
       div.style.display = (div.style.display != "none") ? "none" : distyle;
       div.style.top = (dims.y+13-div.clientHeight)+"px";

       if ( (typeof (shadow) != "undefined") && (shadow) ){
          var div = e("calendarshadow"+calid);
          div.style.position="absolute";
          div.style.left = (dims.x+1)+"px";

          div.style.display = (div.style.display != "none") ? "none" : distyle;
          div.style.top = (dims.y+15-div.clientHeight)+"px";
       }
    }

   putIFrameBehind(div, "calendar_iframe"+calid);
   return false;
}

function toggleDivCal2 (calid, topflip, shadow) {
   var distyle = "inline";

   var div = e("calendar"+calid);
   var iframe = e("calendar_iframe"+calid);
   var elem = e("callink"+calid);
   div.style.position="absolute";

   var cu = new CalUtils();
   var dims = cu.getDimensions(elem);

    if (!topflip) {
      div.style.top = (dims.y-1)+"px";
      div.style.left = (dims.x-1)+"px";
      div.style.display = (div.style.display != "none") ? "none" : distyle;

      if ( (typeof (shadow) != "undefined") && (shadow) ){
         var div = e("calendarshadow"+calid);
         div.style.position="absolute";
         div.style.top = (dims.y+1)+"px";
         div.style.left = (dims.x+1)+"px";

         div.style.display = (div.style.display != "none") ? "none" : distyle;
      }
    } else {
       div.style.top = (dims.y+13-div.clientHeight)+"px";
       div.style.left = (dims.x-1)+"px";
       div.style.display = (div.style.display != "none") ? "none" : distyle;

       if ( (typeof (shadow) != "undefined") && (shadow) ){
          var div = e("calendarshadow"+calid);
          div.style.position="absolute";
          div.style.left = (dims.x+1)+"px";

          div.style.display = (div.style.display != "none") ? "none" : distyle;
          div.style.top = (dims.y+15-div.clientHeight)+"px";
       }
    }
   putIFrameBehind(div, "calendar_iframe"+calid);
   return false;
}

function putIFrameBehind(div, iframeid) {
   if (typeof(iframeid) == "undefined") {
       iframeid = div.id+"_iframe";
   }
   if (isObject(div)) {
     var iframe = document.getElementById(iframeid);
     if (iframe == null) {
        iframe = document.createElement("iframe");
        iframe.id = iframeid;
        div.parentNode.appendChild(iframe);
     }
     iframe.className = div.className;
     iframe.style.position = "absolute";

     var calUtils = new CalUtils();

     var divDim = calUtils.getDimensions(div);
     iframe.width = divDim.w + "px";
     iframe.height = (divDim.h - div.style.border)+ "px";
     iframe.style.top = divDim.y + "px";
     iframe.style.left = divDim.x + "px";
     iframe.style.border = "none";
     iframe.style.display = div.style.display;
   }
}

CalendarManager = Class.create();
CalendarManager.prototype = {
    calcons :new Array(),

    initialize : function() {
    },

    getNewCalCon : function(calcon_config) {
     //this.calcons.push(new CalendarControl(calcon_config));
     //return this.calcons[this.calcons.length-1];
     var calcon = new CalendarControl(calcon_config);
     //inherits(this.config, calcon);
     calcon.myparent = this;
     this.calcons[calcon.id] = calcon;

     calcon.observe = onUpdate;
     var field = document.getElementsByName(calcon.fieldName)[0];
     if (field != null) {
       makeObservable(document.getElementsByName(calcon.fieldName)[0], calcon);
     }

     return calcon;
   },

    createCalendar : function(calcon_config, calendar_config) {
      var calcon = calman.getNewCalCon(calcon_config);
      calcon.getNewCalendar(calendar_config);
      return calcon.calendar;
    }
}

// initialize global CalendarManager
calman = new CalendarManager();


/* calendar main */

Calendar = Class.create();
Calendar.prototype = {
   version: "1.2",
   type : "calendar",

   value: "",

   initialize: function(config) {
      //  fields
      //  ~~~
      this.name = config.id;
      this.id = config.id;
      this.divid = config.divid;

      this.readOnly = true;
      this.showWeekNumbers = true;
      this.weekStartsOn = 0; // -1 = Sunday, 0 = Monday
      this.internalScrollers = false;

      this.selectedDay = null;
      this.preSelectedDay = null;
      this.multipleSelect = false;

      this.fullMonthsOnly = true;
      this.scrollable = true;

      this.monthsTexts = "";
      this.weekdaysTexts = "";
      this.prevMonthHTML = "&laquo";
      this.nextMonthHTML = "&raquo";
      this.weekNoHTML    = "&nbsp;";

      this.howManyDays = 0;
   },

   prevMonth:function () {
      this.viewPeriodS.setDate(1);
      this.viewPeriodS.shiftM(-1);
      this.viewPeriodE.setDate(1);
      this.viewPeriodE.shiftD(-1);
      this.selectedDay = null;
      this.draw();
   },

   nextMonth:function () {
      this.viewPeriodS.setDate(1);
      this.viewPeriodS.shiftM(1);
      this.viewPeriodE.setDate(1);
      this.viewPeriodE.shiftM(2);
      this.viewPeriodE.setDate(1);
      this.viewPeriodE.shiftD(-1);
      this.selectedDay = null;
      this.draw();
   },

   setFullMonthsOnly:function(truefalse) {
      this.fullMonthsOnly = truefalse;
      if (this.fullMonthsOnly) {
         // loose time
         this.viewPeriodE = new Date(this.viewPeriodE.getFullYear(),this.viewPeriodE.getMonth()+1,1);
         this.viewPeriodE.setDate(1);
         this.viewPeriodE.shiftD(-1);
      }
   },

   setOneMonthOnly:function(truefalse) {
      // Set viewport to one full month
      this.oneMonthOnly = truefalse;
      if (this.oneMonthOnly) {
         this.viewPeriodS = new Date(this.viewPeriodS.getFullYear(),this.viewPeriodS.getMonth(), 1);
         this.viewPeriodE = new Date(this.viewPeriodS.getFullYear(),this.viewPeriodS.getMonth()+1,1);
         this.viewPeriodE.shiftD(-1);
      }
   },

   getHowManyDays:function() {
      //Set 1 day in milliseconds
      var one_day=86400000;
      var days = Math.ceil((this.viewPeriodE.getTime()-this.viewPeriodS.getTime())/(one_day))+1;
      return days;
   },

   setPreselectedDate:function(date) {
      // read date from given inputfields
      date = parseUserDateInput(date);
      if (date.getTime() < this.dataPeriodS.getTime()) {
         date = this.dataPeriodS;
      } else if (date.getTime() > this.dataPeriodE.getTime()) {
         date = this.dataPeriodE;
      }
      this.preSelectedDay = date;
      this.selectedDay = this.getCellName(date);
      this.viewPeriodS = new Date(date);
      this.viewPeriodS.setDate(1);

      this.viewPeriodE = new Date(date);
      this.viewPeriodE.setDate(1);
      this.viewPeriodE.shiftM(1);
      this.viewPeriodE.setDate(1);
      this.viewPeriodE.shiftD(-1);
   },

   /* scrolls calendar to its current date */
   jumpToValue:function() {
      this.setPreselectedDate(this.value);
   },

   hideCallBack:function() {
       if (typeof this.hideCallBackName != "undefined") {
         // da es nur 2 Kalender geben kann - letztes Zeichen des Namens (0 oder 1) nehmen
         var temp = this.name.charAt(this.name.length-1);
         temp = this.hideCallBackName+"('"+temp+"');";
         eval(temp);
       }
   },

   getCellName:function (date) {
       return this.name+"_df_"+date.getFullYear()+"-"+(date.getMonth())+"-"+date.getDate();
   },

// This function toggles the given date's style
   setDate:function (cell) {
    if (!this.readOnly) {
      var tempday = e(cell);

      toggleClassName(tempday,"active", "enabled");

      if (!this.multipleSelect) {
         // single selection : remove old selection - select ONE day only
         if (this.selectedDay != null) {
            var sDay = e(this.selectedDay);
            var c = (typeof this.bitfield != "undefined") ? "working" : "enabled";
            modifyClassName(sDay, "active", false);
            modifyClassName(sDay, c, true);
         }

         this.selectedDay = cell;
         var date = cell.substring((this.name.length)+4,cell.length);
         var temp = date.split("-");
         var year = 1*temp[0];
         var month = 1*temp[1];
         var day = 1*temp[2];

         this.value = new Date(year,month,day);

         this.notify(this);
         this.hideCallBack();
      }
    }
   },

   inPeriod:function(date, S, E) {
       return ((date.getTime() >= S.getTime()) && (date.getTime() <= E.getTime()));
   },

   drawHeader:function(current_row, tempdate) {
       if (this.scrollable) {
          if (this.internalScrollers) {
             var cell = document.createElement("TH");
             cell.id = this.name+"_heading_months_lt";
          } else {
             var cell = document.getElementById(this.name+"prev");
          }
          var td2 = new Date(tempdate);
          td2.setDate(1);
          td2.shiftD(-1);
          if (this.inPeriod(td2, this.dataPeriodS, this.dataPeriodE)) {
             cell.innerHTML=this.prevMonthHTML;
             cell.className = "prevMonth";
             if (this.internalScrollers) {
                cell.onclick = function() {
                   var calid = this.id.substring(0,this.id.indexOf("_heading_months_lt"));
                   calman.calcons[calid].calendar.prevMonth();
                }
             } else {
                cell.onclick = function() {
                   var calid = this.id.substring(0,this.id.indexOf("prev"));
                   calman.calcons[calid].calendar.prevMonth();
                }
             }
          } else {
             cell.innerHTML= "";
             cell.className = "disabled";
          }
          if (this.internalScrollers) {
             current_row.appendChild(cell);
          }
       }
       cell= document.createElement("TH");
       cell.colSpan = ( (this.scrollable) && (this.internalScrollers) ) ? 5 : 7;
       if (this.showWeekNumbers) { cell.colSpan++; }

       cell.innerHTML = this.monthsTexts[tempdate.getMonth()]+"&nbsp;"+tempdate.getFullYear();
       cell.textAlign = "center";
       cell.id = this.name+"_heading_months"+tempdate.getMonth();
       if (this.multipleSelect) {
          cell.className = "heading_months_enabled";
          cell.onclick = function() {
             var calid = this.id.substring(0,this.id.indexOf("_heading_months"));
             var month = this.id.substring(this.id.indexOf("_heading_months")+15,this.id.length);
             calman.calcons[calid].calendar.selectMonth(month);
          }
       } else {
          cell.className = "heading_months_disabled";
       }
       current_row.appendChild(cell);
       if (this.scrollable) {
          if (this.internalScrollers) {
             cell = document.createElement("TH");
             cell.id = this.name+"_heading_months_gt";
          } else {
             cell = document.getElementById(this.name+"next");
          }
          var td2 = new Date(tempdate);
          td2.setDate(1);
          td2.shiftM(1);
          if (this.inPeriod(td2, this.dataPeriodS, this.dataPeriodE)) {
             cell.innerHTML=this.nextMonthHTML;
             cell.className = "nextMonth";
             if (this.internalScrollers) {
                cell.onclick = function() {
                   var calid = this.id.substring(0,this.id.indexOf("_heading_months_gt"));
                   calman.calcons[calid].calendar.nextMonth();
                }
             } else {
                cell.onclick = function() {
                   var calid = this.id.substring(0,this.id.indexOf("next"));
                   calman.calcons[calid].calendar.nextMonth();
                }
             }
           } else {
             cell.innerHTML="";
             cell.className = "disabled";
          }
          if (this.internalScrollers) {
             current_row.appendChild(cell);
          }
       }
   },

// This function fills the calendar table with the days of
// the selected month and year.
   draw:function() {
      this.howManyDays = this.getHowManyDays();

      if (this.bitfield) {
         // calc difference in days between viewPeriodS and dataPeriodS
         // to find bitfield begin...
         var tempdate = new Date(this.dataPeriodS);
         var bitfieldindex = 0;
         if (tempdate.getTime() < this.viewPeriodS.getTime()) {
            while (tempdate.getTime() < this.viewPeriodS.getTime()) {
               bitfieldindex++;
               tempdate.shiftD(1);
            }
         } else {
            while (tempdate.getTime() > this.viewPeriodS.getTime()) {
               bitfieldindex--;
               tempdate.shiftD(-1);
            }
         }
      }
      var div = document.getElementById(this.divid);

      // create table if it does not already exist
      var table = document.getElementById(this.name+"_table");
      if (table == null) {
         table = document.createElement("TABLE");
         div.insertBefore(table,div.firstChild);
         table.setAttribute("cellSpacing", "0");
         // FIX for non IEs
         if (typeof isIE == "undefined") {
            table.style.width = "auto";
         }
         table.style.width= "100%";
         table.id = this.name+"_table";
         table.className = "calendar";
      }

      // Recycling: remove complete table body...it's recreated => fast delete?
      var tbody = document.getElementById(this.name+"_tbody");
      if (tbody != null) {
         tbody.parentNode.removeChild(tbody);
      }

      // (re-)create tbody
      tbody = document.createElement("TBODY");
      table.appendChild(tbody);
      tbody.id = this.name+"_tbody";
      // update header and status texts
      tempdate = new Date(this.viewPeriodS);

      // show month name above weekdays
      if (this.oneMonthOnly) {
         // create row for month names
         current_row = document.createElement("TR");

         this.drawHeader(current_row, tempdate);

         tbody.appendChild(current_row);
      }

      // write weekday names in first (or second) row
      var row = document.createElement("TR");

      // week numbers
      if (this.showWeekNumbers) {
         var cell = document.createElement("TH");
         cell.id = this.name+"_heading_"+d;
         cell.className = "heading_daynames";
         cell.innerHTML = this.weekNoHTML;
         row.appendChild(cell);
      }

      for (d = 0 ; d < 7 ; d++) {
         var cell = document.createElement("TH");
         cell.id = this.name+"_heading_"+d;
         if (d+this.weekStartsOn < 0) {
            cell.innerHTML = this.weekdaysTexts[6];
         } else {
            cell.innerHTML = this.weekdaysTexts[d+this.weekStartsOn];
         }
         if (this.multipleSelect) {
            cell.className = "enabled";
            cell.onclick = function() {
               var calid = this.id.substring(0,this.id.indexOf("_heading_"));
               var day = this.id.substring(this.id.indexOf("_heading_")+9,this.id.length);
               calman.calcons[calid].calendar.selectDays(day);
            }
         } else {
            cell.className = "heading_daynames";
         }
         row.appendChild(cell);
      }

      tbody.appendChild(row);

      var daysdrawn = 0;
      var newmonth = false;
      var oldtempcolspan = 0;
      var colspan = 1;

      var w = -1;
      var newWeekNeeded = true;
      while (newWeekNeeded) {
         w++;

         if (((tempdate.getDate() == 1 && newmonth) || (daysdrawn == 0)) && (tempdate.getTime() >= this.viewPeriodS.getTime())) {
            // a new month begins...

            if (this.weekStartsOn == 0) {
               // if tempdate is 0 (sunday) set to 6 as the week begins on monday
               var daystoskip = (tempdate.getDay()-1 < 0) ? 6 : tempdate.getDay()-1;
            } else {
               var daystoskip = tempdate.getDay();
            }

            if (this.oneMonthOnly == false) {
               // create row for month name
               current_row = document.createElement("TR");
               current_cell= document.createElement("TD");
               current_cell.colSpan = 7;
               current_cell.innerHTML = this.monthsTexts[tempdate.getMonth()]+" - "+tempdate.getFullYear();
               current_cell.id = this.name+"_heading_months"+tempdate.getMonth();
               if (this.multipleSelect) {
                  current_cell.className = "enabled";
                  current_cell.onclick = function() {
                     var calid = this.id.substring(0,this.id.indexOf("_heading_months"));
                     var month = this.id.substring(this.id.indexOf("_heading_months")+15,this.id.length);
                     calman.calcons[calid].calendar.selectMonth(month);
                  }
               } else {
                  current_cell.className = "disabled";
               }
               current_row.appendChild(current_cell);

               tbody.appendChild(current_row);
            }

         }

         current_row = document.getElementById(this.name+"_row_"+w);
         if (current_row == null) {
            current_row = document.createElement("TR");
            current_row.id = this.name+"_row_"+w;
            tbody.appendChild(current_row);
         }

         if (this.showWeekNumbers) {
            cell = document.createElement("TD");
            cell.className = "weekno";
            cell.innerHTML = getWeek(tempdate);
            current_row.appendChild(cell);
         }

         // draws week rows
         for (var d = 0; d < 7; d++) {

            if ((tempdate.getDate() == 1 && newmonth == false) && (daysdrawn != 0)) {
               newmonth = true;
               // append remaining empty cells
               for (var e = d; e < 7; e++) {
                  cell = document.createElement("TD");
                  cell.className = "disabled";
                  cell.innerHTML = "&nbsp;";
                  if (current_row != null) {
                     current_row.appendChild(cell);
                  }
               }
               break;
            }

            cell = document.createElement("TD");

            if (daystoskip <= 0 && daysdrawn < this.howManyDays) {
               newmonth = false;
               // Change table cells id to represent current displayed date
               cell.id = this.getCellName(tempdate);
               this.getDateClass(cell, tempdate);

               if ((bitfieldindex+daysdrawn >= 0) && (this.inPeriod(tempdate, this.dataPeriodS, this.dataPeriodE))) {
                  if (this.bitfield.charAt(bitfieldindex+daysdrawn) == "1") {
                     modifyClassName(cell, "working", true);
                     cell.onclick = function(){
                        var calid = this.id.substring(0,this.id.indexOf("_df"));
                        calman.calcons[calid].calendar.setDate(this.id);
                     };
                  } else {
                     modifyClassName(cell, "notworking", true);
                     cell.onclick = null;
                  }

               } else {
                  if (this.readOnly == true) {
                     cell.className = "disabled"
                  } else {
                     modifyClassName(cell, "enabled", true);
                  }
                  cell.onclick = function(){
                     var calid = this.id.substring(0,this.id.indexOf("_df"));
                     calman.calcons[calid].calendar.setDate(this.id);
                  };
               }

               cell.innerHTML = tempdate.getDate();
               // increase already drawn days by one
               tempdate.shiftD(1);
               daysdrawn += 1;

               //  deactivate cell
            } else {
               // append empty cells before calendar start
               cell.className = "disabled";
               cell.innerHTML = "&nbsp;";
               cell.onclick = null;
               daystoskip -= 1;
            }

            // ensure correct period display
            tempdate.shiftD(-1);
            if (tempdate.getTime() < this.dataPeriodS.getTime()) {
               cell.className = "disabled before_period";
               cell.onclick = null;
            }
            if (tempdate.getTime() > this.dataPeriodE.getTime()) {
               cell.className = "disabled beyond_period";
               cell.onclick = null;
            }
            tempdate.shiftD(1);

            current_row.appendChild(cell);
         }
         if ((tempdate.getTime() > this.viewPeriodE.getTime()) || (daysdrawn >= this.howManyDays)) {
            newWeekNeeded = false;
         }

      }

      // always keep 5 rows...no optic changes
      if (w < 5) {
         var row = document.createElement("TR");
         for (var i=0; i <= 7; i++) {
            var cell = document.createElement("TD");
            cell.className = "disabled";
            cell.innerHTML = "&nbsp;";
            row.appendChild(cell);
         }
         tbody.appendChild(row);
      }

      // auto-correction of div size
      document.getElementById(this.divid).style.width = "auto";
      if (this.preSelectedDay != undefined) {
         tempday = document.getElementById(this.getCellName(this.preSelectedDay));
         if (tempday != undefined) {
            if ((!this.dataPeriodS) && (!this.dataPeriodE)) {
               modifyClassName(tempday, "active", true);
            } else {
               // should be preselect!!
               modifyClassName(tempday, "active", true);
            }
         }
      }

   }, // - END function Calendar_draw();

   getFromVKHEXBitfield:function(bitfield) {
      this.dataPeriodS = new Date("20"+bitfield.substr(4,2),bitfield.substr(2,2),bitfield.substr(0,2));
      this.dataPeriodS.shiftM(-1);

      this.dataPeriodE = new Date("20"+bitfield.substr(10,2),bitfield.substr(8,2),bitfield.substr(6,2));
      this.dataPeriodE.shiftM(-1);

      bitfield = bitfield.substring(12,bitfield.length);

      // convert hexadecimal to binary
      this.bitfield = "";
      var temp = "";

      for (var i=0; i < bitfield.length;i += 2) {
         temp = (parseInt(bitfield.substring(i,i+2),16)).toString(2);
         while (temp.length < 8) {
            temp = "0"+temp;
         }
         this.bitfield = this.bitfield + temp;
      }
      return this.bitfield;
   },

   getDateClass:function(cell, date, classname) {
     // mark the active day
     if (this.value.getTime() == date.getTime()) {
       modifyClassName(cell, "active", true);
     }
     // mark today
     if (this.today.getTime() == date.getTime()) {
       modifyClassName(cell, "today", true);
     }
   }

} // EOO - end of object


