var Calendar = Class.create({

	initialize: function(options) {
		options = options || {};

		this.textField = $(options.textField);

		this.converter = options.converter || Calendar.DEFAULT_CONVERTER;

		var selectedDate = this.textField ? this.converter.parse(this.textField.getValue()) : null;

		if (options.positionImage) {
			this.position = Calendar._createPositionFromImage($(options.positionImage));
		} else {
			this.position = options.position || new Point(0, 0);
		}

		var highlightStyleClass = options.highlightStyleClass || 'highlighted_date';

		var highlightSelectedDate = !!options.highlightSelectedDate;

		this.highlightedDateMap = {};
		if (selectedDate && highlightSelectedDate) {
			this.highlightedDateMap[selectedDate.formatMsDsYYYY()] = highlightStyleClass;
		}
		if (options.highlightedDates) {
			options.highlightedDates.each(
				(function(date) {
					date = Date.resolveInputDate(date);
					if (date) {
						this.highlightedDateMap[date.formatMsDsYYYY()] = highlightStyleClass;
					}
				}).bind(this)
			);
		}
		if (options.styleInfos) {
			options.styleInfos.each(
				(function(styleInfo) {
					if (styleInfo && styleInfo.dates) {
						var styleClass = styleInfo.styleClass || highlightStyleClass;
						styleInfo.dates.each(
							(function(date) {
								date = Date.resolveInputDate(date);
								if (date) {
									this.highlightedDateMap[date.formatMsDsYYYY()] = styleClass;
								}
							}).bind(this)
						);
					}
				}).bind(this)
			);
		}

		if (options.selectableRange) {
			options.selectableRange.start = Date.resolveInputDate(options.selectableRange.start);
			options.selectableRange.end   = Date.resolveInputDate(options.selectableRange.end);

			var rangeStartDate = options.selectableRange.start ? options.selectableRange.start.clearTime() : null;
			var rangeEndDate   = options.selectableRange.end ? options.selectableRange.end.clearTime() : null;

			this.selectableRange = { start: rangeStartDate, end: rangeEndDate };
		} else {
			this.selectableRange = { start: null, end: null };
		}

		this.onOpen      = options.onOpen || null;
		this.onPreSelect = options.onPreSelect || null;
		this.onSelect    = options.onSelect || null;
		this.onClose     = options.onClose || null;

		var startDate = Date.resolveInputDate(options.startDate) || selectedDate || Date.today();

		this.startMonth = startDate.getFirstOfMonth();

		this.isOpen = false;
		this.div    = null;
		this.iframe = null;
	},

	open: function() {
		if (!this.isOpen) {
			this.div = this._createDiv();
			document.body.appendChild(this.div);

			if (isBrowserIE) {
				this.iframe = this._createIFrame();
				document.body.appendChild(this.iframe);
			}

			this._draw();

			this.isOpen = true;

			if (this.onOpen) {
				this.onOpen.call(null, this);
			}
		}
	},

	close: function() {
		if (this.isOpen) {
			if (this.iframe) {
				document.body.removeChild(this.iframe);
				this.iframe = null;
			}

			document.body.removeChild(this.div);
			this.div = null;

			this.isOpen = false;

			if (this.onClose) {
				this.onClose.call(null, this);
			}
		}
	},

	_changeMonth: function(delta) {
		this.startMonth.addMonths(delta);
		this._draw();
	},

	_createDiv: function() {
		var div = $(document.createElement('div'));

		div.className      = 'cal';
		div.style.position = 'absolute';
		div.style.left     = this.position.x + "px";
		div.style.top      = this.position.y + "px";
		div.style.zIndex   = Calendar.IFRAME_ZORDER + 1;

		return div;
	},

	_createIFrame: function() {
		var iframe = $(document.createElement('iframe'));

		iframe.src            = Calendar.IFRAME_SRC;
		iframe.scrolling      = 'no';
		iframe.frameborder    = '0px';
		iframe.style.position = 'absolute';
		iframe.style.left     = this.position.x + 'px';
		iframe.style.top      = this.position.y + 'px';
		iframe.style.width    = Calendar.TOTAL_WIDTH + 'px';
		iframe.style.height   = Calendar.IFRAME_HEIGHT + 'px';
		iframe.style.zIndex   = Calendar.IFRAME_ZORDER;

		return iframe;
	},

	_draw: function() {
		this.div.update(this._getHtml());
	},

	_getCellStyle: function(date, isDateSelectable) {
		var cellStyle = this.highlightedDateMap[date.formatMsDsYYYY()];
		if (cellStyle) {
			return cellStyle;
		}

		return isDateSelectable ? 'normal_date' : 'nonselectable_date';
	},

	_getHtml: function() {
		var firstsOfMonth = new Array(Calendar.MONTH_COUNT);
		(Calendar.MONTH_COUNT).times((function(monthIndex){
			firstsOfMonth[monthIndex] = this.startMonth.getCopyPlusMonths(monthIndex);
		}).bind(this));

		var html = '';

		html += '<table border="0" cellpadding="0" cellspacing="0" width="' + Calendar.TOTAL_WIDTH + '">';

		html += '<tr class="cal_header">';
		html += '<td class="arrow" width="' + Calendar.ARROW_WIDTH + '"><a href="javascript:void(0)" onclick="Calendar._changeMonth(-1); return false;">&laquo;</a></td>';
		firstsOfMonth.each((function(firstOfMonth, monthIndex) {
			if (monthIndex > 0) {
				html += '<td width="' + Calendar.GAP_WIDTH + '">&nbsp;</td>';
			}
			html += '<td class="month" width="' + Calendar.MONTH_WIDTH + '">' + Calendar.MONTH_NAMES[firstOfMonth.getMonth()] + ' ' + firstOfMonth.getFullYear() + '</td>';
		}).bind(this));
		html += '<td class="arrow" width="' + Calendar.ARROW_WIDTH + '"><a href="javascript:void(0)" onclick="Calendar._changeMonth(1); return false;">&raquo;</a></td>';
		html += '</tr>';

		html += '<tr>';
		html += '<td colspan="' + Calendar.OUTER_CELL_COUNT + '">';
		html += '<table border="0" cellpadding="0" cellspacing="0" class="calendar_border">';

		html += '<tr valign="top">';
		firstsOfMonth.each((function(firstOfMonth) {
			html += '<td class="calendar">';
			html += '<table bgcolor="#ffffff" border="0" cellpadding="0" cellspacing="0">';

			html += '<tr class="day_bgcolor">';
			html += '<td class="calendar_padding">';
			html += '<table border="0" cellpadding="0" cellspacing="0">';
			html += '<tr>';
			Calendar.DOW_TITLES.each((function(dowTitle) {
				html += '<td class="day" width="' + Calendar.CELL_WIDTH + '">' + dowTitle + '</td>';
			}).bind(this));
			html += '</tr>';
			html += '</table>';
			html += '</td>';
			html += '</tr>';

			var date = firstOfMonth.getCopyPlusDays(-firstOfMonth.getDay());

			html += '<tr>';
			html += '<td class="calendar_padding">';
			html += '<table border="0" cellpadding="0" cellspacing="0">';
			(6).times((function(){
				html += '<tr>';

				(7).times((function() {
					var cellStyle    = null;
					var cellContents = null;

					if (date.isInSameMonth(firstOfMonth)) {
						var isDateSelectable = this._isDateSelectable(date);

						cellStyle    = this._getCellStyle(date, isDateSelectable);
						cellContents = date.getDate();

						if (this.textField && isDateSelectable) {
							cellContents = '<a href="javascript:void(0)" onclick="Calendar._selectDate(\'' + date.formatMsDsYYYY() + '\');">' + cellContents + '</a>';
						}
					} else {
						cellStyle    = 'non_date';
						cellContents = '&nbsp;';
					}

					html += '<td class="date ' + cellStyle + '" width="' + Calendar.CELL_WIDTH + '" height="' + Calendar.CELL_HEIGHT + '">' + cellContents + '</td>';

					date.addDays(1);
				}).bind(this));

				html += '</tr>';
			}).bind(this));
			html += '</table>';
			html += '</td>';
			html += '</tr>';

			html += '</table>';
			html += '</td>';
		}).bind(this));
		html += '</tr>';

		html += '</table>';
		html += '</td>';
		html += '</tr>';

		html += '<tr>';
		html += '<td colspan="' + Calendar.OUTER_CELL_COUNT + '">';
		html += '<div class="cal_footer">';
		html += '<a href="javascript:void(0)" onclick="Calendar.close(); return false;">Close</a>';
		html += '</div>';
		html += '</td>';
		html += '</tr>';

		html += '</table>';

		return html;
	},

	_isDateSelectable: function(date) {
		if (this.selectableRange.start && (date < this.selectableRange.start)) {
			return false;
		} else if (this.selectableRange.end && (date >= this.selectableRange.end)) {
			return false;
		}
		return true;
	},

	_selectDate: function(inputDate) {
		if (this.onPreSelect) {
			if (!this.onPreSelect.call(null, this, inputDate)) {
				return false;
			}
		}

		if (this.textField) {
			this.textField.setValue(this.converter.format(inputDate));
		}

		if (this.onSelect) {
			this.onSelect.call(null, this, inputDate);
		}

		return true;
	}
});

Calendar.DOW_TITLES  = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
Calendar.MONTH_COUNT = 2;
Calendar.MONTH_NAMES = ['January', 'February', 'March', 'April', 'May', 'June',
                        'July', 'August', 'September', 'October', 'November', 'December'],

Calendar.ARROW_WIDTH = 18;
Calendar.GAP_WIDTH   = 18;
Calendar.MONTH_WIDTH = 120;

Calendar.CELL_HEIGHT = 20;
Calendar.CELL_WIDTH  = 20;

Calendar.TOTAL_WIDTH = (2 * Calendar.ARROW_WIDTH) + (Calendar.MONTH_WIDTH * Calendar.MONTH_COUNT) + (Calendar.GAP_WIDTH * (Calendar.MONTH_COUNT - 1));
Calendar.OUTER_CELL_COUNT = 2 + Calendar.MONTH_COUNT + (Calendar.MONTH_COUNT - 1);

Calendar.IFRAME_HEIGHT = 200;
Calendar.IFRAME_SRC    = '/blank.jsp';
Calendar.IFRAME_ZORDER = 11;

Calendar.DEFAULT_CONVERTER = {
	format: function(date) {
		return date ? date.formatInputDate() : '';
	},

	parse: function(source) {
		return Date.parseInputDate(source);
	}
};

Calendar._instance = null;

Calendar.open = function(options) {
	Calendar.close();

	Calendar._instance = new Calendar(options);
	Calendar._instance.open();
};

Calendar.openForTravelStartDate = function(startField, endField, image) {
	startField = $(startField);
	endField = $(endField);
	image = $(image);

	Calendar.open({
		textField: startField,
		positionImage: image,
		selectableRange: { start: Date.today() },
		highlightedDates: [startField.getValue(), endField.getValue()]
	});
};

Calendar.openForTravelEndDate = function(startField, endField, image) {
	startField = $(startField);
	endField = $(endField);
	image = $(image);

	Calendar.open({
		textField: endField,
		startDate: startField.getValue(),
		positionImage: image,
		selectableRange: { start: Date.today() },
		highlightedDates: [startField.getValue(), endField.getValue()]
	});
};

Calendar.close = function() {
	if (Calendar._instance) {
		Calendar._instance.close();
		Calendar._instance = null;
	}
};

Calendar._changeMonth = function(delta) {
	if (Calendar._instance) {
		Calendar._instance._changeMonth(delta);
	}
};

Calendar._createPositionFromImage = function(image) {
	var imagePos = ImageUtils.getPosition(image);

	var x = imagePos.x + image.width + 3;
	var y = imagePos.y;

	var xOver = (x + Calendar.TOTAL_WIDTH) - (document.viewport.getRight() - 17);
	if (xOver > 0) {
		x -= xOver;
		y += image.height + 3;
	}

	return new Point(x, y);
};

Calendar._selectDate = function(inputDateString) {
	if (Calendar._instance) {
		var inputDate = Date.parseMsDsYYYY(inputDateString);
		if (Calendar._instance._selectDate(inputDate)) {
			Calendar.close();
		}
	}
};
