var ProtoCalendar = Class.create();
ProtoCalendar.Version = "1.1.8.1";
Object.extend(ProtoCalendar, {
	JAN: 0,
	DEC: 11,
	SUNDAY: 0,
	MONDAY: 1,
	SATURDAY: 6,
	LABEL_FORMAT: 'yyyy/mm/dd'
});

ProtoCalendar.LangFile = new Object();
ProtoCalendar.LangFile['en'] = {
  weekFirstDay: ProtoCalendar.MONDAY,
  weekLastDay: ProtoCalendar.SUNDAY,
  WEEK_DAYS_LIST: [ 1, 2, 3, 4, 5, 6, 0 ],
  WEEK_DAYS_INDEX: [ 6, 0, 1, 2, 3, 4, 5 ],
  NO_DATE_ERROR: 'No day has been selected.',
  DEFAULT_FORMAT: 'dd/mmm/yyyy',
  MONTH_ABBRS: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
  YEAR_LABEL: ' ',
  MONTH_LABEL: ' ',
  WEEKDAY_ABBRS: ['Mon','Tue','Wed','Thr','Fri','Sat','Sun'],
  YEAR_AND_MONTH: false
},
ProtoCalendar.LangFile['mm'] = {
  weekFirstDay: ProtoCalendar.MONDAY,
  weekLastDay: ProtoCalendar.SUNDAY,
  WEEK_DAYS_LIST: [ 1, 2, 3, 4, 5, 6, 0 ],
  WEEK_DAYS_INDEX: [ 6, 0, 1, 2, 3, 4, 5 ],
  NO_DATE_ERROR: 'No day has been selected.',
  DEFAULT_FORMAT: 'dd/mm/yyyy',
  MONTH_ABBRS: ['01','02','03','04','05','06','07','08','09','10','11','12'],
  YEAR_LABEL: ' ',
  MONTH_LABEL: ' ',
  WEEKDAY_ABBRS: ['Mon','Tue','Wed','Thr','Fri','Sat','Sun'],
  YEAR_AND_MONTH: false
},
ProtoCalendar.LangFile['ch'] = {
  weekFirstDay: ProtoCalendar.SUNDAY,
  weekLastDay: ProtoCalendar.SATURDAY,
  WEEK_DAYS_LIST: [ 0, 1, 2, 3, 4, 5, 6 ],
  WEEK_DAYS_INDEX: [ 0, 1, 2, 3, 4, 5, 6 ],
  NO_DATE_ERROR: '请选择日期。',
  DEFAULT_FORMAT: 'yyyy/mm/dd',
  MONTH_ABBRS: ['1','2','3','4','5','6','7','8','9','10','11','12'],
  YEAR_LABEL: ' ',
  MONTH_LABEL: ' ',
  WEEKDAY_ABBRS: ['日','一','二','三','四','五','六'],
  YEAR_AND_MONTH: true
},
ProtoCalendar.LangFile['ja'] = {
  weekFirstDay: ProtoCalendar.SUNDAY,
  weekLastDay: ProtoCalendar.SATURDAY,
  WEEK_DAYS_LIST: [ 0, 1, 2, 3, 4, 5, 6 ],
  WEEK_DAYS_INDEX: [ 0, 1, 2, 3, 4, 5, 6 ],
  NO_DATE_ERROR: 'No day has been selected.',
  DEFAULT_FORMAT: 'yyyy/mm/dd',
  MONTH_ABBRS: ['01','02','03','04','05','06','07','08','09','10','11','12'],
  YEAR_LABEL: ' ',
  MONTH_LABEL: ' ',
  WEEKDAY_ABBRS: ['日','月','火','水','木','金','土'],
  YEAR_AND_MONTH: true
};

ProtoCalendar.LangFile.defaultLang = 'en';
ProtoCalendar.LangFile.defaultLangFile = function() { return ProtoCalendar.LangFile[defaultLang]; };

ProtoCalendar.newDate = function() {
  var d = new Date();
  d.setDate(1);
  return d;
}

//Only check vertically
ProtoCalendar.withinViewport = function(element) {
  var dimensions = ProtoCalendar.callWithVisibility(element, function() { return element.getDimensions(); });
  var width = dimensions.width;
  var height = dimensions.height;
  var offsets = ProtoCalendar.callWithVisibility(element, function() { return element.viewportOffset(); });
  var offsetY = offsets.top;
  return (offsetY >=0) && (offsetY + height <= document.viewport.getHeight());
}

ProtoCalendar.callWithVisibility = function(element, func) {
  element = $(element);
  var display = $(element).getStyle('display');
  if (display != 'none' && display != null) {// Safari bug 
    return func();
  }
  var els = element.style;
  var originalVisibility = els.visibility;
  var originalPosition = els.position;
  var originalDisplay = els.display;
  els.visibility = 'hidden';
  els.position = 'absolute';
  els.display = 'block';
  var result = func();
  els.display = originalDisplay;
  els.position = originalPosition;
  els.visibility = originalVisibility;
  return result;
}

Object.extend(ProtoCalendar, {
	getNumDayOfMonth: function(year, month){
	return 32 - new Date(year, month, 32).getDate();
	},

	getDayOfWeek: function(year, month, day) {
	return new Date(year, month, day).getDay();
	}
});

ProtoCalendar.prototype = {
  initialize: function(options) {
    var date = ProtoCalendar.newDate();
    this.options = Object.extend({
		month: date.getMonth(),
		year: date.getFullYear(),
		lang: ProtoCalendar.LangFile.defaultLang
	}, options || { });
    this.date = new Date(this.options.year, this.options.month, 1);
  },

  getMonth: function() {
    return this.date.getMonth();
  },

  getYear: function() {
    return this.date.getFullYear();
  },

  setMonth: function(month) {
    return this.date.setMonth(month);
  },

  setYear: function(year) {
    return this.date.setFullYear(year);
  },

  getDate: function() {
    return this.date;
  },

  setDate: function(date) {
    this.date = date;
  },

  setYearByOffset: function(offset) {
    this.date.setFullYear(this.date.getFullYear() + offset);
  },

  setMonthByOffset: function(offset) {
    this.date.setMonth(this.date.getMonth() + offset);
  },

  getNumDayOfMonth: function() {
    return ProtoCalendar.getNumDayOfMonth(this.getYear(), this.getMonth());
  },

  getDayOfWeek: function(day) {
    return ProtoCalendar.getDayOfWeek(this.getYear(), this.getMonth(), day);
  },

  clone: function() {
    return new ProtoCalendar({year: this.getYear(), month: this.getMonth()});
  }
};

var AbstractProtoCalendarRender = Class.create();
Object.extend(AbstractProtoCalendarRender, {
	id: 1,
	getId: function() {
		var id = AbstractProtoCalendarRender.id;
		AbstractProtoCalendarRender.id += 1;
		return id;
	}
});

AbstractProtoCalendarRender.prototype = {
  initialize: function(options) {
    this.id = AbstractProtoCalendarRender.getId();
    this.options = Object.extend({
		containerClass: 'cal-container',
		tableClass: 'cal-table',
		headerTopClass: 'cal-header-top',
		headerClass: 'cal-header',
		headerBottomClass: 'cal-header-bottom',
		bodyTopClass: 'cal-body-top',
		bodyClass: 'cal-body',
		bodyBottomClass: 'cal-body-bottom',
		bodyId: this.getIdPrefix() + '-body',
		footerTopClass: 'cal-footer-top',
		footerClass: 'cal-footer',
		footerBottomClass: 'cal-footer-bottom',
		footerId: this.getIdPrefix() + '-footer',
		yearSelectClass: 'cal-select-year',
		yearSelectId: this.getIdPrefix() + '-select-year',
		monthSelectClass: 'cal-select-month',
		monthSelectClassEn: 'cal-select-month-en',
		monthSelectId: this.getIdPrefix() + '-select-month',
		borderClass: 'cal-border',
		okButtonClass: 'cal-ok-button',
		okButtonId: this.getIdPrefix() + '-ok-button',
		errorDivClass: 'cal-error-list',
		errorDivId: this.getIdPrefix() + '-error-list',
		labelRowClass: 'cal-label-row',
		labelCellClass: 'cal-label-cell',
		nextButtonClass: 'cal-next-btn',
		prevButtonClass: 'cal-prev-btn',
		dayCellClass: 'cal-day-cell',
		dayClass: 'cal-day',
		weekdayClass: 'cal-weekday',
		sundayClass: 'cal-sunday',
		saturdayClass: 'cal-saturday',
		holidayClass: 'cal-holiday',
		otherdayClass: 'cal-otherday',
		disabledDayClass: 'cal-disabled',
		selectedDayClass: 'cal-selected',
		nextBtnId: this.getIdPrefix() + '-next-btn',
		prevBtnId: this.getIdPrefix() + '-prev-btn',
		lang: ProtoCalendar.LangFile.defaultLang,
		showEffect: 'Appear',
		hideEffect: 'Fade',
		ifInvisible: 'Flip', /* None | Scroll | Flip */
		scrollMargin: 20
	}, options || {});
    this.langFile = ProtoCalendar.LangFile[this.options.lang];
    this.container = this.createContainer();
    this.alignTo = $(this.options.alignTo);
    this.alignOrient = 'Below';
    if (navigator.appVersion.match(/\bMSIE\b/)) {
      this.iframe = this.createIframe();
    }
    this.resizeHandler = this.setPosition.bind(this);
  },

  createContainer: function() {
    var container = $(document.createElement('div'));
    container.addClassName(this.options.containerClass);
    container.setStyle({position:'absolute',
		top: "0px",
		left: "0px",
		zindex:1,
		display: 'none'});
    container.hide();
    document.body.appendChild(container);
    return container;
  },

  createIframe: function() {
    var iframe = document.createElement("iframe");
    iframe.setAttribute("src", "javascript:false;");
    iframe.setAttribute("frameBorder", "0");
    iframe.setAttribute("scrolling", "no");
    Element.setStyle(iframe, { position:'absolute',
		top: "0px",
		left: "0px",
		zindex:10,
		display: 'none',
		overflow: 'hidden',
		filter: 'progid:DXImageTransform.Microsoft.Alpha(opacity=0)'
		});
    document.body.appendChild(iframe);
    return $(iframe);
  },

  getWeekdayLabel: function(weekday) {
    return this.langFile.WEEKDAY_ABBRS[weekday];
  },

  getCalendarBeginDay: function(calendar) {
    var offset = this.getDayIndexOfWeek(calendar, 1);
    var date = new Date(calendar.getYear(), calendar.getMonth(), 1 - offset);
    return date;
  },

  getCalendarEndDay: function(calendar) {
    var lastDayOfMonth = calendar.getNumDayOfMonth();
    var offset = 6 - this.getDayIndexOfWeek(calendar, lastDayOfMonth);
    var date = new Date(calendar.getYear(), calendar.getMonth(), lastDayOfMonth + offset + 1);
    return date;
  },

  getDayIndexOfWeek: function(calendar, day) {
    return this.langFile.WEEK_DAYS_INDEX[ calendar.getDayOfWeek(day) ];
  },

  getIdPrefix: function() {
    return 'cal' + this.id;
  },

  getDayDivId: function(date) {
    return this.getIdPrefix() + '-year' + date.getFullYear() + '-month' + date.getMonth() + '-day' + date.getDate();
  },

  setPosition: function() {
    if (!this.alignTo) return true;
    this.setAlignment(this.alignTo, this.container, this.alignOrient);
    var withinView = ProtoCalendar.withinViewport(this.container);
    if (!withinView && this.options.ifInvisible == 'Flip') {
      this.alignOrient = (this.alignOrient == 'Above' ? 'Below' : 'Above');
      this.setAlignment(this.alignTo, this.container, this.alignOrient);
    }
    if (this.iframe) {
      var dimensions = Element.getDimensions(this.container);
      this.iframe.setAttribute("width", dimensions.width);
      this.iframe.setAttribute("height", dimensions.height);
      this.setAlignment(this.alignTo, this.iframe, this.alignOrient);
    }
    if (this.options.ifInvisible == 'Scroll') this.scrollIfInvisible();
    return true;
  },

  setAlignment: function(alignTo, element, pos) {
    var offsets = Position.cumulativeOffset(alignTo);
    element.setStyle({left: offsets[0] + "px"});
    element.setStyle({top: (offsets[1] + alignTo.offsetHeight) + "px"});
  },

  show: function(option) {
    Event.observe(window, 'resize', this.resizeHandler);
    this.setPosition();
    if (typeof Effect != 'undefined') {
      var effect =  this.options['showEffect'] || 'Appear';
      if (!this._effect || this._effect.state == 'finished') {
        this._effect = new Effect[effect](this.container, {duration: 0.5});
      }
    } else {
      this.container.show();
    }
    if (this.iframe) this.iframe.show();
  },

  scrollIfInvisible: function() {
    var container = this.container;
    var dimensions = ProtoCalendar.callWithVisibility(container, function() { return container.getDimensions(); });
    var width = dimensions.width;
    var height = dimensions.height;
    var offsets = ProtoCalendar.callWithVisibility(container, function() { return container.viewportOffset(); });
    var offsetY = offsets.top;
    var diff = offsetY + height - document.viewport.getHeight();
    if (diff > 0) {
      window.scrollBy(0, diff + this.options.scrollMargin);
    }
  },    

  hide: function(option) {
    Event.stopObserving(window, 'resize', this.resizeHandler);
    if (!this.container.visible()) {
      return ;
    }
    if (typeof Effect != 'undefined') {
      var effect =  this.options['hideEffect'] || 'Fade';
      if (!this._effect || this._effect.state == 'finished') {
        this._effect = new Effect[effect](this.container, {duration: 0.3});
      }
    } else {
      this.container.hide();
    }
    if (this.iframe) this.iframe.hide();
  },

  hideImmediately: function(option) {
    if (!this.container.visible()) {
      return ;
    }
    this.container.hide();
    if (this.iframe) this.iframe.hide();
  },

  toggle: function(element) {
    this.container.visible() ? this.hide() : this.show();
  },

  render: function(calendar) { },

  rerender: function(calendar) { },

  getContainer: function() {
    return this.container;
  },

  getPrevButton: function() {
    return $(this.options.prevBtnId);
  },

  getNextButton: function() {
    return $(this.options.nextBtnId);
  },

  getYearSelect: function() {
    return $(this.options.yearSelectId);
  },

  getMonthSelect: function() {
    return $(this.options.monthSelectId);
  },

  getOkButton: function() {
    return $(this.options.okButtonId);
  },

  getBody: function() {
    return $(this.options.bodyId);
  },

  getDayDivs: function() {
    //Good Performance for IE
    var divEls = [];
    var dayDivs = this.dayDivs;
    for (var i = 0; i < dayDivs.length; i++) {
      divEls.push(document.getElementById(dayDivs[i]));
    }
    return divEls;
    //return this.container.getElementsBySelector("a." + this.options.dayClass);
  },

  getDateFromEl: function(el) {
    var element = $(el);
    return new Date(element.readAttribute('year'), element.readAttribute('month'), element.readAttribute('day'));
  },

  selectDate: function(date) {
    var dayEl = $(this.getDayDivId(date));
    if (dayEl) dayEl.addClassName(this.options.selectedDayClass);
  },

  selectTime: function(date) {
  },

  deselectDate: function(date) {
    if (date) {
      var dateEl = $(this.getDayDivId(date));
      if (dateEl) dateEl.removeClassName(this.options.selectedDayClass);
    }
  },

  evaluateWithOptions: function(html) {
    var template = new Template(html);
    return template.evaluate(this.options);
  },

  defaultOnError: function(msg) {
    this.ensureErrorDiv();
    this.errorDiv.show();
    this.errorDiv.innerHTML += '<li>' + this.langFile[msg] + '</li>';
  },

  hideError: function() {
    this.ensureErrorDiv();
    this.errorDiv.innerHTML = '';
    this.errorDiv.hide();
  },

  ensureErrorDiv: function() {
    if (!this.errorDiv) {
      var errorDivHtml = '<div class="#{errorDivClass}" id="#{errorDivId}"><ul></ul></div>';
      new Insertion.Before($(this.options.footerId), this.evaluateWithOptions(errorDivHtml));
      this.errorDiv = $(this.options.errorDivId);
    }
    },

  isSelectable: function(date) {
      return (this.options.minDate - date) <= 0 && (date - this.options.maxDate) <= 0;
    }
  
};

var ProtoCalendarRender = Class.create();
Object.extend(ProtoCalendarRender.prototype, AbstractProtoCalendarRender.prototype);

Object.extend(
  ProtoCalendarRender.prototype,
  {
    render: function(calendar) {
      var html = '';
      html += this.renderHeader(calendar);
      html += '<div class="#{bodyTopClass}"></div><div class="#{bodyClass}" id="#{bodyId}">';
      html += '</div><div class="#{bodyBottomClass}"></div>';
      html += this.renderFooter(calendar);
      this.container.innerHTML = this.evaluateWithOptions(html);
      this.rerender(calendar);
    },

    rerender: function(calendar) {
      this.getBody().innerHTML = this.evaluateWithOptions(this.renderBody(calendar));
      selectOption(this.getMonthSelect(), calendar.getMonth());
      selectOption(this.getYearSelect(), calendar.getYear());
      if (this.container.visible()) this.setPosition();
    },

    renderHeader: function(calendar) {
      var html = '';
      // required 'href'
      html += '<div class="#{headerTopClass}"></div><div class="#{headerClass}">' +
        '<a href="#" id="#{prevBtnId}" class="#{prevButtonClass}">&lt;&lt;</a>' +
        this.createSelect(calendar.getYear(), calendar.getMonth()) +
        '<a href="#" id="#{nextBtnId}" class="#{nextButtonClass}">&gt;&gt;</a>' +
        '</div><div class="#{headerBottomClass}"></div>';
      return html;
    },

    renderFooter: function(calendar) {
      return '<div class="#{footerTopClass}"></div><div class="#{footerClass}" id="#{footerId}"></div><div class="#{footerBottomClass}"></div>';
    },

    createSelect: function(year, month) {
      var yearPart = this.createYearSelect(year) + this.langFile.YEAR_LABEL;
      var monthPart = this.createMonthSelect(month) + this.langFile.MONTH_LABEL;
      if (this.langFile.YEAR_AND_MONTH) {
        return  yearPart + monthPart;
      } else {
        return monthPart + yearPart;
      }
    },

    createYearSelect: function(year) {
      var html = '';
      html += '<select id="#{yearSelectId}" class="#{yearSelectClass}">';
      for (var y = this.options.startYear, endy = this.options.endYear; y <= endy; y += 1) {
        html += '<option value="' + y + '"' + (y == year ? ' selected' : '') + '>' + y + '</option>';
      }
      html += '</select>';
      return html;
    },

    createMonthSelect: function(month) {
      if (!this.monthSelectHtml) {
        var html = '';
		if(this.options.lang == 'en'){
	        html += '<select id="#{monthSelectId}" class="#{monthSelectClassEn}">';
		}else{
	        html += '<select id="#{monthSelectId}" class="#{monthSelectClass}">';
		}
        for (var m = ProtoCalendar.JAN; m <= ProtoCalendar.DEC; m += 1) {
          html += '<option value="' + m + '"' + (m == month ? ' selected' : '') + '>' + this.langFile['MONTH_ABBRS'][m] + '</option>';
        }
        html += '</select>';
        this.monthSelectHtml = html;
      }
      return this.monthSelectHtml;
    },

    renderBody: function(calendar) {
      this.dayDivs = [];
      var html = '<table class="#{tableClass}" cellspacing="0">';
      html += '<tr class="#{labelRowClass}">';
      var othis = this;
      if (!this.headHtml) {
        this.headHtml = '';
        for (var i = 0; i < 7; i += 1) {
			var exClassName = '';
			var j = this.langFile.WEEK_DAYS_LIST[i];
			if (j == ProtoCalendar.SUNDAY) { exClassName = ' #{sundayClass}'; }
			if (j == ProtoCalendar.SATURDAY) { exClassName = ' #{saturdayClass}'; }
			othis.headHtml += '<th class="#{labelCellClass}' + exClassName + '">' +
			othis.getWeekdayLabel(i) +
			'</th>';
		}
      }
      html += this.headHtml;
      var curDay = this.getCalendarBeginDay(calendar);
      var calEndDay = this.getCalendarEndDay(calendar);
      html += '<tbody>';
      var dayNum = Math.round((calEndDay - curDay) / 1000 / 60 / 60 / 24);
      for(var i = 0; i < dayNum; i += 1, curDay.setDate(curDay.getDate() + 1)) {
        var divClassName;
        if(curDay.getMonth() != calendar.getMonth()) {
          divClassName = this.options.otherdayClass;
        } else if (curDay.getDay() == ProtoCalendar.SUNDAY) {
          divClassName = this.options.sundayClass;
        } else if (curDay.getDay() == ProtoCalendar.SATURDAY) {
          divClassName = this.options.saturdayClass;
        } else {
          divClassName = this.options.weekdayClass;
        }

        if (curDay.getDay() == this.langFile.weekFirstDay) { html += '<tr>'; }
        var dayId = this.getDayDivId(curDay);
        var dayHtml = '';
        if (this.isSelectable(curDay)) {
          dayHtml = '<a class="#{dayClass}" href="#" id="' + dayId +
            '" year="' + curDay.getFullYear() +
            '" month="' + curDay.getMonth() +
            '" day="' + curDay.getDate() +
            '">' + curDay.getDate() + '</a>';
          this.dayDivs.push(dayId);
        } else {
          divClassName += ' ' + this.options.disabledDayClass;
          dayHtml = curDay.getDate();
        }
        html += '<td class="' + divClassName + ' #{dayCellClass}">' + dayHtml + '</td>';
        if (curDay.getDay() == this.langFile.weekLastDay) { html += '</tr>'; }
      }
      html += '</tbody></table>';
      return html;
    }

  });

var ProtoCalendarController = Class.create();
ProtoCalendarController.prototype = {
  initialize: function(calendarRender, options) {
    this.options = Object.extend({
		onNoDateError: this.defaultOnNoDateError.bind(this)
		}, options);
    this.calendarRender = calendarRender;
    this.initializeDate();
    this.calendar = new ProtoCalendar(this.options);
    this.calendarRender.render(this.calendar);
    if (options.year && options.month && options.day) {
      var date = new Date(this.options.year, this.options.month, this.options.day);
      this.selectDate(date, true);
    } else {
      this.selectDate(null);
    }
    this.observeEventsOnce();
    this.observeEvents();
    this.onChangeHandlers = [];
  },

  initializeDate: function() {
    var date = ProtoCalendar.newDate();
    if (!this.options.year) {
      if (date.getFullYear() >= this.options.startYear && date.getFullYear() <= this.options.endYear) { 
        this.options.year = date.getFullYear();
      } else {
        this.options.year = this.options.startYear;
      }
    }
    if (!this.options.month) {
      this.options.month = date.getMonth();
    }
    if (!this.options.day) {
      this.options.day = date.getDate();
    }
  },

  observeEventsOnce: function() {
    var calrndr = this.calendarRender;
    calrndr.getPrevButton().observe('click', this.showPrevMonth.bindAsEventListener(this));
    calrndr.getNextButton().observe('click', this.showNextMonth.bindAsEventListener(this));
    var othis = this;
    var yearSelect = calrndr.getYearSelect();
    var monthSelect = calrndr.getMonthSelect();
    var year = this.calendar.getYear();
    var month = this.calendar.getMonth();
    yearSelect.observe('change', function() {
        othis.setMonth(parseInt(yearSelect[yearSelect.selectedIndex].value, 10), parseInt(monthSelect[monthSelect.selectedIndex].value, 10));
    });
    monthSelect.observe('change', function() {
        othis.setMonth(parseInt(yearSelect[yearSelect.selectedIndex].value, 10), parseInt(monthSelect[monthSelect.selectedIndex].value, 10));
    });
    // add auto focus
//    if (this.options.enableSecond) {
//      var second = calrndr.getSecondInput();
//      second.observe('keyup', this._autoFocus.bindAsEventListener(second, calrndr.getOkButton()));
//      second.observe('keydown', this._disablePaste.bindAsEventListener(second));
//      second.observe('contextmenu', this._disableContextMenu.bindAsEventListener(second));
//      if (navigator.appVersion.match(/\bMSIE\b/)) {
//        second.setStyle({'imeMode': 'disabled'});
//      }
//    }
  },
  _disableContextMenu: function(event) {
    Event.stop(event);
    return false;
  },
  _disablePaste: function(event) {
    // ctrl + v || shift + insert
    if ((event.keyCode == 86 && event.ctrlKey) || (event.keyCode == 45 && event.shiftKey)) {
      Event.stop(event);
      return false;
    }
  },
  _autoFocus: function(event, nextEl) {
    // shift || tab
    if (event.keyCode == 16 || event.keyCode == 9 || (event.keyCode == 9 && event.shiftKey)) {
      Event.stop(event);
      return false;
    }
    var v = this.value;
    if (v.length && v.length == 2) {
      nextEl.focus();
      nextEl.select();
    }
    return true;
  },
  observeEvents: function() {
    var othis = this;
    this.calendarRender.getDayDivs().each(function(el) {
		Event.observe(el, 'click', othis.onClickHandler.bindAsEventListener(othis));
		});
  },

  onClickHandler: function(event) {
    Event.stop(event);
    var date = this.calendarRender.getDateFromEl(Event.element(event));
    if (date) {
      this.selectDate(date);
      this.onChangeHandler();
      setTimeout(this.hideCalendar.bind(this), 150);
    }
  },

  onSubmit: function() {
    this.hideError();
    var date = this.selectedDate;
    if (!date) return this.options.onNoDateError();
    this.selectDate(date, true);
    this.onChangeHandler();
    this.hideCalendar();
  },

  selectDate: function(date, redraw) {
    this.calendarRender.deselectDate(this.selectedDate);
    this.selectedDate = date;
    if (!date) return;
    if (redraw && (date.getFullYear() != this.calendar.getYear() || date.getMonth() != this.calendar.getMonth())) {
      this.setMonth(date.getFullYear(), date.getMonth());
    }
    this.calendarRender.selectDate(this.selectedDate);
  },

  getSelectedDate: function() {
    return this.selectedDate;
  },

  addChangeHandler: function(func) {
    this.onChangeHandlers.push(func);
  },

  onChangeHandler: function() {
    this.onChangeHandlers.each(function(f) { f(); });
  },

  showCalendar: function() {
    this.calendarRender.show();
  },

  hideCalendar: function() {
    this.calendarRender.hide();
  },

  blurCalendar: function(event) {
    if (event.keyCode == 9) {
      this.hideImmediatelyCalendar();
    }
  },

  hideImmediatelyCalendar: function() {
    this.calendarRender.hideImmediately();
  },

  toggleCalendar: function() {
    this.calendarRender.toggle();
  },

  showPrevMonth: function(event) {
    this.shiftMonthByOffset(-1);
    if (event) Event.stop(event);
  },

  showNextMonth: function(event) {
    this.shiftMonthByOffset(1);
    if (event) Event.stop(event);
  },

  shiftMonthByOffset: function(offset) {
    if (offset == 0) return;
    var newDate = new Date(this.calendar.getDate().getTime());
    newDate.setMonth(newDate.getMonth() + offset);
    if (this.options.startYear > newDate.getFullYear() || this.options.endYear < newDate.getFullYear()) return;
    this.calendar.setMonthByOffset(offset);
    this.afterSet();
  },

  setMonth: function(year, month) {
    if (this.calendar.getYear() == year && this.calendar.getMonth() == month) return;
    this.calendar.setYear(year);
    this.calendar.setMonth(month);
    this.afterSet();
  },

  afterSet: function() {
    this.calendarRender.rerender(this.calendar);
    this.selectDate(this.selectedDate);
    this.observeEvents();
  },

  getContainer: function() {
    return this.calendarRender.getContainer();
  },

  defaultOnNoDateError: function() {
    this.calendarRender.defaultOnError('NO_DATE_ERROR');
  },

  hideError: function() {
    this.calendarRender.hideError();
  }
};

//Don't instantiate this, extend BaseCalendar
var BaseCalendar = Class.create();
BaseCalendar.bindOnLoad = function(f) {
  if (document.observe) {
    document.observe('dom:loaded', f);
  } else {
    Event.observe(window, 'load', f);
  }
};


BaseCalendar.prototype = {
  initialize: function(options) {
    throw "Cannot instantiate BaseCalendar.";
  },

  initializeOptions: function(options) {
    if (!options) options = {};
    this.options = Object.extend({
		startYear: ProtoCalendar.newDate().getFullYear(),
		endYear: ProtoCalendar.newDate().getFullYear() + 1,
		minDate: new Date(1900, 0, 1),
		maxDate: new Date(3000, 0, 1),
		format: ProtoCalendar.LangFile[options.lang || ProtoCalendar.LangFile.defaultLang]['DEFAULT_FORMAT'],
		lang: ProtoCalendar.LangFile.defaultLang
	}, options);
  },

  initializeBase: function() {
    this.calendarController = new ProtoCalendarController(new ProtoCalendarRender(this.options), this.options);
    this.langFile = ProtoCalendar.LangFile[this.options.lang] || ProtoCalendar.LangFile.defaultLangFile();
    this.changeHandlers = [];
    this.observeEvents();
  },

  observeEvents: function() {
    Event.observe(document.body, 'click', this.windowClickHandler.bindAsEventListener(this));
    this.calendarController.addChangeHandler(this.onCalendarChange.bind(this));
    this.doObserveEvents();
  },

  doObserveEvents: function() {
    //Override this
  },

  windowClickHandler: function(event) {
    var target = $(Event.element(event));
    if (target != this.input && !Element.descendantOf(target, this.calendarController.getContainer())) {
      this.calendarController.hideCalendar();
    }
  },

  addChangeHandler: function(f) {
    this.changeHandlers.push(f);
  },

  onCalendarChange: function() {
    this.changeHandlers.each(function(f) { f(); });
  }
};

var InputCalendar = Class.create();
InputCalendar.createOnLoaded = function(input, options) {
  BaseCalendar.bindOnLoad(function() {
    new InputCalendar(input, options);
  });
};
InputCalendar.initCalendars = function(inputs, options) {
  if (document.observe) {
    document.observe('dom:loaded', function() {
      $$(inputs).each(function(input) {
        new InputCalendar(input, options);
      });
    });
  } else {
    Event.observe(window, 'load', function() {
      $$(inputs).each(function(input) {
        new InputCalendar(input, options);
      });
    });
  }
};
Object.extend(InputCalendar.prototype, BaseCalendar.prototype);
Object.extend(
  InputCalendar.prototype,
  {
    initialize: function(input, options) {
      this.input = $(input); // used in doObserveEvents()
      this.initializeOptions(options);
      this.options = Object.extend({
		alignTo: input,
		inputReadOnly: true,
		labelFormat: undefined,
		labelEl: undefined
	  }, this.options);
      this.initializeBase();
      this.initializeInput();
      this.labelEl = $(this.options.labelEl);
    },

    initializeInput: function() {
      this.dateFormat = new ProtoCalendar.DateFormat(this.options.format);
      if (this.input.value && this.dateFormat.parse(this.input.value, this.options.lang)) {
        this.onInputChange();
      } else {
        this.onCalendarChange();
      }
      this.input.setAttribute('readOnly', 'true');
    },

    doObserveEvents: function() {
      this.input.observe('change', this.onInputChange.bind(this));
      this.input.observe('focus', this.calendarController.showCalendar.bind(this.calendarController));
      this.input.observe('keydown', this.calendarController.blurCalendar.bindAsEventListener(this.calendarController));
      this.addChangeHandler(this.changeInputValue.bind(this));
    },

    onInputChange: function() {
		var date = this.dateFormat.parse(this.input.value, this.options.lang);
		if (date) {
			this.calendarController.selectDate(date, true);
		} else {
			var inputValue = this.input.value.toLowerCase();
			var date;
//			if (this.langFile['today'] && this.langFile['today'] == inputValue || inputValue == 'today') {
//				date = ProtoCalendar.newDate();
//			} else if (this.langFile.parseDate && (date = this.langFile.parseDate(inputValue))) {
			if (this.langFile.parseDate && (date = this.langFile.parseDate(inputValue))) {
          //done is parseDate
			} else {
				date = undefined;
			}
			this.calendarController.selectDate(date, true);
			this.onCalendarChange();
		}
    },

    changeInputValue: function() {
		var d = this.calendarController.getSelectedDate();
		this.input.value = this.dateFormat.format(d, this.options.lang);
		if (!this.labelEl) return;
		var m = d.getMonth()+1;
		this.labelEl.value = d.getFullYear()+"/"+m+"/"+d.getDate();
	}
  });


ProtoCalendar.DateFormat = Class.create();
Object.extend(ProtoCalendar.DateFormat,
	{
		MONTH_ABBRS: ProtoCalendar.LangFile.en.MONTH_ABBRS,
		WEEKDAY_ABBRS: ProtoCalendar.LangFile.en.WEEKDAY_ABBRS,
		formatRegexp: /(?:d{3,4}i|d{1,4}|m{1,4}|yy(?:yy)?|([hHMs])\1?|TT|tt|[lL])|.+?/g,
		zeroize: function (value, length) {
		if (!length) length = 2;
		value = String(value);
		for (var i = 0, zeros = ''; i < (length - value.length); i++) {
			zeros += '0';
		}
		return zeros + value;
	}
});

ProtoCalendar.DateFormat.prototype =  {
  initialize: function(format) {
	this.dateFormat = format;
	this.formatterInited = false;
  },

  format: function(date, lang) {
	if (!this.formatterInited) this.initFormatter();
	if (!date) return '';
	var langFile = ProtoCalendar.LangFile[lang || ProtoCalendar.LangFile.defaultLang];
	var str = '';
	this.formatHandlers.each(function(f) {
		str += f(date, langFile);
	});
    return str;
  },

  initFormatter: function() {
    var handlers = [];
    var matches = this.dateFormat.match(ProtoCalendar.DateFormat.formatRegexp);
    for (var i = 0, n = matches.length; i < n; i++) {
		switch(matches[i]) {
			case 'dd':	handlers.push(function(date, lf) { return ProtoCalendar.DateFormat.zeroize(date.getDate()) }); break;
			case 'mm':	handlers.push(function(date, lf) { return ProtoCalendar.DateFormat.zeroize(date.getMonth() + 1); }); break;
			case 'mmm':	handlers.push(function(date, lf) { return lf.MONTH_ABBRS[date.getMonth()]; }); break;
			case 'yyyy':handlers.push(function(date, lf) { return date.getFullYear(); }); break;
			default:	handlers.push(createIdentity(matches[i]));
		}
	};
    this.formatHandlers = handlers;
    this.formatterInited = true;
  },

  parse: function(str, lang) {
	if (!str) return undefined;
	var date = ProtoCalendar.newDate();
	var d = str.split("/");
	if(lang == "en"){
		date.setDate(d[0]);
		var m = "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".indexOf(d[1]) / 4;
		date.setMonth(m);
		date.setFullYear(d[2]);
	} else if(lang == "mm"){
		date.setDate(d[0]);
		date.setMonth(d[1]-1);
		date.setFullYear(d[2]);
	}else{
		date.setFullYear(d[0]);
		date.setMonth(d[1]-1);
		date.setDate(d[2]);
	}
    return date;
  }
};


function createIdentity(v) {
  return function() { return v; }
}

function selectOption(select, value) {
  var selectEl = $(select);
  var options = selectEl.options;
  for (var i = 0; i < options.length; i++) {
    if (options[i].value === value.toString()) {
      options[i].selected = true;
      return;
    }
  }
}

var SelectCalendar = Class.create();
SelectCalendar.createOnLoaded = function(select, options) {
  BaseCalendar.bindOnLoad(function() { new SelectCalendar(select, options); });
};
Object.extend(SelectCalendar.prototype, BaseCalendar.prototype);
Object.extend(
  SelectCalendar.prototype,
  {
    initialize: function(select, options) {
      this.yearSelect = $(select.yearSelect);
      this.monthSelect = $(select.monthSelect);
      this.daySelect = $(select.daySelect);
      this.initializeOptions(options);
      this.options = Object.extend({alignTo: select.yearSelect}, this.options);
      this.initializeBase();
      this.initializeSelect();
    },

    initializeSelect: function() {
      if (this.getSelectedDate()) {
        this.onSelectChange();
      } else {
        this.onCalendarChange();
      }
    },

    doObserveEvents: function() {
      this.yearSelect.observe('change', this.onSelectChange.bind(this));
      this.monthSelect.observe('change', this.onSelectChange.bind(this));
      this.daySelect.observe('change', this.onSelectChange.bind(this));
      this.addChangeHandler(this.changeSelectValue.bind(this));
    },

    onSelectChange: function() {
      var date = this.getSelectedDate();
      if (!date) return;
      this.calendarController.selectDate(date, true);
      this.onCalendarChange();
    },

    changeSelectValue: function() {
      var date = this.calendarController.getSelectedDate();
      if (date) {
        selectOption(this.yearSelect, date.getFullYear());
        selectOption(this.monthSelect, date.getMonth() + 1);
        selectOption(this.daySelect, date.getDate());
      }
    },

    getSelectedDate: function() {
      if (this.yearSelect.value == '' 
          || this.monthSelect.value == '' 
          || this.daySelect.value == '') {
        return undefined;
      }
      var d = ProtoCalendar.newDate();
      d.setFullYear(this.yearSelect.value);
      d.setMonth(this.monthSelect.value - 1);
      d.setDate(this.daySelect.value);
      if (isNaN(d.getTime())) {
        return undefined;
      } else {
        return d;
      }
    }
  });

