var imagelang = 'en' ; var currlang = 'en' ; var prefixpath = 'http://static.asiawebdirect.com/awd/' ; hasShortType = 'true';	
var httphrefShort = location.href;
httphrefShort = httphrefShort.replace("http://", "");
var arrhttphrefShort = httphrefShort.split("/");
httphrefShort = "http://" + arrhttphrefShort[0];

var pathwebcheckavailabilityShort = httphrefShort + '/smartreserve/indexShort.php';
var ckAvailStartTdyShort = new Date();
//remove time from Date
var dateCutOffdays = 2;
var dateAddNight = 3;
ckAvailStartTdyShort = new Date(ckAvailStartTdyShort.getFullYear(),  ckAvailStartTdyShort.getMonth(), ckAvailStartTdyShort.getDate());
var ckAvailStartDateShort = new Date(ckAvailStartTdyShort.getFullYear(),  ckAvailStartTdyShort.getMonth(), ckAvailStartTdyShort.getDate() + dateCutOffdays);
var ckAvailEndDateShort = new Date(ckAvailStartDateShort.getFullYear(),  ckAvailStartDateShort.getMonth(), ckAvailStartDateShort.getDate() + dateAddNight);

function sm_getForm() {
	var formname = (arguments[0]) ? arguments[0] : "checkAvailShort";
	return document.forms[formname];
}

function sm_getEl(tmpname) {
	var formuse = (arguments[1]) ? arguments[1] : sm_getForm();
	
	if (formuse == undefined) return false;;
	
	return formuse.elements[tmpname];
}

//->this section may be same javascript other file
function padZero(num) {
	return (num< 10) ? '0' + num : num ;
}

function IsIE() {
	return navigator.appVersion.match(/\bMSIE\b/);
}

function ChDatetoStr(cDate) {
	return padZero(cDate.getDate()) + '/' + padZero(cDate.getMonth() + 1) + '/' + cDate.getFullYear();
}

function ChStrtoDate(cDate) {
	var arrcDate = cDate.split("/");
	return new Date(arrcDate[2], Number(arrcDate[1])-1, Number(arrcDate[0]));
}

function setValue(arrElement, values) {
	var formuse = (arguments[2]) ? arguments[2] : sm_getForm();
	var i;
	var j = arrElement.length;
	var tmpInput;
	for(i=0;i<j;i++) {
		tmpInput = sm_getEl(arrElement[i], formuse);
		if((tmpInput!=null) && (tmpInput!=false))
		{
			if(tmpInput.type.indexOf("select")==0) setElement(tmpInput, values);
			else tmpInput.value = values;
		}
	}
}

function setElement(cElement,cValue) {
	for(var i=0;i<cElement.length;i++) {
		if(cElement.options[i].value==cValue) {
			cElement.selectedIndex = i;
			cElement.options[i].setAttribute('selected', true);
			break;
		}
	}
}
//<-this section may be same javascript other file

function startapp(formuse, TypeSet) {
	var m;
	//create date option
	var days = new Array("checkHotel[sDay2]", "checkHotel[eDay2]");
	var tmpdate;
	for(m=0;m<2;m++) {
		obj = sm_getEl(days[m], formuse);
		
		if (!obj) return;
		
		obj.length = 0;
		for(i=0;i<31;i++) {
			tmpdate = padZero(i+1);
			obj.options[i]=new Option(tmpdate, tmpdate);
		}	
	}

	//create month option
	var months = new Array("checkHotel[sMonth2]", "checkHotel[eMonth2]");
	var tmpmonth;
	if(sm_getEl('checkHotel[LanguageCode]', formuse).value=="ja") 
		tmpmonth = new Array("1ï¿½ï¿½","2ï¿½ï¿½","3ï¿½ï¿½","4ï¿½ï¿½","5ï¿½ï¿½","6ï¿½ï¿½","7ï¿½ï¿½","8ï¿½ï¿½","9ï¿½ï¿½","10ï¿½ï¿½","11ï¿½ï¿½","12ï¿½ï¿½");
	else 
		tmpmonth = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");

	for(m=0;m<2;m++) {
		obj = sm_getEl(months[m], formuse);
		obj.length = 0;
		for(i=0;i<12;i++) {
			obj.options[i]=new Option(tmpmonth[i], padZero(i+1));
		}
	}

	//create year option
	var years = new Array("checkHotel[sYear2]", "checkHotel[eYear2]");
	var m;
	var tmpyear;

	var tnow = new Date();
	var sMonth = tnow.getMonth() + 1;
	var sYear = tnow.getFullYear();
	var sYearOld = sYear;
	var i;
	for(i=0;i<=17;i++) {
		sMonth = sMonth + 1;
		if(sMonth>=13) {
			sMonth = 1;
			sYear = sYear + 1;
		}
	}
	prefixyearAddOn = sYear - sYearOld;

	for(m=0;m<2;m++) {
		obj = sm_getEl(years[m], formuse);
		obj.length = 0;
		tmpyear = ckAvailStartTdyShort.getFullYear();
		for(i=0;i<=prefixyearAddOn;i++) {
			obj.options[i]=new Option(tmpyear, tmpyear);
			tmpyear++;
		}
	}

	sm_setAllDate(ChDatetoStr(ckAvailStartDateShort), "sDate");
	sm_setAllDate(ChDatetoStr(ckAvailEndDateShort), "eDate");
	sm_getEl('checkHotel[Nights]', formuse).value = dateAddNight;
	if(sm_getEl('checkHotel[pDestinationID]', formuse)) showshortDestination(TypeSet, formuse);
	else setSubmitButtonShort(true);
}

//dates is string date = 31/01/2007
function sm_setAllDate(dates, caseset) {
	var arrtmpdate = dates.split("/");

	if(caseset=="sDate") {
		var a = new Array("checkHotel[sDay]", "checkHotel[sDay2]");
		var b = new Array("checkHotel[sMonth]", "checkHotel[sMonth2]");
		var c = new Array("checkHotel[sYear]", "checkHotel[sYear2]");
		var d = new Array("txtCheck_InShort");
	} else

	if(caseset=="eDate") {
		var a = new Array("checkHotel[eDay]", "checkHotel[eDay2]");
		var b = new Array("checkHotel[eMonth]", "checkHotel[eMonth2]");
		var c = new Array("checkHotel[eYear]", "checkHotel[eYear2]");
		var d = new Array("txtCheck_OutShort");
	}

	setValue(a, arrtmpdate[0]);
	setValue(b, arrtmpdate[1]);
	setValue(c, arrtmpdate[2]);
	setValue(d, dates);
}

function showshortDestination(fCountry, formuse) {
	var a = sm_getEl("pDestinationID", formuse).value;
	var b = sm_getEl("DestinationID", formuse).value;
	en_disListShort(true, formuse);
	var lang = sm_getEl('checkHotel[LanguageCode]', formuse).value;
	var x = pathwebcheckavailabilityShort + '?action=dest&deF='+a+'&deF2='+b+'&lang='+lang;
	if(fCountry!="") x = x + "&TypeSet=" + fCountry;
	runXmlReturnJavascript(x);
}

function changelistShort(aarr, barr, obj, indexSelected) {
	var i;
	obj = sm_getEl(obj);
	obj.length = 0;
	for(i=0;i<aarr.length;i++) {
		obj.options[i]=new Option(aarr[i],barr[i]);
	}
	obj.selectedIndex = indexSelected;
	en_disListShort(false);
}

function sm_search(formuse) {
	if(sm_getEl("checkHotel[pDestinationID]", formuse)) {
		if(sm_getEl("checkHotel[pDestinationID]", formuse).value=="0") {
			alert("Please select a country");
			return false;
		}
		
		sm_getEl("pDestinationID", formuse).value = sm_getEl("checkHotel[pDestinationID]", formuse).value;
		
		if(sm_getEl("checkHotel[DestinationID]", formuse).options[0].selected) {
			sm_getEl("checkHotel[DestinationID]", formuse).options[0].value = sm_getEl("pDestinationID", formuse).value;
		}
		sm_getEl("DestinationID", formuse).value = sm_getEl("checkHotel[DestinationID]", formuse).value;
	}

	formuse.submit();
}

function changeDestinationShort() {
	var obj = sm_getEl('checkHotel[pDestinationID]');
	if(obj.value=="0") {
		var a = sm_getEl("checkHotel[DestinationID]");
		a.length = 0;
		a.options[0]=new Option("Select a city/area", 0);
		return false;
	}

	en_disListShort(true);
	var b = sm_getEl("pDestinationID").value;
	var lang = sm_getEl('checkHotel[LanguageCode]').value;
	var x = pathwebcheckavailabilityShort + '?action=hotel&hotel='+obj.value+'&deF='+b+'&lang='+lang;
	runXmlReturnJavascript(x);
}

function setSubmitButtonShort(flag) {
	var formuse = (arguments[1]) ? arguments[1] : sm_getForm();
	var cElement = formuse.getElementsByTagName('input')
	var i;
	var j = cElement.length;
	var k = null;
	for(i=0;i<j;i++) {
		if(cElement[i].name=="Submit") k = cElement[i];
	}
	if(k!=null) k.style.display = (flag) ? "" : "none";
}

function en_disListShort(flag) {
	var cForm = (arguments[1]) ? arguments[1] : sm_getForm();
	var cElement = cForm.elements['checkHotel[pDestinationID]'];
	cElement.disabled=flag;
	var cElement = cForm.elements['checkHotel[DestinationID]'];
	cElement.disabled=flag;
	setSubmitButtonShort(false, cForm);
}

function ValidateDate(tmpdate) {
	var defRtn = (arguments[1]) ? arguments[1] : 99;
	var chkdate = ChDatetoStr(ChStrtoDate(tmpdate));
	if(defRtn==99) return (chkdate==tmpdate);
	else return (chkdate==tmpdate) ? tmpdate : defRtn;
}

function updateCheckAvailAllShort(column) {
	var obj = (arguments[1]) ? arguments[1] : null;
	var defDate;
	var dateSel;
	var tmpselmonth;
	if(column=="sDate") {
		defDate = ChDatetoStr(ckAvailStartTdyShort);
		tmpselmonth = sm_getEl("checkHotel[sMonth2]").value;
		dateSel = sm_getEl("checkHotel[sDay2]").value + '/' + tmpselmonth + '/' + sm_getEl("checkHotel[sYear2]").value;
	} else 
	
	if(column=="eDate") {
		defDate = ChDatetoStr(ckAvailStartDateShort);
		tmpselmonth = sm_getEl("checkHotel[eMonth2]").value;
		dateSel = sm_getEl("checkHotel[eDay2]").value + '/' + tmpselmonth + '/' + sm_getEl("checkHotel[eYear2]").value;
	}

	dateSel = ValidateDate(dateSel, defDate);
	var tmpdateSel = ChStrtoDate(dateSel);
	if(
		(tmpdateSel.getTime()<ckAvailStartTdyShort.getTime()) &&
		(tmpselmonth-1==ckAvailStartTdyShort.getMonth())
		) {
		var tmpnewmonth = new Date(tmpdateSel.getFullYear(),  Number(tmpselmonth), tmpdateSel.getDate());
		dateSel = ChDatetoStr(tmpnewmonth);
	}

	sm_setAllDate(dateSel, column);

	var ckIn = ChStrtoDate(sm_getEl("txtCheck_InShort").value);
	var ckOut = ChStrtoDate(sm_getEl("txtCheck_OutShort").value);

	if(ckIn.getTime()<ckAvailStartTdyShort.getTime()) {
		sm_setAllDate(ChDatetoStr(ckAvailStartTdyShort), "sDate");
	}

	if(ckOut.getTime()<=ckIn.getTime()) {
		var tmpaddday = ChStrtoDate(sm_getEl("txtCheck_InShort").value);
		tmpaddday = new Date(tmpaddday.getFullYear(), tmpaddday.getMonth(), tmpaddday.getDate() + 1);
		sm_setAllDate(ChDatetoStr(tmpaddday), "eDate");
	}

	try {
		var ckIn = ChStrtoDate(sm_getEl("txtCheck_InShort").value);
		var ckOut = ChStrtoDate(sm_getEl("txtCheck_OutShort").value);
		var cTime  = ckOut.getTime() - ckIn.getTime();
		var nDate  = new Date(cTime);
		var cValue = Number(nDate.getDate()) - 1;
		cValue = Math.floor(cTime / (1000 * 60 * 60 * 24));
		sm_getEl("checkHotel[Nights]").value = cValue;
	} catch(e) 	{}
}

function showChangeDateShort(values) {
	if(values=='in') {
		sm_setAllDate(sm_getEl('txtCheck_InShort').value, "sDate");
		updateCheckAvailAllShort('sDate');
	} else 

	if(values=='out') {
		sm_setAllDate(sm_getEl('txtCheck_OutShort').value, "eDate");
		updateCheckAvailAllShort('eDate');
	}
}

function updateMtxDate(type) {
	if (type=="in") {
		setDateSelect("in");
		updateMtxDate("nights");
	} else if (type == "nights") {
		var chk_in = strToDateObj($('mtx_check_in').value);
		var nights = parseInt($('num_nights').value, 10);
		var chk_out = chk_in;
		chk_out.setDate(chk_out.getDate()+nights);
		$('mtx_check_out').value = padZero(chk_out.getDate()) + "/" + padZero(chk_out.getMonth()+1) + "/" + chk_out.getFullYear();
		updateMtxDate("out");
	} else if (type == "out") {
		setDateSelect("out");
		
		// update Nights select
		var chk_in = strToDateObj($('mtx_check_in').value);
		var chk_out = strToDateObj($('mtx_check_out').value);
		var diff = chk_out.getTime()-chk_in.getTime();
		var nights = diff/(24*3600*1000);
		//$('num_nights').value = nights;
		updateNightsSelected(nights);
	} 
	
}

function updateNightsSelected(nights) {
	var n = $('num_nights');
	var max_val = parseInt(n.options[n.options.length-1].value,10);
	if (max_val >= nights) {
		n.value = nights;
	} else {
		// we don't have this number yet
		for (var i=max_val+1; i<=nights; i++) {
			var o = new Option(i, i);
			if (i == nights) {
				o.selected = "selected";
			}
			n.options[n.options.length] = o;
		}
	}
}

function updateMonthSelected(sel, val) {
	var val_year = parseInt(val.substr(0,4),10);
	var val_month = parseInt(val.substr(4),10);
	
	var str_max = sel.options[sel.options.length-1].value;
	var max_year = parseInt(str_max.substr(0, 4),10);
	var max_month = parseInt(str_max.substr(4),10);
	
	if (max_year>val_year || (max_year==val_year && max_month>=val_month)) {
		sel.value = val;
	} else {
		// option not available yet
		var end = new Date(val_year, val_month, 1);
		var d = new Date(max_year, max_month , 1);
		do {
			d.setMonth(d.getMonth()+1);
			var str = d.toDateString();
			var tmp = str.split(" ");
			var txt = tmp[1] + ", " + tmp[3];
			var val = d.getFullYear() + "" + d.getMonth();
			
			var o = new Option(txt, val);
			if (i==0) {
				o.selected = "selected";
			}
			sel.options[sel.options.length] = o;
		} while (d <= end);
	}
}

function setDateSelect(type) {
	if (type == "in") {
		var input = $('mtx_check_in');
		var output = $('check_in_date');
		var obj_month = $('check_in_month');
	} else if (type == "out") {
		var input = $('mtx_check_out');
		var output = $('check_out_date');
		var obj_month = $('check_out_month');
	}
	var str_date = input.value
	
	var tmp = str_date.split("/");
	var sel_date = new Date(tmp[2], tmp[1]-1, tmp[0]);
	
	if (type == "in") {
		// cannot be the past
		if (matrix.beginDate) {
			var chk = strToDate(matrix.beginDate);
		} else {
			var d = new Date();
			var chk = new Date(d.getFullYear(), d.getMonth(), d.getDate());
		}
	
		if (sel_date < chk) {
			sel_date = chk;
			$('mtx_check_in').value = padZero(sel_date.getDate()) + "/" + padZero(sel_date.getMonth()+1) + "/" + sel_date.getFullYear();
		}
	} else if (type == "out") {
		// cannot before check_in
		var chk_in = strToDateObj($('mtx_check_in').value);
		chk_in.setDate(chk_in.getDate()+1);
		if (sel_date < chk_in) {
			sel_date = chk_in;
			$('mtx_check_out').value = padZero(sel_date.getDate()) + "/" + padZero(sel_date.getMonth()+1) + "/" + sel_date.getFullYear();
		}
	}
	
	var tmp_date = new Date(sel_date.getFullYear(), sel_date.getMonth(), 1);
	output.options.length = 0;
	var i = 0;
	do {
		var o = dateToOption(tmp_date, "date")			
		if (tmp_date.getDate() == sel_date.getDate()) {
			o.selected = "selected";
		}
		output.options[i] = o;
		
		i++;
		tmp_date.setDate(tmp_date.getDate()+1);
	}
	while (tmp_date.getMonth()==sel_date.getMonth());
	
	// set month select box
	var val = sel_date.getFullYear() + "" + sel_date.getMonth();
	//obj_month.value = val;
	updateMonthSelected(obj_month, val);
	
}

function strToDateObj(str) {
	// assume dd/mm/yyyy
	if (str == "") {
		var tmp = new Date();
		var d = new Date(tmp.getFullYear(), tmp.getMonth(), tmp.getDate());
		return d;
	}
	var tmp = str.split("/");
	var d = new Date(tmp[2], tmp[1]-1, tmp[0]);
	return d;
}

function initializeCalendar() {
	if (!$('check_in_date')) {
		// no calendar for current matrix
		return false;
	}
	var d = new Date();
	d.setDate(1);
	var end = d.getFullYear()+2;
	$('check_in_month').options.length = 0;
	$('check_out_month').options.length = 0;
	var i = 0;
	do {
		var str = d.toDateString();
		var tmp = str.split(" ");
		var txt = tmp[1] + ", " + tmp[3];
		var val = d.getFullYear() + "" + d.getMonth();
		
		var o = new Option(txt, val);
		if (i==0) {
			o.selected = "selected";
		}
		$('check_in_month').options[i] = o;
		var oo = new Option(txt, val);
		if (i==0) {
			oo.selected = "selected";
		}
		$('check_out_month').options[i] = oo;
		d.setMonth(d.getMonth()+1);
		i++;
	} while (d.getFullYear() < end);
	
	// date/month selection
	var d = new Date();
	var chk = new Date(d.getFullYear(), d.getMonth(), d.getDate());
	$('mtx_check_in').value = padZero(chk.getDate()) + "/" + padZero(chk.getMonth()+1) + "/" + chk.getFullYear();
	setDateSelect("in");
	
	chk.setDate(chk.getDate()+1);
	$('mtx_check_out').value = padZero(chk.getDate()) + "/" + padZero(chk.getMonth()+1) + "/" + chk.getFullYear();
	setDateSelect("out");
	
	// some calculation incase calendar object is inside some relative position element
	crossobj.top = "0px";
	crossobj.left = "0px";
	var pos_c = Element.cumulativeOffset(document.getElementById("calendar"));
	calOffsetCompensate.top = pos_c['top'];
	calOffsetCompensate.left = pos_c['left'];

}

function dateToOption(date, type) {
	var str = date.toDateString();
	var tmp = str.split(" ");
	if (type == "date") {
		var txt = tmp[0] + " " + tmp[2];
		var val = tmp[2];
		var o = new Option(txt, val);
		return o;
	}
}

function dateSelectChange(obj) {
	if (obj.id=="check_in_date" || obj.id=="check_in_month") {
		var ym = $('check_in_month').value;
		var y = parseInt(ym.substr(0, 4),10);
		var m = parseInt(ym.substr(4),10);
		var d = parseInt($('check_in_date').value,10);
		var chk_in = new Date(y, m, d);
		validateSelectedDate(chk_in, "in");
		$('mtx_check_in').value = padZero(chk_in.getDate()) + "/" + padZero(chk_in.getMonth()+1) + "/" + chk_in.getFullYear();
		updateMtxDate("in");
	} else if (obj.id=="check_out_date" || obj.id=="check_out_month") {
		var ym = $('check_out_month').value;
		var y = parseInt(ym.substr(0, 4),10);
		var m = parseInt(ym.substr(4),10);
		var d = parseInt($('check_out_date').value,10);
		var chk_out = new Date(y, m, d);
		validateSelectedDate(chk_out, "out");
		$('mtx_check_out').value = padZero(chk_out.getDate()) + "/" + padZero(chk_out.getMonth()+1) + "/" + chk_out.getFullYear();
		updateMtxDate("out");
	}
}

function validateSelectedDate(sel_date, type) {
	if (type == "in") {
		// cannot be the past
		var d = new Date();
		var chk = new Date(d.getFullYear(), d.getMonth(), d.getDate());
		if (sel_date < chk) {
			sel_date = chk;
			$('mtx_check_in').value = padZero(sel_date.getDate()) + "/" + padZero(sel_date.getMonth()+1) + "/" + sel_date.getFullYear();
		}
	} else if (type == "out") {
		// cannot before check_in
		var chk_in = strToDateObj($('mtx_check_in').value);
		chk_in.setDate(chk_in.getDate()+1);
		if (sel_date < chk_in) {
			sel_date = chk_in;
			$('mtx_check_out').value = padZero(sel_date.getDate()) + "/" + padZero(sel_date.getMonth()+1) + "/" + sel_date.getFullYear();
		}
	}

}

function loadPeriodMatrix() {
    var s_in = $('mtx_check_in').value.split("/");
    var checkIn = s_in[2] + s_in[1] + s_in[0];
    var s_out = $('mtx_check_out').value.split("/");
    var checkOut = s_out[2] + s_out[1] + s_out[0];
    
    matrix.checkIn = checkIn;
    matrix.checkOut = checkOut;
    matrix.showOnRequest = !$('mtx_filterOnRequest').checked;
    
    matrix.setCookie();
    
    matrix.loadCheckInMatrix(checkIn, checkOut);
}

function syncronizeCalendar() {
	if (!window.matrix) {
		return false;
	}
	
	if (!$('mtx_check_in')) {
		return false;
	}
	
	if (window.matrix.checkIn) {
		var s_in = window.matrix.checkIn;
		$('mtx_check_in').value = s_in.substr(6) + "/" + s_in.substr(4, 2) + "/" + s_in.substr(0,4);
		updateMtxDate("in");
	}
	
	if (window.matrix.checkOut) {
		var s_out = window.matrix.checkOut;
		$('mtx_check_out').value = s_out.substr(6) + "/" + s_out.substr(4, 2) + "/" + s_out.substr(0,4);
		updateMtxDate("out");
	}
}

function showMapIcon(target) {
	$(target).style.display = "";
}
;
	if((String(typeof(crossobj))=="undefined") && (hasShortType=="true")) {
		var fixedX = -1			// x position (-1 if to appear below control)
		var fixedY = -1			// y position (-1 if to appear below control)
		var startAt = 0			// 0 - sunday ; 1 - monday
		var showWeekNumber = 1		// 0 - don't show; 1 - show
		var showToday = 1			// 0 - don't show; 1 - show
		var imgDir = prefixpath + "images/calendar/" + imagelang + "/";

		var gotoString = "Go To Current Month"
		var todayString = "Today is"
		var weekString = "Wk"

		//var scrollLeftMessage = "Click to scroll to previous month. Hold mouse button to scroll automatically."
		//var scrollRightMessage = "Click to scroll to next month. Hold mouse button to scroll automatically."
		//var selectMonthMessage = "Click to select a month."
		//var selectYearMessage = "Click to select a year."

		var scrollLeftMessage = ""
		var scrollRightMessage = ""
		var selectMonthMessage = ""
		var selectYearMessage = ""

		var closeCalendarMessage = "Close"
		var selectDateMessage = "Select [date] as date." // do not replace [date], it will be replaced by date.

		var crossobj, crossMonthObj, crossYearObj
		var monthSelected, yearSelected, dateSelected
		var omonthSelected, oyearSelected, odateSelected
		var monthConstructed, yearConstructed, intervalID1, intervalID2, timeoutID1, timeoutID2
		var ctlToPlaceValue1, ctlToPlaceValue2, ctlNow, dateFormat
		var nStartingYear

		var bPageLoaded=false
		var ie=document.all
		var ie6=!window.XMLHttpRequest;
		var dom=document.getElementById

		var ns4=document.layers
		var today = new Date()
		var dateNow = today.getDate()
		var monthNow = today.getMonth()
		if (currlang == 'en') {
			var yearNow = today.getYear()
		} else {
			var yearNow = today.getYear() + 543;
		}
		var imgsrc = new Array("drop1.gif","drop1.gif","left1.gif","left1.gif","right1.gif","right1.gif")
		var img = new Array()
		var badDate = imgDir + "baddate.gif"

		var imgBL = imgDir + "cornerbotleft.gif"
		var imgBR = imgDir + "cornerbotright.gif"
		var imgTL = imgDir + "cornertopleft.gif"
		var imgTR = imgDir + "cornertopright.gif"

		var bShow = false;
		var iStart,iEnd
		var calOffsetCompensate = new Object;

		/* hides <select> and <applet> objects (for IE only) */
		function hideElement( elmID, overDiv )
		{
			
		  if( ie6 )
		  {
			for( i = 0; i < document.all.tags( elmID ).length; i++ )
			{
			  obj = document.all.tags( elmID )[i];
			  if( !obj || !obj.offsetParent )
			  {
				continue;
			  }

			    var pos = Element.cumulativeOffset(obj);
			    objTop  = pos['top'];
			    objLeft = pos['left'];
			    
			    // compensation incase calendar locate inside position relative parent
			    objTop -= calOffsetCompensate.top;
			    objLeft -= calOffsetCompensate.left;

			  objHeight = obj.offsetHeight - 5;
			  objWidth = obj.offsetWidth;

			var tmpoverDivoffsetTop = overDiv.offsetTop;
			if(LongIsinLayer || ShortIsinLayer) {
				aTag = document.getElementById(LayerParentCalendar);
				if(aTag!=null) tmpoverDivoffsetTop += aTag.offsetTop;
			}
			
			  if(( overDiv.offsetLeft + overDiv.offsetWidth ) <= objLeft );
			  else if(( tmpoverDivoffsetTop + overDiv.offsetHeight ) <= objTop );
			  else if( tmpoverDivoffsetTop >= ( objTop + objHeight ));
			  else if( overDiv.offsetLeft >= ( objLeft + objWidth ));
			  else
			  {
				obj.style.visibility = "hidden";
				obj.style.display = "none";
			  }
			  
			}
		  }
		}

		/*
		* unhides <select> and <applet> objects (for IE only)
		*/
		function showElement( elmID )
		{
			if( ie )
			{
				for( i = 0; i < document.all.tags( elmID ).length; i++ )
				{
					obj = document.all.tags( elmID )[i];

					if( !obj || !obj.offsetParent )
					{
						continue;
					}
					if(obj.style.visibility=="hidden" && obj.style.display=="none"){
						obj.style.visibility = "visible";
						obj.style.display = "";
					}
				}
			}
		}

		function HolidayRec (d, m, y, desc)
		{
			this.d = d
			this.m = m
			this.y = y
			this.desc = desc
		}


		var HolidaysCounter = 0
		var Holidays = new Array()

		function addHoliday (d, m, y, desc)
		{
			Holidays[HolidaysCounter++] = new HolidayRec ( d, m, y, desc )
		}


		if (dom)
		{
			for(i=0;i<imgsrc.length;i++)
			{
				img[i] = new Image
				img[i].src = imgDir + imgsrc[i]
			}
			mainLayer = "<div onclick='bShow=true' id='calendar' style='z-index:+999;position:absolute;visibility:hidden;'>"
			mainLayer += "	<table cellspacing=0 cellpadding=0 style='font-family:arial;font-size:11px;'>"
			mainLayer += "		<tr><td>"
			mainLayer += "			<table width=100% cellspacing=0 cellpadding=0 border=0>"
			mainLayer += "				<tr><td><img src="+imgTL+"></td>"
			mainLayer += "					<td bgcolor='#ffbf00' style='padding-top:3px;font-family:arial; font-size:11px;' width=158><font color='#ffffff'><B><span id='caption'></span></B></font></td>"
			mainLayer += "					<td align=right bgcolor='#ffbf00'>"
			mainLayer += "						<table id='closeButton' cellpadding=0 cellspacing=1 border=0 bgcolor=#f9d35f>"
			mainLayer += "							<tr><td><a href='javascript:hideCalendar()'onMouseOver='document.getElementById(\"closeButton\").className=\"whiteBorder\"' onMouseOut='document.getElementById(\"closeButton\").className=\"\"'><IMG SRC='"+imgDir+"close.gif' BORDER='0' ALT='"+closeCalendarMessage+"'></a></td></tr>"
			mainLayer += "						</table></td>"
			mainLayer += "					<td><img src="+imgTR+"></td></tr>"
			mainLayer += "			</table>"
			mainLayer += "		</td></tr>"
			mainLayer += "		<tr><td style='padding:0px' bgcolor=#ffffff>"
			mainLayer += "			<table width=100% cellspacing=0 cellpadding=0 border=0>"
			mainLayer += "				<tr><td width=1 bgcolor=#FFBF00></td><td><span id='calendarcontent'></span></td><td width=1 bgcolor=#FFBF00></td></tr>"
			mainLayer += "			</table>"
			mainLayer += "		</td></tr>"

			if (showToday==1)
				mainLayer += "<tr><td align=center><table width=100% cellspacing=0 cellpadding=0 border=0><tr><td rowspan=2 width=1><img src=" + imgBL + "></td><td align=center height=5 bgcolor=#FFFFFF><span id='lblToday' style='font-family:Verdana;font-size:9px;'></span></td><td rowspan=2><img src=" + imgBR + "></td></tr><tr><td bgcolor=#FFBF00 width=100% height=1></td></tr></table></td></tr>";

			mainLayer += "</table></div><div id='selectMonth' style='z-index:+999;position:absolute;visibility:hidden;'></div><div id='selectYear' style='z-index:+999;position:absolute;visibility:hidden;'></div>";

			document.write (mainLayer);
		}

		//var	monthName = new Array("January","February","March","April","May","June","July","August","September","October","November","December")

		var monthName = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")

		if (startAt==0)
		{
			dayName = new Array ("Sun","Mon","Tue","Wed","Thu","Fri","Sat")
		}
		else
		{
			dayName = new Array ("Mon","Tue","Wed","Thu","Fri","Sat","Sun")
		}
		var	styleAnchor="text-decoration:none;color:black;"

		// Set Border Color of Selected Date
		var	styleLightBorder="border-style:solid;border-width:1px;border-color:#a0a0a0;"
		//var	styleLightBorder="border-style:solid;border-width:1px;border-color:#FF0000;"

		function swapImage(srcImg, destImg){
			if (ie)	{ document.getElementById(srcImg).setAttribute("src",imgDir + destImg) }
		}

		function initca() {
			if (!ns4)
			{
				if (!ie) { yearNow += 1900	}

				crossobj=(dom)?document.getElementById("calendar").style : ie? document.all.calendar : document.calendar
				hideCalendar()
				crossMonthObj=(dom)?document.getElementById("selectMonth").style : ie? document.all.selectMonth	: document.selectMonth
				crossYearObj=(dom)?document.getElementById("selectYear").style : ie? document.all.selectYear : document.selectYear

				monthConstructed=false;
				yearConstructed=false;

				if (showToday==1)
				{
					//document.getElementById("lblToday").innerHTML =	todayString + " <a onmousemove='window.status=\""+gotoString+"\"' onmouseout='window.status=\"\"' title='"+gotoString+"' style='"+styleAnchor+"' href='javascript:monthSelected=monthNow;yearSelected=yearNow;constructCalendar();'>"+dayName[(today.getDay()-startAt==-1)?6:(today.getDay()-startAt)]+", " + dateNow + " " + monthName[monthNow].substring(0,3)	+ "	" +	yearNow	+ "</a>"
				}

				
				sHTML1="<span id='spanLeft' style='border-style:solid;border-width:1;border-color:#f9d35f;cursor:pointer' onmouseover='swapImage(\"changeLeft\",\"left1.gif\");this.style.borderColor=\"#FFFFFF\";window.status=\""+scrollLeftMessage+"\"' onclick=\"javascript:decMonth()\" onmouseout='clearInterval(intervalID1);swapImage(\"changeLeft\",\"left1.gif\");this.style.borderColor=\"#f9d35f\";window.status=\"\"' onmousedown='clearTimeout(timeoutID1);timeoutID1=setTimeout(\"StartDecMonth()\",500)'onmouseup='clearTimeout(timeoutID1);clearInterval(intervalID1)'>&nbsp<IMG align=middle id='changeLeft' SRC='"+imgDir+"left1.gif' width=10 height=11 BORDER=0>&nbsp</span>&nbsp;"
				sHTML1+="<span id='spanRight' style='border-style:solid;border-width:1;border-color:#f9d35f;cursor:pointer' onmouseover='swapImage(\"changeRight\",\"right1.gif\");this.style.borderColor=\"#FFFFFF\";window.status=\""+scrollRightMessage+"\"' onmouseout='clearInterval(intervalID1);swapImage(\"changeRight\",\"right1.gif\");this.style.borderColor=\"#f9d35f\";window.status=\"\"' onclick='incMonth()' onmousedown='clearTimeout(timeoutID1);timeoutID1=setTimeout(\"StartIncMonth()\",500)'onmouseup='clearTimeout(timeoutID1);clearInterval(intervalID1)'>&nbsp<IMG align=middle id='changeRight' SRC='"+imgDir+"right1.gif'	width=10 height=11 BORDER=0>&nbsp</span>&nbsp"
				
				sHTML1+="<span id='spanMonth' style='border-style:solid;border-width:1;border-color:#f9d35f;cursor:pointer' onmouseover='swapImage(\"changeMonth\",\"drop1.gif\");this.style.borderColor=\"#FFFFFF\";window.status=\""+selectMonthMessage+"\"' onmouseout='swapImage(\"changeMonth\",\"drop1.gif\");this.style.borderColor=\"#f9d35f\";window.status=\"\"' onclick='popUpMonth()'></span>&nbsp;"
				sHTML1+="<span id='spanYear' style='border-style:solid;border-width:1;border-color:#f9d35f;cursor:pointer' onmouseover='swapImage(\"changeYear\",\"drop2.gif\");this.style.borderColor=\"#FFFFFF\";window.status=\""+selectYearMessage+"\"' onmouseout='swapImage(\"changeYear\",\"drop1.gif\");this.style.borderColor=\"#f9d35f\";window.status=\"\"'	onclick='popUpYear()'></span>"
				//sHTML1+="<span id='spanYear' style='display: none; border-style:solid;border-width:1;border-color:#f9d35f;cursor:pointer' onmouseover='swapImage(\"changeYear\",\"drop2.gif\");this.style.borderColor=\"#FFFFFF\";window.status=\""+selectYearMessage+"\"' onmouseout='swapImage(\"changeYear\",\"drop1.gif\");this.style.borderColor=\"#f9d35f\";window.status=\"\"'	onclick='popUpYear()'></span>&nbsp;"

				document.getElementById("caption").innerHTML  =	sHTML1

				bPageLoaded=true;
			}
			checkhasOtherCalendar();
		}

		var LongIsinLayer = false;
		var ShortIsinLayer = false;
		var LayerParentCalendar = "";

		function checkhasOtherCalendar() {
			if(LongIsinLayer==false) {
				for( j = 0; j < document.getElementsByTagName('div').length; j++ )
				{
					divtmp = document.getElementsByTagName('div')[j];
					
					for( i = 0; i < divtmp.getElementsByTagName('img').length; i++ )
					{
						obj = divtmp.getElementsByTagName('img')[i];
						if(obj.src.indexOf('images/calendar/calendar.gif')!=-1)
						{
							if(String(obj.onclick).indexOf('opencalendar')!=-1)
							{
								LongIsinLayer = true;
								ShortIsinLayer = true;
								LayerParentCalendar = divtmp.id;
								break;
							}
						}
					}
				}
			}
		}

		function hideCalendar()	{
			try
			{
				crossobj.visibility="hidden";
				if (crossMonthObj != null){crossMonthObj.visibility="hidden"}
				if (crossYearObj !=	null){crossYearObj.visibility="hidden"}

				showElement( 'SELECT' );
				showElement( 'APPLET' );
			} catch(e) {}
		}

		function padZero(num) {
			return (num< 10) ? '0' + num : num ;
		}

		function constructDate(d,m,y)
		{
			sTmp = dateFormat
			sTmp = sTmp.replace	("dd","<e>")
			sTmp = sTmp.replace	("d","<d>")
			sTmp = sTmp.replace	("<e>",padZero(d))
			sTmp = sTmp.replace	("<d>",d)
			sTmp = sTmp.replace	("mmm","<o>")
			sTmp = sTmp.replace	("mm","<n>")
			sTmp = sTmp.replace	("m","<m>")
			sTmp = sTmp.replace	("<m>",m+1)
			sTmp = sTmp.replace	("<n>",padZero(m+1))
			sTmp = sTmp.replace	("<o>",monthName[m])
			return sTmp.replace ("yyyy",y)
		}

		var eventdoonclose;
		function closeCalendar() {
			var	sTmp
			hideCalendar();
			if (yearSelected>(yearNow+prefixyearAddOn))
			{
				var strvar = padZero(dateNow) + "/" + (padZero(monthNow+1)) + "/" + yearNow;
			} 
			else 
			{
				var strvar = padZero(dateSelected) + "/" + (padZero(monthSelected+1)) + "/" + yearSelected;
			}
			ctlToPlaceValue1.value = strvar;
			eval(eventdoonclose);
		}

		/*** Year Pulldown	***/
		function StartDecYear()
		{
			intervalID2=setInterval("decYear()",80)
		}

		function StartIncYear()
		{
			intervalID2=setInterval("incYear()",80)
		}

		/*** Month Pulldown	***/
		function StartDecMonth()
		{
			intervalID1=setInterval("decMonth()",80)
		}

		function StartIncMonth()
		{
			intervalID1=setInterval("incMonth()",80)
		}

		function incMonth () {
			monthSelected++
			if (monthSelected>11) {
				monthSelected=0
				yearSelected++
			}
			constructCalendar()
		}

		function decMonth () {
			monthSelected--
			if (monthSelected<0) {
				monthSelected=11
				yearSelected--
			}
			constructCalendar()
		}

		function constructMonth() {
			popDownYear()
		//	if (!monthConstructed) {
				sHTML =	""
				iStart=0;
				iEnd=12;

					//iStart=monthNow;
					j=0;
					k=0;

					for(i=iStart; i<iEnd; i++) {
						j = i;
						sName =	monthName[j];
						//j = i + monthNow
						//if (j<=11) {
						//	k = yearNow
						//	sName =	monthName[j];
						//} else {
						//	j = j-12
						//	k = yearNow + 1
						//	sName =	monthName[j];
						//}
						k = yearSelected;
						if (i==monthSelected){
							sName =	"<B>" +	sName +	"</B>"
						}
						sHTML += "<tr><td id='m" + j + "/" + k + "' onmouseover='this.style.backgroundColor=\"#FFBF00\"' onmouseout='this.style.backgroundColor=\"\"' style='cursor:pointer' onclick='monthConstructed=false;monthSelected=" + j + ";yearSelected="+k+";constructCalendar();popDownMonth();event.cancelBubble=true' class=calendar10>&nbsp;" + sName + " &nbsp;</td></tr>"
					}

				document.getElementById("selectMonth").innerHTML = "<table width=43 style='font-family:arial; font-size:11px; border-width:1; border-style:solid; border-color:#FFBF00;' bgcolor='#FFFFDD' cellspacing=0 onmouseover='clearTimeout(timeoutID1)'	onmouseout='clearTimeout(timeoutID1);timeoutID1=setTimeout(\"popDownMonth()\",100);event.cancelBubble=true'>" +	sHTML +	"</table>"

				monthConstructed=true
		//	}
		}

		function popUpMonth() {
			constructMonth()
			crossMonthObj.visibility = (dom||ie)? "visible"	: "show"
			var oldcrossMonthObjleft = crossMonthObj.left;
			crossMonthObj.left = parseInt(crossobj.left) + 50
			if(crossMonthObj.left==oldcrossMonthObjleft) crossMonthObj.left = (parseInt(crossobj.left) + 50) +'px';
			var oldcrossMonthObjtop= crossMonthObj.top;
			crossMonthObj.top = parseInt(crossobj.top) + 26
			if(crossMonthObj.top==oldcrossMonthObjtop) crossMonthObj.top = (parseInt(crossobj.top) + 26) +'px';

			hideElement( 'SELECT', document.getElementById("selectMonth") );
			hideElement( 'APPLET', document.getElementById("selectMonth") );
		}

		function popDownMonth()	{
			crossMonthObj.visibility= "hidden"
		}

		/*** Year Pulldown ***/

		function incYear() {
			for	(i=0; i<7; i++){
				newYear	= (i+nStartingYear)+1
				if (newYear==yearSelected)
				{ txtYear =	"&nbsp;<B>"	+ newYear +	"</B>&nbsp;" }
				else
				{ txtYear =	"&nbsp;" + newYear + "&nbsp;" }
				document.getElementById("y"+i).innerHTML = txtYear
			}
			nStartingYear ++;
			bShow=true
		}

		function decYear() {
			for	(i=0; i<7; i++){
				newYear	= (i+nStartingYear)-1
				if (newYear==yearSelected)
				{ txtYear =	"&nbsp;<B>"	+ newYear +	"</B>&nbsp;" }
				else
				{ txtYear =	"&nbsp;" + newYear + "&nbsp;" }
				document.getElementById("y"+i).innerHTML = txtYear
			}
			nStartingYear --;
			bShow=true
		}

		function selectYear(nYear) {
			yearSelected=parseInt(nYear+nStartingYear);
			yearConstructed=false;
			constructMonth();
			constructCalendar();
			popDownYear();
		}

		function constructYear() {
			popDownMonth()
			sHTML =	""
			if (!yearConstructed) {
				sHTML =	"";
				//sHTML =	"<tr><td align='center'	onmouseover='this.style.backgroundColor=\"#FFBF00\"' onmouseout='clearInterval(intervalID1);this.style.backgroundColor=\"\"' style='cursor:pointer'	onmousedown='clearInterval(intervalID1);intervalID1=setInterval(\"decYear()\",30)' onmouseup='clearInterval(intervalID1)'>-</td></tr>"

				j =	0
				nStartingYear =	yearNow;
				for	(i=(yearNow); i<=(yearNow+prefixyearAddOn); i++) {
					sName =	i;
					if (i==yearSelected){
						sName =	"<B>" +	sName +	"</B>"
					}

					sHTML += "<tr><td id='y" + j + "' onmouseover='this.style.backgroundColor=\"#FFBF00\"' onmouseout='this.style.backgroundColor=\"\"' style='cursor:pointer' onclick='selectYear("+j+");event.cancelBubble=true' class=calendar10>&nbsp;" + sName + "&nbsp;</td></tr>"
					j ++;
				}

				//sHTML += "<tr><td align='center' onmouseover='this.style.backgroundColor=\"#FFCC99\"' onmouseout='clearInterval(intervalID2);this.style.backgroundColor=\"\"' style='cursor:pointer' onmousedown='clearInterval(intervalID2);intervalID2=setInterval(\"incYear()\",30)'	onmouseup='clearInterval(intervalID2)'>+</td></tr>"

				document.getElementById("selectYear").innerHTML	= "<table width=47 style='font-family:arial; font-size:11px; border-width:1; border-style:solid; border-color:#FFBF00;'	bgcolor='#FFFFDD' onmouseover='clearTimeout(timeoutID2)' onmouseout='clearTimeout(timeoutID2);timeoutID2=setTimeout(\"popDownYear()\",100)' cellspacing=0>"	+ sHTML	+ "</table>"

				yearConstructed	= true
			}
		}

		function popDownYear() {
			clearInterval(intervalID1)
			clearTimeout(timeoutID1)
			clearInterval(intervalID2)
			clearTimeout(timeoutID2)
			crossYearObj.visibility= "hidden"
		}

		function popUpYear() {
			var	leftOffset

			constructYear()

			crossYearObj.visibility	= (dom||ie)? "visible" : "show"
			leftOffset = parseInt(crossobj.left) + document.getElementById("spanYear").offsetLeft
			if (ie)
			{
				leftOffset += 6
			}
			var oldcrossYearObjleft = crossYearObj.left;
			crossYearObj.left =	leftOffset
			if(crossYearObj.left==oldcrossYearObjleft) crossYearObj.left = leftOffset +'px';
			var oldcrossYearObjtop = crossYearObj.top;
			crossYearObj.top = parseInt(crossobj.top) +	26
			if(crossYearObj.top==oldcrossYearObjtop) crossYearObj.top = (parseInt(crossobj.top) +	26) +'px';
		}

		/*** calendar ***/
	   function WeekNbr(n) {
		  // Algorithm used:
		  // From Klaus Tondering's Calendar document (The Authority/Guru)
		  // hhtp://www.tondering.dk/claus/calendar.html
		  // a = (14-month) / 12
		  // y = year + 4800 - a
		  // m = month + 12a - 3
		  // J = day + (153m + 2) / 5 + 365y + y / 4 - y / 100 + y / 400 - 32045
		  // d4 = (J + 31741 - (J mod 7)) mod 146097 mod 36524 mod 1461
		  // L = d4 / 1460
		  // d1 = ((d4 - L) mod 365) + L
		  // WeekNumber = d1 / 7 + 1

		  year = n.getFullYear();
		  month = n.getMonth() + 1;
		  if (startAt == 0) {
			 day = n.getDate() + 1;
		  }
		  else {
			 day = n.getDate();
		  }

		  a = Math.floor((14-month) / 12);
		  y = year + 4800 - a;
		  m = month + 12 * a - 3;
		  b = Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400);
		  J = day + Math.floor((153 * m + 2) / 5) + 365 * y + b - 32045;
		  d4 = (((J + 31741 - (J % 7)) % 146097) % 36524) % 1461;
		  L = Math.floor(d4 / 1460);
		  d1 = ((d4 - L) % 365) + L;
		  week = Math.floor(d1/7) + 1;

		  return week;
	   }

		function constructCalendar () {
			if (currlang == 'th') {
				tmpyearSelected = yearSelected - 543;
			} else {
				tmpyearSelected = yearSelected;
			}
			
			//if ((monthSelected<monthNow) && (yearSelected==yearNow)) yearSelected = yearSelected+1;	//For dropdown changed

			var aNumDays = Array (31,0,31,30,31,30,31,31,30,31,30,31)
			var dateMessage
			var startDate =	new Date (tmpyearSelected,monthSelected,1)
			var endDate

			if (monthSelected==1)
			{
				endDate	= new Date (tmpyearSelected,monthSelected+1,1);
				endDate	= new Date (endDate - (24*60*60*1000));
				numDaysInMonth = endDate.getDate()
			}
			else
			{
				numDaysInMonth = aNumDays[monthSelected];
			}

			datePointer	= 0
			dayPointer = startDate.getDay() - startAt

			if (dayPointer<0)
			{
				dayPointer = 6
			}

			sHTML =	"<table	 border=0 style='font-family:verdana;font-size:10px;'><tr>"

			//if (showWeekNumber==1)
			//{
			//	sHTML += "<td width=27><b>" + weekString + "</b></td><td width=1 rowspan=7 bgcolor='#d0d0d0' style='padding:0px'><img src='"+imgDir+"divider.gif' width=1></td>"
			//}

			for	(i=0; i<7; i++)	{
				//sHTML += "<td width='' align='right'><B>"+ dayName[i]+"</B></td>"
				sHTML += "<td width='' align='center' class=calendar10>"+ dayName[i]+"</td>"
			}
			sHTML +="</tr><tr>"

			//if (showWeekNumber==1)
		//	{
		//		sHTML += "<td align=right>" + WeekNbr(startDate) + "&nbsp;</td>"
		//	}

			for	( var i=1; i<=dayPointer;i++ )
			{
				sHTML += "<td class=calendar10>&nbsp;</td>"
			}

			for	( datePointer=1; datePointer<=numDaysInMonth; datePointer++ )
			{
				dayPointer++;
				//sHTML += "<td align=right>"
				sStyle=styleAnchor
				if ((datePointer==odateSelected) && (monthSelected==omonthSelected) && (yearSelected==oyearSelected))
				{ sStyle+=styleLightBorder }

				sHint = ""
				for (k=0;k<HolidaysCounter;k++)
				{
					if ((parseInt(Holidays[k].d)==datePointer)&&(parseInt(Holidays[k].m)==(monthSelected+1)))
					{
						if ((parseInt(Holidays[k].y)==0)||((parseInt(Holidays[k].y)==yearSelected)&&(parseInt(Holidays[k].y)!=0)))
						{
							sStyle+="background-color:#FFDDDD;"
							sHint+=sHint==""?Holidays[k].desc:"\n"+Holidays[k].desc
						}
					}
				}

				var regexp= /\"/g
				sHint=sHint.replace(regexp,"&quot;")
				dateMessage = "onmousemove='window.status=\""+selectDateMessage.replace("[date]",constructDate(datePointer,monthSelected,yearSelected))+"\"' onmouseout='window.status=\"\"' "

				currentDate = new Date(yearSelected, monthSelected, datePointer)
				//alert(currentDate)

				if ((datePointer==dateNow)&&(monthSelected==monthNow)&&(yearSelected==yearNow))
				{
					sHTML += "<td align=right class=calendar10>"
					sHTML += "<b><a "+dateMessage+" title=\"" + sHint + "\" style='"+sStyle+"' href='javascript:dateSelected="+datePointer+";closeCalendar();'><font color=#ff0000>&nbsp;" + datePointer + "</font>&nbsp;</a></b>"}
				//else if	(dayPointer % 7 == (startAt * -1)+1)
				//{
				//	sHTML += "<td align=right>"
				//	sHTML += "<a "+dateMessage+" title=\"" + sHint + "\" style='"+sStyle+"' href='javascript:dateSelected="+datePointer + ";closeCalendar();'>&nbsp;<font color=#909090>" + datePointer + "</font>&nbsp;</a>" }
				else
				{
					sHTML += "<td align=right class=calendar10>"
					sHTML += "<a "+dateMessage+" title=\"" + sHint + "\" style='"+sStyle+"' href='javascript:dateSelected="+datePointer + ";closeCalendar();'>&nbsp;" + datePointer + "&nbsp;</a>" }

				sHTML += ""
				if ((dayPointer+startAt) % 7 == startAt) {
					sHTML += "</tr><tr>"
				//	if ((showWeekNumber==1)&&(datePointer<numDaysInMonth))
				//	{
				//		sHTML += "<td align=right>" + (WeekNbr(new Date(yearSelected,monthSelected,datePointer+1))) + "&nbsp;</td>"
				//	}
				}
			}

			document.getElementById("calendarcontent").innerHTML   = sHTML
			document.getElementById("spanMonth").innerHTML = "&nbsp;" + monthName[monthSelected] + " &nbsp;<IMG id='changeMonth' SRC='"+imgDir+"drop1.gif' WIDTH='12' HEIGHT='10' BORDER=0>"
			document.getElementById("spanYear").innerHTML =	"&nbsp;" + yearSelected	+ "&nbsp;<IMG id='changeYear' SRC='"+imgDir+"drop1.gif' WIDTH='12' HEIGHT='10' BORDER=0>"
		}

		var prefixyearAddOn = 1;
		function prefixYear(values) {
			if(values==1) {
				prefixyearAddOn = 1;
			} else {
				var tnow = new Date();
				var sMonth = tnow.getMonth() + 1;
				var sYear = tnow.getFullYear();
				var sYearOld = sYear;
				var i;
				for(i=0;i<=17;i++) {
					sMonth = sMonth + 1;
					if(sMonth>=13) {
						sMonth = 1;
						sYear = sYear + 1;
					}
				}
				prefixyearAddOn = sYear - sYearOld;
			}
		}

		function opencalendar(format,ctl1,ctl2, eventonclose, FixShowX, FixShowY) {
			if (typeof(ctl1) == "string") {
				ctl1 = $(ctl1);
			}
			eventdoonclose = eventonclose;
			hideCalendar();
			var	leftpos=0
			var	toppos=0
			if (bPageLoaded)
			{
				if ( crossobj.visibility ==	"hidden" ) {
					ctlToPlaceValue1	= ctl1
					ctlToPlaceValue2	= ctl2
					dateFormat=format;

					formatChar = "/"
					aFormat	= dateFormat.split(formatChar)

					tokensChanged =	0
					if ( formatChar	!= "" )
					{
						var strDate
						if (ctl1.value == "")
						{
							strDate = padZero(dateNow) + "/" + padZero(monthNow + 1) + "/" + yearNow;
						}
						else
						{
							strDate = ctl1.value;
						}

						aData =	strDate.split(formatChar)

						for	(i=0;i<3;i++)
						{
							if ((aFormat[i]=="d") || (aFormat[i]=="dd"))
							{
								dateSelected = parseInt(aData[i], 10)
								tokensChanged ++
							}
							else if	((aFormat[i]=="m") || (aFormat[i]=="mm"))
							{
								monthSelected =	parseInt(aData[i], 10) - 1
								tokensChanged ++
							}
							else if	(aFormat[i]=="yyyy")
							{
								yearSelected = parseInt(aData[i], 10)
								tokensChanged ++
							}
							else if	(aFormat[i]=="mmm")
							{
								for	(j=0; j<12;	j++)
								{
									if (aData[i]==monthName[j])
									{
										monthSelected=j
										tokensChanged ++
									}
								}
							}
						}
					}

					if ((tokensChanged!=3)||isNaN(dateSelected)||isNaN(monthSelected)||isNaN(yearSelected))
					{
						dateSelected = dateNow
						monthSelected =	monthNow
						yearSelected = yearNow
					}

					odateSelected=dateSelected;
					omonthSelected=monthSelected;
					oyearSelected=yearSelected;

				    var pos = Element.cumulativeOffset(ctl2);
				    toppos  = pos['top'];
					leftpos = pos['left'];
					
					toppos  = ctl2.clientHeight + toppos;
					//leftpos = leftpos - document.getElementById("calendar").clientWidth;
					
					toppos -= calOffsetCompensate.top;
					leftpos -= calOffsetCompensate.left;
					
					if(leftpos<0) leftpos = 0;

					var oldcrossobjleft = crossobj.left;
					crossobj.left =	leftpos + 'px';
					
					var oldcrossobjtop = crossobj.top;
					crossobj.top = (toppos + 5) + 'px';

					constructCalendar(1, monthSelected, yearSelected);
					crossobj.visibility = (dom||ie) ? "visible" : "show"

					hideElement('SELECT', document.getElementById("calendar"));
					hideElement('APPLET', document.getElementById("calendar"));

					bShow = true;
				}
				else
				{
					hideCalendar();
					if (ctlNow!=ctl1) {opencalendar(format,ctl1,ctl2, eventonclose, FixShowX, FixShowY)}
				}
				ctlNow = ctl1;
			}
		}

		var _is_ie=document.all;
		var _have_dom=document.getElementById;
		function getEl(tmpname) {
			var a = (_have_dom)?document.getElementById(tmpname) : _is_ie? eval("document.all."+tmpname)	: eval("document."+tmpname)
			return a;
		}

		function checkattach(wevent,names,wfunc) {
			var oldonload;
			oldonload = eval(wevent);
			if(typeof(oldonload)=='function') {
				eval(wevent+'=function '+names+'() {oldonload();'+wfunc+'();}');
			} else {
				eval(wevent+'=function '+names+'() {'+wfunc+'();}');
			}
		}

		function hidecal1 () {
//			try {
//				if (event.keyCode==27)
//				{
//					hideCalendar()
//				}				
//			} catch(e) {}
		}

		checkattach("document.onkeypress","addhideandonkeypress","hidecal1");

		function hidecal2 () {
			if (!bShow)
			{
				hideCalendar()
			}
			bShow = false
		}

		checkattach("document.onclick","addhideandonclick","hidecal2");

		//checkattach("window.onload","onloadinitca","initca");
		initca();
	}
;

/*
ajax section
*/
var tmpDivNaMe = 'divAwdAjaxArea';
if(!document.getElementById(tmpDivNaMe)) document.write('<div id="' + tmpDivNaMe + '" name="' + tmpDivNaMe + '"></div>');

var objAjax = null;
var awdTasks;

function initTasks()
{
	if(!awdTasks) awdTasks = new class_awdTasks();
}

function class_awdTasks()
{
	this.initParam = function()
	{
		this.arrRequests = new Array();
		this.arrFrames = new Array();
	}
	this.setRequest = function(id, obj)
	{
		this.arrRequests[id] = obj;
	}
	this.getRequest = function(id)
	{
		return this.arrRequests[id];
	}
	this.makeRequest = function()
	{
		var obj = (arguments[0]) ? arguments[0] : new Object();
		return new Number(this.arrRequests.push(obj) - 1);
	}
	this.deleteRequest = function(id)
	{
		delete(this.arrRequests[id]);
	}
	this.setFrame = function(id, obj) {
		this.arrFrames[id] = obj;
	}
	this.getFrame = function(id) {
		return this.arrFrames[id];
	}
	this.deleteFrame = function(id)
	{
		delete(this.arrFrames[id]);
	}
	this.ready = function(id)
	{
		var tmpReq = this.getRequest(id);
		var tmpHTML = (arguments[1]) ? arguments[1] : "XxX";
		if(tmpHTML=="XxX") {
			if(tmpReq.callback instanceof Function) {
				tmpReq.callback(tmpReq.argsend, tmpHTML);
			}
			var tmpdom = new awdElement();
			if(document.getElementById(tmpdom.awdPrefix + id)) tmpdom.removeElement(document.getElementById(tmpdom.awdPrefix + id));
			this.deleteRequest(id);
		} else {
			if(tmpReq.typerun=='javascript') eval(tmpHTML);
			else {
				if(tmpReq.callback instanceof Function) {
					tmpReq.callback(tmpReq.argsend, tmpHTML);
				}
				var tmpdom = new awdElement();
				if(document.getElementById(tmpdom.awdPrefix + id)) tmpdom.removeElement(document.getElementById(tmpdom.awdPrefix + id));
				this.deleteRequest(id);
			}
		}
	}
	this.initParam();
}

function awdElement() 
{
	this.reqId = 'awdAjaxRequestId_';
	this.awdPrefix = 'awdGroup_';
	this.awdPrefixChild = 'divAwdAjaxChild';
	this.removeElement = function(elementName)
	{
		elementName.parentNode.removeChild(elementName);
	}
  
	this.createElement = function(parentElement, elementType, needProperty)
	{
		var textShow = (arguments[3]) ? arguments[3] : "";
		var el = document.createElement(elementType);
		var i = 0;
		for (var k in needProperty) 
		{
			var keys = k.split('.');
			if (keys.length == 2) el[keys[0]][keys[1]] = needProperty[k];
			else el[k] = needProperty[k];
		}
		try {el.innerHTML = textShow;} catch(e) {}
		parentElement.appendChild(el);
		return el;
	}

	this.createScript = function( parentElement, id, url )
	{ 
		var span = this.createElement( parentElement, 'span', {'style.display' : 'none', 'id' : id}, '%<scr' + 'ipt></' + 'script>' );
		setTimeout(
			function() {
				var tmpscript  = span.getElementsByTagName('script')[0];
				tmpscript.type = 'text/javascript';
				tmpscript.src  = url;
			}, 5
		);
	}
}

function awdAjax()
{
	this.awdError = function(errMessage, url, errLine)
	{
		var tmpFile = "File : " + url + " Line : " + errLine + "\n";
		var tmperror = "Error : " + errMessage + "\n" + tmpFile;
		if(errMessage.indexOf('unterminated string literal') + errMessage.indexOf('missing ;') + errMessage.indexOf('Syntax error') > -3) 
		{}
		alert(tmperror);
	}

	this.initParam = function()
	{
		this.setError = true;
		if(this.setError) window.onerror = this.awdError;

		this.dom = new awdElement();
		if(document.getElementById(tmpDivNaMe)) this.divAwdAjax = document.getElementById(tmpDivNaMe);
		else this.divAwdAjax = this.dom.createElement(document.body, 'div', {'id' : tmpDivNaMe});
	}

	this.validateFunction = function(callback)
	{
		if(callback && callback instanceof Function) return callback;
		else return function() {};
	}

	this.exec_query = function(url, callback)
	{
		var tmpid = (arguments[2]) ? arguments[2] : awdTasks.makeRequest();
		var methodToUse = (arguments[3]) ? arguments[3].toUpperCase() : "GET";
		var multiThread = (arguments[4]) ? arguments[4] : false;
		var argsend = (arguments[5]) ? arguments[5] : new Array();
		var typerun = (arguments[6]) ? arguments[6] : "html";
		var domuse = (arguments[7]) ? arguments[7] : "xml";
		callback = this.validateFunction(callback);
		var xmldom;
		try {xmldom = new XMLHttpRequest();} catch(e) {
			try {xmldom = new ActiveXObject('Msxml2.XMLHTTP');} catch(e) {
				try {xmldom = new ActiveXObject('Microsoft.XMLHTTP');} catch(e) {
					xmldom = null;
				}
			}
		}
		if((domuse=="xml") && (xmldom==null)) domuse = "awd";

		if(methodToUse=="GET") {
			awdTasks.setRequest(tmpid, {'callback' : callback, 'request' : url, 'argsend' : argsend, 'typerun' : typerun});
			(url.indexOf('?')==-1) ? url = url + '?' : url = url + '&';
			url = url + this.dom.reqId + '=' + tmpid;
		} else 

		if(methodToUse=="POST") {
			var variablesend = "";
			var tmpindexOf = url.indexOf("?");
			if(tmpindexOf!=-1) {
				variablesend = url.substring(tmpindexOf+1, url.length);
				url = url.substring(0, tmpindexOf);
			}
			if(variablesend=="") variablesend = this.dom.reqId + '=' + tmpid;
			else variablesend = variablesend + '&' + this.dom.reqId + '=' + tmpid;

			awdTasks.setRequest(tmpid, {'callback' : callback, 'request' : url, 'argsend' : argsend, 'typerun' : typerun});
		}

		if(domuse=="awd") {
			if(methodToUse=="GET") {
				//response must to be only javascript
				this.dom.createScript(this.divAwdAjax, this.dom.awdPrefix + tmpid, url);
			} else 
			
			if(methodToUse=="POST") {
				//response can be html or javascript upon callback function to process
				//html
				//   callback(HTML, typerun) {alert(HTML);}
				//javascript
				//   callback(HTML, typerun) {eval(HTML);}
				return this.frame({'url' : url, 'onComplete' : callback, 'argsend' : argsend, 'typerun' : typerun}, tmpid, multiThread);
			}
		} else {
			if(methodToUse=="GET") {
				xmldom.open(methodToUse, url, true);
				xmldom.onreadystatechange = function() {
					if(xmldom.readyState==4) {
						awdTasks.ready(tmpid, xmldom.responseText);
					}
				}
				xmldom.send(null);
			} else 
			
			if(methodToUse=="POST") {
				xmldom.open(methodToUse, url, false);
				xmldom.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
				xmldom.send(variablesend);
				awdTasks.ready(tmpid, xmldom.responseText);
			}
		}
	}

	this.ready = function(id)
	{
		var tmpReq = awdTasks.getRequest(id);
		if(tmpReq.callback instanceof Function) tmpReq.callback(tmpReq.argsend);
		if(document.getElementById(this.dom.awdPrefix + id)) this.dom.removeElement(document.getElementById(this.dom.awdPrefix + id));
		awdTasks.deleteRequest(id);
	}

	this.frame = function(c) 
	{
		var tmpid = (arguments[1]) ? arguments[1] : awdTasks.makeRequest();
		var multiThread = (arguments[2]) ? arguments[2] : false;
		var url = (c && c.url) ? c.url : "about:blank";
		var objRunResponse;
		objRunResponse = (multiThread) ? "awdTasks.getFrame("+tmpid+")" : "objAjax";
		var n = 'ifrmAwdAjax' + tmpid;
		var d = this.dom.createElement(this.divAwdAjax, 'div', {'id' : this.dom.awdPrefixChild+tmpid}, '<iframe style="display:;" height="100" width="100" src="" id="'+n+'" name="'+n+'" onload="'+objRunResponse+'.loaded(\''+n+'\', '+tmpid+')"></iframe>' );
		var i = document.getElementById(n);
		i.src = url;
		if(c && typeof(c.onComplete) == 'function') i.onComplete = c.onComplete;
		if(c && typeof(c.argsend) == 'object') i.argsend = c.argsend;
		return n;
	}
  
	this.form = function(f, name) {
		f.setAttribute('target', name);
	}
  
	this.submit = function(f, c) {
		var tmpid = (arguments[2]) ? arguments[2] : awdTasks.makeRequest();
		this.form(f, this.frame(c, tmpid, true));
		if(c && c.onStart instanceof Function) return c.onStart(f);
		else return true;
	}

	this.loaded = function(id, tmpid) {
        var i = document.getElementById(id);
        if (i.contentDocument) var d = i.contentDocument;
        else if (i.contentWindow) var d = i.contentWindow.document;
        else var d = window.frames[id].document;
        if (d.location.href == "about:blank") return;

		//if(i.onComplete instanceof Function) i.onComplete(i.argsend, d.body.innerHTML);
		awdTasks.ready(tmpid, d.body.innerHTML);
		if(i) {
			this.dom.removeElement(document.getElementById(this.dom.awdPrefixChild+tmpid));
			//awdTasks.deleteRequest(tmpid);
			awdTasks.deleteFrame(tmpid);
		}
	}
	this.initParam();
}

/*
e.g.
runReturnFunction("sendparam.php?id=10", doneReturn);
function doneReturn(argsend, P) {
	//P is response look like text
	//should be use eval to compile text to command
	//e.g. P = "var Error = false;"
	eval(P);
	if(Error) alert("Has something error");
	else alert("OK");
}
function doneReturn(argsend, P) {   
	document.getElementById('r').innerHTML = P;
}
*/
function runAjax() {
	initTasks();
	var url = (arguments[0]) ? arguments[0] : null;
	var callback = (arguments[1]) ? arguments[1] : null;
	var multiThread = (arguments[2]) ? arguments[2] : false;
	var setMethod = (arguments[3]) ? arguments[3].toUpperCase() : "GET";
	var argsend = (arguments[4]) ? arguments[4] : new Array();
	var typerun = (arguments[5]) ? arguments[5] : "html";
	var domuse = (arguments[6]) ? arguments[6] : "xml";
	var tmparray = new Array();
	var tmpid = awdTasks.makeRequest();
	if(multiThread) {
		eval("var objAjax"+tmpid+" = new awdAjax();");
		eval("tmparray[0] = objAjax"+tmpid+";");
		awdTasks.setFrame(tmpid, tmparray[0]);
	} else {
		if(objAjax==null) objAjax = new awdAjax();
		tmparray[0] = objAjax;
	}
	tmparray[0].exec_query(url, callback, tmpid, setMethod, multiThread, argsend, typerun, domuse);
}

function runXmlReturnJavascript(url) {
	var callback = function() {}
	var multiThread = (arguments[1]) ? arguments[1] : false;
	var argsend = new Array();
	if(typeof(multiThread)=="object") {
		for(var k in multiThread) {
			if(k=="function") callback = multiThread[k];
			else if(k=="argsend") argsend = multiThread[k];
		}
		multiThread = false;
	}
	var setMethod = (arguments[2]) ? arguments[2].toUpperCase() : "GET";

	runAjax(url, callback, multiThread, setMethod, argsend, 'javascript', 'xml');
}

function runAwdReturnJavascript(url) {
	var callback = function() {}
	var multiThread = (arguments[1]) ? arguments[1] : false;
	var argsend = new Array();
	if(typeof(multiThread)=="object") {
		for(var k in multiThread) {
			if(k=="function") callback = multiThread[k];
			else if(k=="argsend") argsend = multiThread[k];
		}
		multiThread = false;
	}
	var setMethod = (arguments[2]) ? arguments[2].toUpperCase() : "GET";

	runAjax(url, callback, multiThread, setMethod, argsend, 'javascript', 'awd');
}

function runXmlReturnHtml(url, callback) {
	var multiThread = (arguments[2]) ? arguments[2] : false;
	var setMethod = (arguments[3]) ? arguments[3] : "POST";
	var argsend = new Array();
	if(typeof(callback)=="object") {
		var tmpcallback = callback;
		for(var k in tmpcallback) {
			if(k=="function") callback = tmpcallback[k];
			else if(k=="argsend") argsend = tmpcallback[k];
		}
	}

	runAjax(url, callback, multiThread, setMethod, argsend, 'html', 'xml');
}

function runAwdReturnHtml(url, callback) {
	var multiThread = (arguments[2]) ? arguments[2] : false;
	var setMethod = "POST";
	var argsend = new Array();
	if(typeof(callback)=="object") {
		var tmpcallback = callback;
		for(var k in tmpcallback) {
			if(k=="function") callback = tmpcallback[k];
			else if(k=="argsend") argsend = tmpcallback[k];
		}
	}

	runAjax(url, callback, multiThread, setMethod, argsend, 'html', 'awd');
}

function runFormReturn(f, callback) {
	initTasks();
	var tmparray = new Array();
	var tmpid = awdTasks.makeRequest();
	eval("var objAjax"+tmpid+" = new awdAjax();");
	eval("tmparray[0] = objAjax"+tmpid+";")
	awdTasks.setFrame(tmpid, tmparray[0]);
	return tmparray[0].submit(f, {
			'onStart' : ((callback && callback.onStart instanceof Function) ? callback.onStart : ""), 
			'onComplete' : (callback && callback.onComplete instanceof Function) ? callback.onComplete : ""
			}, tmpid);
}

/*
useful function
*/
function setInnerHTML(textShow, elementId) 
{
	var showError = (arguments[2]) ? arguments[2] : true;
	var divspan = document.getElementById(elementId);
	if(typeof(divspan)!='object') {
		if(showError) alert("Does not have object id : " + elementId);
		return false;	
	}
	var tag;
	try {
		tag = divspan.tagName;
	} catch(e) {
		tag = "";
	}
	if(tag!="") tag = tag.toUpperCase();
	if((tag=="DIV") || (tag=="SPAN")) divspan.innerHTML = textShow;
	else if(showError) alert("Can not set text : " + textShow);
}

/*
user defined function
*/
function runPostForm(f, startCallback) {
	runFormReturn(f, {'onStart' : startCallback, 'onComplete' : completeCallback});
}

function donereturn(argsend, P) {
	setInnerHTML(P, 'myElement');
}

function clearHTML() {
	setInnerHTML("Loading...", 'myElement');
}

var zdgfdf1;
var zdgfdf2;
var zdgfdf3;

function AAA1() {
	zdgfdf1.submit();
}

function AAA2() {
	zdgfdf2.submit();
}

function AAA3() {
	zdgfdf3.submit();
}

function startCallback1(f) {
	zdgfdf1 = f;
	setTimeout(AAA1, 3000);
	return false;
}

function startCallback2(f) {
	zdgfdf2 = f;
	setTimeout(AAA2, 3000);
	return false;
}

function startCallback3(f) {
	zdgfdf3 = f;
	setTimeout(AAA3, 3000);
	return false;
}

function completeCallback(response) {
	document.getElementById('nr').innerHTML = parseInt(document.getElementById('nr').innerHTML) + 1;
	document.getElementById('r').innerHTML = document.getElementById('r').innerHTML+'<br>'+response;
}


;
/*  Prototype JavaScript framework, version 1.6.0.3
 *  (c) 2005-2008 Sam Stephenson
 *
 *  Prototype is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/

var Prototype = {
  Version: '1.6.0.3',

  Browser: {
    IE:     !!(window.attachEvent &&
      navigator.userAgent.indexOf('Opera') === -1),
    Opera:  navigator.userAgent.indexOf('Opera') > -1,
    WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
    Gecko:  navigator.userAgent.indexOf('Gecko') > -1 &&
      navigator.userAgent.indexOf('KHTML') === -1,
    MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
  },

  BrowserFeatures: {
    XPath: !!document.evaluate,
    SelectorsAPI: !!document.querySelector,
    ElementExtensions: !!window.HTMLElement,
    SpecificElementExtensions:
      document.createElement('div')['__proto__'] &&
      document.createElement('div')['__proto__'] !==
        document.createElement('form')['__proto__']
  },

  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,

  emptyFunction: function() { },
  K: function(x) { return x }
};

if (Prototype.Browser.MobileSafari)
  Prototype.BrowserFeatures.SpecificElementExtensions = false;


/* Based on Alex Arnell's inheritance implementation. */
var Class = {
  create: function() {
    var parent = null, properties = $A(arguments);
    if (Object.isFunction(properties[0]))
      parent = properties.shift();

    function klass() {
      this.initialize.apply(this, arguments);
    }

    Object.extend(klass, Class.Methods);
    klass.superclass = parent;
    klass.subclasses = [];

    if (parent) {
      var subclass = function() { };
      subclass.prototype = parent.prototype;
      klass.prototype = new subclass;
      parent.subclasses.push(klass);
    }

    for (var i = 0; i < properties.length; i++)
      klass.addMethods(properties[i]);

    if (!klass.prototype.initialize)
      klass.prototype.initialize = Prototype.emptyFunction;

    klass.prototype.constructor = klass;

    return klass;
  }
};

Class.Methods = {
  addMethods: function(source) {
    var ancestor   = this.superclass && this.superclass.prototype;
    var properties = Object.keys(source);

    if (!Object.keys({ toString: true }).length)
      properties.push("toString", "valueOf");

    for (var i = 0, length = properties.length; i < length; i++) {
      var property = properties[i], value = source[property];
      if (ancestor && Object.isFunction(value) &&
          value.argumentNames().first() == "$super") {
        var method = value;
        value = (function(m) {
          return function() { return ancestor[m].apply(this, arguments) };
        })(property).wrap(method);

        value.valueOf = method.valueOf.bind(method);
        value.toString = method.toString.bind(method);
      }
      this.prototype[property] = value;
    }

    return this;
  }
};

var Abstract = { };

Object.extend = function(destination, source) {
  for (var property in source)
    destination[property] = source[property];
  return destination;
};

Object.extend(Object, {
  inspect: function(object) {
    try {
      if (Object.isUndefined(object)) return 'undefined';
      if (object === null) return 'null';
      return object.inspect ? object.inspect() : String(object);
    } catch (e) {
      if (e instanceof RangeError) return '...';
      throw e;
    }
  },

  toJSON: function(object) {
    var type = typeof object;
    switch (type) {
      case 'undefined':
      case 'function':
      case 'unknown': return;
      case 'boolean': return object.toString();
    }

    if (object === null) return 'null';
    if (object.toJSON) return object.toJSON();
    if (Object.isElement(object)) return;

    var results = [];
    for (var property in object) {
      var value = Object.toJSON(object[property]);
      if (!Object.isUndefined(value))
        results.push(property.toJSON() + ': ' + value);
    }

    return '{' + results.join(', ') + '}';
  },

  toQueryString: function(object) {
    return $H(object).toQueryString();
  },

  toHTML: function(object) {
    return object && object.toHTML ? object.toHTML() : String.interpret(object);
  },

  keys: function(object) {
    var keys = [];
    for (var property in object)
      keys.push(property);
    return keys;
  },

  values: function(object) {
    var values = [];
    for (var property in object)
      values.push(object[property]);
    return values;
  },

  clone: function(object) {
    return Object.extend({ }, object);
  },

  isElement: function(object) {
    return !!(object && object.nodeType == 1);
  },

  isArray: function(object) {
    return object != null && typeof object == "object" &&
      'splice' in object && 'join' in object;
  },

  isHash: function(object) {
    return object instanceof Hash;
  },

  isFunction: function(object) {
    return typeof object == "function";
  },

  isString: function(object) {
    return typeof object == "string";
  },

  isNumber: function(object) {
    return typeof object == "number";
  },

  isUndefined: function(object) {
    return typeof object == "undefined";
  }
});

Object.extend(Function.prototype, {
  argumentNames: function() {
    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1]
      .replace(/\s+/g, '').split(',');
    return names.length == 1 && !names[0] ? [] : names;
  },

  bind: function() {
    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
    var __method = this, args = $A(arguments), object = args.shift();
    return function() {
      return __method.apply(object, args.concat($A(arguments)));
    }
  },

  bindAsEventListener: function() {
    var __method = this, args = $A(arguments), object = args.shift();
    return function(event) {
      return __method.apply(object, [event || window.event].concat(args));
    }
  },

  curry: function() {
    if (!arguments.length) return this;
    var __method = this, args = $A(arguments);
    return function() {
      return __method.apply(this, args.concat($A(arguments)));
    }
  },

  delay: function() {
    var __method = this, args = $A(arguments), timeout = args.shift() * 1000;
    return window.setTimeout(function() {
      return __method.apply(__method, args);
    }, timeout);
  },

  defer: function() {
    var args = [0.01].concat($A(arguments));
    return this.delay.apply(this, args);
  },

  wrap: function(wrapper) {
    var __method = this;
    return function() {
      return wrapper.apply(this, [__method.bind(this)].concat($A(arguments)));
    }
  },

  methodize: function() {
    if (this._methodized) return this._methodized;
    var __method = this;
    return this._methodized = function() {
      return __method.apply(null, [this].concat($A(arguments)));
    };
  }
});

Date.prototype.toJSON = function() {
  return '"' + this.getUTCFullYear() + '-' +
    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
    this.getUTCDate().toPaddedString(2) + 'T' +
    this.getUTCHours().toPaddedString(2) + ':' +
    this.getUTCMinutes().toPaddedString(2) + ':' +
    this.getUTCSeconds().toPaddedString(2) + 'Z"';
};

var Try = {
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) { }
    }

    return returnValue;
  }
};

RegExp.prototype.match = RegExp.prototype.test;

RegExp.escape = function(str) {
  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
};

/*--------------------------------------------------------------------------*/

var PeriodicalExecuter = Class.create({
  initialize: function(callback, frequency) {
    this.callback = callback;
    this.frequency = frequency;
    this.currentlyExecuting = false;

    this.registerCallback();
  },

  registerCallback: function() {
    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
  },

  execute: function() {
    this.callback(this);
  },

  stop: function() {
    if (!this.timer) return;
    clearInterval(this.timer);
    this.timer = null;
  },

  onTimerEvent: function() {
    if (!this.currentlyExecuting) {
      try {
        this.currentlyExecuting = true;
        this.execute();
      } finally {
        this.currentlyExecuting = false;
      }
    }
  }
});
Object.extend(String, {
  interpret: function(value) {
    return value == null ? '' : String(value);
  },
  specialChar: {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\'
  }
});

Object.extend(String.prototype, {
  gsub: function(pattern, replacement) {
    var result = '', source = this, match;
    replacement = arguments.callee.prepareReplacement(replacement);

    while (source.length > 0) {
      if (match = source.match(pattern)) {
        result += source.slice(0, match.index);
        result += String.interpret(replacement(match));
        source  = source.slice(match.index + match[0].length);
      } else {
        result += source, source = '';
      }
    }
    return result;
  },

  sub: function(pattern, replacement, count) {
    replacement = this.gsub.prepareReplacement(replacement);
    count = Object.isUndefined(count) ? 1 : count;

    return this.gsub(pattern, function(match) {
      if (--count < 0) return match[0];
      return replacement(match);
    });
  },

  scan: function(pattern, iterator) {
    this.gsub(pattern, iterator);
    return String(this);
  },

  truncate: function(length, truncation) {
    length = length || 30;
    truncation = Object.isUndefined(truncation) ? '...' : truncation;
    return this.length > length ?
      this.slice(0, length - truncation.length) + truncation : String(this);
  },

  strip: function() {
    return this.replace(/^\s+/, '').replace(/\s+$/, '');
  },

  stripTags: function() {
    return this.replace(/<\/?[^>]+>/gi, '');
  },

  stripScripts: function() {
    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
  },

  extractScripts: function() {
    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
    return (this.match(matchAll) || []).map(function(scriptTag) {
      return (scriptTag.match(matchOne) || ['', ''])[1];
    });
  },

  evalScripts: function() {
    return this.extractScripts().map(function(script) { return eval(script) });
  },

  escapeHTML: function() {
    var self = arguments.callee;
    self.text.data = this;
    return self.div.innerHTML;
  },

  unescapeHTML: function() {
    var div = new Element('div');
    div.innerHTML = this.stripTags();
    return div.childNodes[0] ? (div.childNodes.length > 1 ?
      $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) :
      div.childNodes[0].nodeValue) : '';
  },

  toQueryParams: function(separator) {
    var match = this.strip().match(/([^?#]*)(#.*)?$/);
    if (!match) return { };

    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
      if ((pair = pair.split('='))[0]) {
        var key = decodeURIComponent(pair.shift());
        var value = pair.length > 1 ? pair.join('=') : pair[0];
        if (value != undefined) value = decodeURIComponent(value);

        if (key in hash) {
          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
          hash[key].push(value);
        }
        else hash[key] = value;
      }
      return hash;
    });
  },

  toArray: function() {
    return this.split('');
  },

  succ: function() {
    return this.slice(0, this.length - 1) +
      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
  },

  times: function(count) {
    return count < 1 ? '' : new Array(count + 1).join(this);
  },

  camelize: function() {
    var parts = this.split('-'), len = parts.length;
    if (len == 1) return parts[0];

    var camelized = this.charAt(0) == '-'
      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
      : parts[0];

    for (var i = 1; i < len; i++)
      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);

    return camelized;
  },

  capitalize: function() {
    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
  },

  underscore: function() {
    return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase();
  },

  dasherize: function() {
    return this.gsub(/_/,'-');
  },

  inspect: function(useDoubleQuotes) {
    var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) {
      var character = String.specialChar[match[0]];
      return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16);
    });
    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
  },

  toJSON: function() {
    return this.inspect(true);
  },

  unfilterJSON: function(filter) {
    return this.sub(filter || Prototype.JSONFilter, '#{1}');
  },

  isJSON: function() {
    var str = this;
    if (str.blank()) return false;
    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
  },

  evalJSON: function(sanitize) {
    var json = this.unfilterJSON();
    try {
      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
    } catch (e) { }
    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
  },

  include: function(pattern) {
    return this.indexOf(pattern) > -1;
  },

  startsWith: function(pattern) {
    return this.indexOf(pattern) === 0;
  },

  endsWith: function(pattern) {
    var d = this.length - pattern.length;
    return d >= 0 && this.lastIndexOf(pattern) === d;
  },

  empty: function() {
    return this == '';
  },

  blank: function() {
    return /^\s*$/.test(this);
  },

  interpolate: function(object, pattern) {
    return new Template(this, pattern).evaluate(object);
  }
});

if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, {
  escapeHTML: function() {
    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
  },
  unescapeHTML: function() {
    return this.stripTags().replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');
  }
});

String.prototype.gsub.prepareReplacement = function(replacement) {
  if (Object.isFunction(replacement)) return replacement;
  var template = new Template(replacement);
  return function(match) { return template.evaluate(match) };
};

String.prototype.parseQuery = String.prototype.toQueryParams;

Object.extend(String.prototype.escapeHTML, {
  div:  document.createElement('div'),
  text: document.createTextNode('')
});

String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text);

var Template = Class.create({
  initialize: function(template, pattern) {
    this.template = template.toString();
    this.pattern = pattern || Template.Pattern;
  },

  evaluate: function(object) {
    if (Object.isFunction(object.toTemplateReplacements))
      object = object.toTemplateReplacements();

    return this.template.gsub(this.pattern, function(match) {
      if (object == null) return '';

      var before = match[1] || '';
      if (before == '\\') return match[2];

      var ctx = object, expr = match[3];
      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
      match = pattern.exec(expr);
      if (match == null) return before;

      while (match != null) {
        var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1];
        ctx = ctx[comp];
        if (null == ctx || '' == match[3]) break;
        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
        match = pattern.exec(expr);
      }

      return before + String.interpret(ctx);
    });
  }
});
Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;

var $break = { };

var Enumerable = {
  each: function(iterator, context) {
    var index = 0;
    try {
      this._each(function(value) {
        iterator.call(context, value, index++);
      });
    } catch (e) {
      if (e != $break) throw e;
    }
    return this;
  },

  eachSlice: function(number, iterator, context) {
    var index = -number, slices = [], array = this.toArray();
    if (number < 1) return array;
    while ((index += number) < array.length)
      slices.push(array.slice(index, index+number));
    return slices.collect(iterator, context);
  },

  all: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = true;
    this.each(function(value, index) {
      result = result && !!iterator.call(context, value, index);
      if (!result) throw $break;
    });
    return result;
  },

  any: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result = false;
    this.each(function(value, index) {
      if (result = !!iterator.call(context, value, index))
        throw $break;
    });
    return result;
  },

  collect: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];
    this.each(function(value, index) {
      results.push(iterator.call(context, value, index));
    });
    return results;
  },

  detect: function(iterator, context) {
    var result;
    this.each(function(value, index) {
      if (iterator.call(context, value, index)) {
        result = value;
        throw $break;
      }
    });
    return result;
  },

  findAll: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  grep: function(filter, iterator, context) {
    iterator = iterator || Prototype.K;
    var results = [];

    if (Object.isString(filter))
      filter = new RegExp(filter);

    this.each(function(value, index) {
      if (filter.match(value))
        results.push(iterator.call(context, value, index));
    });
    return results;
  },

  include: function(object) {
    if (Object.isFunction(this.indexOf))
      if (this.indexOf(object) != -1) return true;

    var found = false;
    this.each(function(value) {
      if (value == object) {
        found = true;
        throw $break;
      }
    });
    return found;
  },

  inGroupsOf: function(number, fillWith) {
    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
    return this.eachSlice(number, function(slice) {
      while(slice.length < number) slice.push(fillWith);
      return slice;
    });
  },

  inject: function(memo, iterator, context) {
    this.each(function(value, index) {
      memo = iterator.call(context, memo, value, index);
    });
    return memo;
  },

  invoke: function(method) {
    var args = $A(arguments).slice(1);
    return this.map(function(value) {
      return value[method].apply(value, args);
    });
  },

  max: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value >= result)
        result = value;
    });
    return result;
  },

  min: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var result;
    this.each(function(value, index) {
      value = iterator.call(context, value, index);
      if (result == null || value < result)
        result = value;
    });
    return result;
  },

  partition: function(iterator, context) {
    iterator = iterator || Prototype.K;
    var trues = [], falses = [];
    this.each(function(value, index) {
      (iterator.call(context, value, index) ?
        trues : falses).push(value);
    });
    return [trues, falses];
  },

  pluck: function(property) {
    var results = [];
    this.each(function(value) {
      results.push(value[property]);
    });
    return results;
  },

  reject: function(iterator, context) {
    var results = [];
    this.each(function(value, index) {
      if (!iterator.call(context, value, index))
        results.push(value);
    });
    return results;
  },

  sortBy: function(iterator, context) {
    return this.map(function(value, index) {
      return {
        value: value,
        criteria: iterator.call(context, value, index)
      };
    }).sort(function(left, right) {
      var a = left.criteria, b = right.criteria;
      return a < b ? -1 : a > b ? 1 : 0;
    }).pluck('value');
  },

  toArray: function() {
    return this.map();
  },

  zip: function() {
    var iterator = Prototype.K, args = $A(arguments);
    if (Object.isFunction(args.last()))
      iterator = args.pop();

    var collections = [this].concat(args).map($A);
    return this.map(function(value, index) {
      return iterator(collections.pluck(index));
    });
  },

  size: function() {
    return this.toArray().length;
  },

  inspect: function() {
    return '#<Enumerable:' + this.toArray().inspect() + '>';
  }
};

Object.extend(Enumerable, {
  map:     Enumerable.collect,
  find:    Enumerable.detect,
  select:  Enumerable.findAll,
  filter:  Enumerable.findAll,
  member:  Enumerable.include,
  entries: Enumerable.toArray,
  every:   Enumerable.all,
  some:    Enumerable.any
});
function $A(iterable) {
  if (!iterable) return [];
  if (iterable.toArray) return iterable.toArray();
  var length = iterable.length || 0, results = new Array(length);
  while (length--) results[length] = iterable[length];
  return results;
}

if (Prototype.Browser.WebKit) {
  $A = function(iterable) {
    if (!iterable) return [];
    // In Safari, only use the `toArray` method if it's not a NodeList.
    // A NodeList is a function, has an function `item` property, and a numeric
    // `length` property. Adapted from Google Doctype.
    if (!(typeof iterable === 'function' && typeof iterable.length ===
        'number' && typeof iterable.item === 'function') && iterable.toArray)
      return iterable.toArray();
    var length = iterable.length || 0, results = new Array(length);
    while (length--) results[length] = iterable[length];
    return results;
  };
}

Array.from = $A;

Object.extend(Array.prototype, Enumerable);

if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse;

Object.extend(Array.prototype, {
  _each: function(iterator) {
    for (var i = 0, length = this.length; i < length; i++)
      iterator(this[i]);
  },

  clear: function() {
    this.length = 0;
    return this;
  },

  first: function() {
    return this[0];
  },

  last: function() {
    return this[this.length - 1];
  },

  compact: function() {
    return this.select(function(value) {
      return value != null;
    });
  },

  flatten: function() {
    return this.inject([], function(array, value) {
      return array.concat(Object.isArray(value) ?
        value.flatten() : [value]);
    });
  },

  without: function() {
    var values = $A(arguments);
    return this.select(function(value) {
      return !values.include(value);
    });
  },

  reverse: function(inline) {
    return (inline !== false ? this : this.toArray())._reverse();
  },

  reduce: function() {
    return this.length > 1 ? this : this[0];
  },

  uniq: function(sorted) {
    return this.inject([], function(array, value, index) {
      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
        array.push(value);
      return array;
    });
  },

  intersect: function(array) {
    return this.uniq().findAll(function(item) {
      return array.detect(function(value) { return item === value });
    });
  },

  clone: function() {
    return [].concat(this);
  },

  size: function() {
    return this.length;
  },

  inspect: function() {
    return '[' + this.map(Object.inspect).join(', ') + ']';
  },

  toJSON: function() {
    var results = [];
    this.each(function(object) {
      var value = Object.toJSON(object);
      if (!Object.isUndefined(value)) results.push(value);
    });
    return '[' + results.join(', ') + ']';
  }
});

// use native browser JS 1.6 implementation if available
if (Object.isFunction(Array.prototype.forEach))
  Array.prototype._each = Array.prototype.forEach;

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};

if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) {
  i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
  var n = this.slice(0, i).reverse().indexOf(item);
  return (n < 0) ? n : i - n - 1;
};

Array.prototype.toArray = Array.prototype.clone;

function $w(string) {
  if (!Object.isString(string)) return [];
  string = string.strip();
  return string ? string.split(/\s+/) : [];
}

if (Prototype.Browser.Opera){
  Array.prototype.concat = function() {
    var array = [];
    for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
    for (var i = 0, length = arguments.length; i < length; i++) {
      if (Object.isArray(arguments[i])) {
        for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
          array.push(arguments[i][j]);
      } else {
        array.push(arguments[i]);
      }
    }
    return array;
  };
}
Object.extend(Number.prototype, {
  toColorPart: function() {
    return this.toPaddedString(2, 16);
  },

  succ: function() {
    return this + 1;
  },

  times: function(iterator, context) {
    $R(0, this, true).each(iterator, context);
    return this;
  },

  toPaddedString: function(length, radix) {
    var string = this.toString(radix || 10);
    return '0'.times(length - string.length) + string;
  },

  toJSON: function() {
    return isFinite(this) ? this.toString() : 'null';
  }
});

$w('abs round ceil floor').each(function(method){
  Number.prototype[method] = Math[method].methodize();
});
function $H(object) {
  return new Hash(object);
};

var Hash = Class.create(Enumerable, (function() {

  function toQueryPair(key, value) {
    if (Object.isUndefined(value)) return key;
    return key + '=' + encodeURIComponent(String.interpret(value));
  }

  return {
    initialize: function(object) {
      this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
    },

    _each: function(iterator) {
      for (var key in this._object) {
        var value = this._object[key], pair = [key, value];
        pair.key = key;
        pair.value = value;
        iterator(pair);
      }
    },

    set: function(key, value) {
      return this._object[key] = value;
    },

    get: function(key) {
      // simulating poorly supported hasOwnProperty
      if (this._object[key] !== Object.prototype[key])
        return this._object[key];
    },

    unset: function(key) {
      var value = this._object[key];
      delete this._object[key];
      return value;
    },

    toObject: function() {
      return Object.clone(this._object);
    },

    keys: function() {
      return this.pluck('key');
    },

    values: function() {
      return this.pluck('value');
    },

    index: function(value) {
      var match = this.detect(function(pair) {
        return pair.value === value;
      });
      return match && match.key;
    },

    merge: function(object) {
      return this.clone().update(object);
    },

    update: function(object) {
      return new Hash(object).inject(this, function(result, pair) {
        result.set(pair.key, pair.value);
        return result;
      });
    },

    toQueryString: function() {
      return this.inject([], function(results, pair) {
        var key = encodeURIComponent(pair.key), values = pair.value;

        if (values && typeof values == 'object') {
          if (Object.isArray(values))
            return results.concat(values.map(toQueryPair.curry(key)));
        } else results.push(toQueryPair(key, values));
        return results;
      }).join('&');
    },

    inspect: function() {
      return '#<Hash:{' + this.map(function(pair) {
        return pair.map(Object.inspect).join(': ');
      }).join(', ') + '}>';
    },

    toJSON: function() {
      return Object.toJSON(this.toObject());
    },

    clone: function() {
      return new Hash(this);
    }
  }
})());

Hash.prototype.toTemplateReplacements = Hash.prototype.toObject;
Hash.from = $H;
var ObjectRange = Class.create(Enumerable, {
  initialize: function(start, end, exclusive) {
    this.start = start;
    this.end = end;
    this.exclusive = exclusive;
  },

  _each: function(iterator) {
    var value = this.start;
    while (this.include(value)) {
      iterator(value);
      value = value.succ();
    }
  },

  include: function(value) {
    if (value < this.start)
      return false;
    if (this.exclusive)
      return value < this.end;
    return value <= this.end;
  }
});

var $R = function(start, end, exclusive) {
  return new ObjectRange(start, end, exclusive);
};

var Ajax = {
  getTransport: function() {
    return Try.these(
      function() {return new XMLHttpRequest()},
      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
    ) || false;
  },

  activeRequestCount: 0
};

Ajax.Responders = {
  responders: [],

  _each: function(iterator) {
    this.responders._each(iterator);
  },

  register: function(responder) {
    if (!this.include(responder))
      this.responders.push(responder);
  },

  unregister: function(responder) {
    this.responders = this.responders.without(responder);
  },

  dispatch: function(callback, request, transport, json) {
    this.each(function(responder) {
      if (Object.isFunction(responder[callback])) {
        try {
          responder[callback].apply(responder, [request, transport, json]);
        } catch (e) { }
      }
    });
  }
};

Object.extend(Ajax.Responders, Enumerable);

Ajax.Responders.register({
  onCreate:   function() { Ajax.activeRequestCount++ },
  onComplete: function() { Ajax.activeRequestCount-- }
});

Ajax.Base = Class.create({
  initialize: function(options) {
    this.options = {
      method:       'post',
      asynchronous: true,
      contentType:  'application/x-www-form-urlencoded',
      encoding:     'UTF-8',
      parameters:   '',
      evalJSON:     true,
      evalJS:       true
    };
    Object.extend(this.options, options || { });

    this.options.method = this.options.method.toLowerCase();

    if (Object.isString(this.options.parameters))
      this.options.parameters = this.options.parameters.toQueryParams();
    else if (Object.isHash(this.options.parameters))
      this.options.parameters = this.options.parameters.toObject();
  }
});

Ajax.Request = Class.create(Ajax.Base, {
  _complete: false,

  initialize: function($super, url, options) {
    $super(options);
    this.transport = Ajax.getTransport();
    this.request(url);
  },

  request: function(url) {
    this.url = url;
    this.method = this.options.method;
    var params = Object.clone(this.options.parameters);

    if (!['get', 'post'].include(this.method)) {
      // simulate other verbs over post
      params['_method'] = this.method;
      this.method = 'post';
    }

    this.parameters = params;

    if (params = Object.toQueryString(params)) {
      // when GET, append parameters to URL
      if (this.method == 'get')
        this.url += (this.url.include('?') ? '&' : '?') + params;
      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
        params += '&_=';
    }

    try {
      var response = new Ajax.Response(this);
      if (this.options.onCreate) this.options.onCreate(response);
      Ajax.Responders.dispatch('onCreate', this, response);

      this.transport.open(this.method.toUpperCase(), this.url,
        this.options.asynchronous);

      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);

      this.transport.onreadystatechange = this.onStateChange.bind(this);
      this.setRequestHeaders();

      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
      this.transport.send(this.body);

      /* Force Firefox to handle ready state 4 for synchronous requests */
      if (!this.options.asynchronous && this.transport.overrideMimeType)
        this.onStateChange();

    }
    catch (e) {
      this.dispatchException(e);
    }
  },

  onStateChange: function() {
    var readyState = this.transport.readyState;
    if (readyState > 1 && !((readyState == 4) && this._complete))
      this.respondToReadyState(this.transport.readyState);
  },

  setRequestHeaders: function() {
    var headers = {
      'X-Requested-With': 'XMLHttpRequest',
      'X-Prototype-Version': Prototype.Version,
      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
    };

    if (this.method == 'post') {
      headers['Content-type'] = this.options.contentType +
        (this.options.encoding ? '; charset=' + this.options.encoding : '');

      /* Force "Connection: close" for older Mozilla browsers to work
       * around a bug where XMLHttpRequest sends an incorrect
       * Content-length header. See Mozilla Bugzilla #246651.
       */
      if (this.transport.overrideMimeType &&
          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
            headers['Connection'] = 'close';
    }

    // user-defined headers
    if (typeof this.options.requestHeaders == 'object') {
      var extras = this.options.requestHeaders;

      if (Object.isFunction(extras.push))
        for (var i = 0, length = extras.length; i < length; i += 2)
          headers[extras[i]] = extras[i+1];
      else
        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
    }

    for (var name in headers)
      this.transport.setRequestHeader(name, headers[name]);
  },

  success: function() {
    var status = this.getStatus();
    return !status || (status >= 200 && status < 300);
  },

  getStatus: function() {
    try {
      return this.transport.status || 0;
    } catch (e) { return 0 }
  },

  respondToReadyState: function(readyState) {
    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);

    if (state == 'Complete') {
      try {
        this._complete = true;
        (this.options['on' + response.status]
         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
         || Prototype.emptyFunction)(response, response.headerJSON);
      } catch (e) {
        this.dispatchException(e);
      }

      var contentType = response.getHeader('Content-type');
      if (this.options.evalJS == 'force'
          || (this.options.evalJS && this.isSameOrigin() && contentType
          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
        this.evalResponse();
    }

    try {
      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
    } catch (e) {
      this.dispatchException(e);
    }

    if (state == 'Complete') {
      // avoid memory leak in MSIE: clean up
      this.transport.onreadystatechange = Prototype.emptyFunction;
    }
  },

  isSameOrigin: function() {
    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
      protocol: location.protocol,
      domain: document.domain,
      port: location.port ? ':' + location.port : ''
    }));
  },

  getHeader: function(name) {
    try {
      return this.transport.getResponseHeader(name) || null;
    } catch (e) { return null }
  },

  evalResponse: function() {
    try {
      return eval((this.transport.responseText || '').unfilterJSON());
    } catch (e) {
      this.dispatchException(e);
    }
  },

  dispatchException: function(exception) {
    (this.options.onException || Prototype.emptyFunction)(this, exception);
    Ajax.Responders.dispatch('onException', this, exception);
  }
});

Ajax.Request.Events =
  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];

Ajax.Response = Class.create({
  initialize: function(request){
    this.request = request;
    var transport  = this.transport  = request.transport,
        readyState = this.readyState = transport.readyState;

    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
      this.status       = this.getStatus();
      this.statusText   = this.getStatusText();
      this.responseText = String.interpret(transport.responseText);
      this.headerJSON   = this._getHeaderJSON();
    }

    if(readyState == 4) {
      var xml = transport.responseXML;
      this.responseXML  = Object.isUndefined(xml) ? null : xml;
      this.responseJSON = this._getResponseJSON();
    }
  },

  status:      0,
  statusText: '',

  getStatus: Ajax.Request.prototype.getStatus,

  getStatusText: function() {
    try {
      return this.transport.statusText || '';
    } catch (e) { return '' }
  },

  getHeader: Ajax.Request.prototype.getHeader,

  getAllHeaders: function() {
    try {
      return this.getAllResponseHeaders();
    } catch (e) { return null }
  },

  getResponseHeader: function(name) {
    return this.transport.getResponseHeader(name);
  },

  getAllResponseHeaders: function() {
    return this.transport.getAllResponseHeaders();
  },

  _getHeaderJSON: function() {
    var json = this.getHeader('X-JSON');
    if (!json) return null;
    json = decodeURIComponent(escape(json));
    try {
      return json.evalJSON(this.request.options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  },

  _getResponseJSON: function() {
    var options = this.request.options;
    if (!options.evalJSON || (options.evalJSON != 'force' &&
      !(this.getHeader('Content-type') || '').include('application/json')) ||
        this.responseText.blank())
          return null;
    try {
      return this.responseText.evalJSON(options.sanitizeJSON ||
        !this.request.isSameOrigin());
    } catch (e) {
      this.request.dispatchException(e);
    }
  }
});

Ajax.Updater = Class.create(Ajax.Request, {
  initialize: function($super, container, url, options) {
    this.container = {
      success: (container.success || container),
      failure: (container.failure || (container.success ? null : container))
    };

    options = Object.clone(options);
    var onComplete = options.onComplete;
    options.onComplete = (function(response, json) {
      this.updateContent(response.responseText);
      if (Object.isFunction(onComplete)) onComplete(response, json);
    }).bind(this);

    $super(url, options);
  },

  updateContent: function(responseText) {
    var receiver = this.container[this.success() ? 'success' : 'failure'],
        options = this.options;

    if (!options.evalScripts) responseText = responseText.stripScripts();

    if (receiver = $(receiver)) {
      if (options.insertion) {
        if (Object.isString(options.insertion)) {
          var insertion = { }; insertion[options.insertion] = responseText;
          receiver.insert(insertion);
        }
        else options.insertion(receiver, responseText);
      }
      else receiver.update(responseText);
    }
  }
});

Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
  initialize: function($super, container, url, options) {
    $super(options);
    this.onComplete = this.options.onComplete;

    this.frequency = (this.options.frequency || 2);
    this.decay = (this.options.decay || 1);

    this.updater = { };
    this.container = container;
    this.url = url;

    this.start();
  },

  start: function() {
    this.options.onComplete = this.updateComplete.bind(this);
    this.onTimerEvent();
  },

  stop: function() {
    this.updater.options.onComplete = undefined;
    clearTimeout(this.timer);
    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
  },

  updateComplete: function(response) {
    if (this.options.decay) {
      this.decay = (response.responseText == this.lastText ?
        this.decay * this.options.decay : 1);

      this.lastText = response.responseText;
    }
    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
  },

  onTimerEvent: function() {
    this.updater = new Ajax.Updater(this.container, this.url, this.options);
  }
});
function $(element) {
  if (arguments.length > 1) {
    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
      elements.push($(arguments[i]));
    return elements;
  }
  if (Object.isString(element))
    element = document.getElementById(element);
  return Element.extend(element);
}

if (Prototype.BrowserFeatures.XPath) {
  document._getElementsByXPath = function(expression, parentElement) {
    var results = [];
    var query = document.evaluate(expression, $(parentElement) || document,
      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0, length = query.snapshotLength; i < length; i++)
      results.push(Element.extend(query.snapshotItem(i)));
    return results;
  };
}

/*--------------------------------------------------------------------------*/

if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
  // DOM level 2 ECMAScript Language Binding
  Object.extend(Node, {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  });
}

(function() {
  var element = this.Element;
  this.Element = function(tagName, attributes) {
    attributes = attributes || { };
    tagName = tagName.toLowerCase();
    var cache = Element.cache;
    if (Prototype.Browser.IE && attributes.name) {
      tagName = '<' + tagName + ' name="' + attributes.name + '">';
      delete attributes.name;
      return Element.writeAttribute(document.createElement(tagName), attributes);
    }
    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
  };
  Object.extend(this.Element, element || { });
  if (element) this.Element.prototype = element.prototype;
}).call(window);

Element.cache = { };

Element.Methods = {
  visible: function(element) {
    return $(element).style.display != 'none';
  },

  toggle: function(element) {
    element = $(element);
    Element[Element.visible(element) ? 'hide' : 'show'](element);
    return element;
  },

  hide: function(element) {
    element = $(element);
    element.style.display = 'none';
    return element;
  },

  show: function(element) {
    element = $(element);
    element.style.display = '';
    return element;
  },

  remove: function(element) {
    element = $(element);
    element.parentNode.removeChild(element);
    return element;
  },

  update: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);
    content = Object.toHTML(content);
    element.innerHTML = content.stripScripts();
    content.evalScripts.bind(content).defer();
    return element;
  },

  replace: function(element, content) {
    element = $(element);
    if (content && content.toElement) content = content.toElement();
    else if (!Object.isElement(content)) {
      content = Object.toHTML(content);
      var range = element.ownerDocument.createRange();
      range.selectNode(element);
      content.evalScripts.bind(content).defer();
      content = range.createContextualFragment(content.stripScripts());
    }
    element.parentNode.replaceChild(content, element);
    return element;
  },

  insert: function(element, insertions) {
    element = $(element);

    if (Object.isString(insertions) || Object.isNumber(insertions) ||
        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
          insertions = {bottom:insertions};

    var content, insert, tagName, childNodes;

    for (var position in insertions) {
      content  = insertions[position];
      position = position.toLowerCase();
      insert = Element._insertionTranslations[position];

      if (content && content.toElement) content = content.toElement();
      if (Object.isElement(content)) {
        insert(element, content);
        continue;
      }

      content = Object.toHTML(content);

      tagName = ((position == 'before' || position == 'after')
        ? element.parentNode : element).tagName.toUpperCase();

      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());

      if (position == 'top' || position == 'after') childNodes.reverse();
      childNodes.each(insert.curry(element));

      content.evalScripts.bind(content).defer();
    }

    return element;
  },

  wrap: function(element, wrapper, attributes) {
    element = $(element);
    if (Object.isElement(wrapper))
      $(wrapper).writeAttribute(attributes || { });
    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
    else wrapper = new Element('div', wrapper);
    if (element.parentNode)
      element.parentNode.replaceChild(wrapper, element);
    wrapper.appendChild(element);
    return wrapper;
  },

  inspect: function(element) {
    element = $(element);
    var result = '<' + element.tagName.toLowerCase();
    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
      var property = pair.first(), attribute = pair.last();
      var value = (element[property] || '').toString();
      if (value) result += ' ' + attribute + '=' + value.inspect(true);
    });
    return result + '>';
  },

  recursivelyCollect: function(element, property) {
    element = $(element);
    var elements = [];
    while (element = element[property])
      if (element.nodeType == 1)
        elements.push(Element.extend(element));
    return elements;
  },

  ancestors: function(element) {
    return $(element).recursivelyCollect('parentNode');
  },

  descendants: function(element) {
    return $(element).select("*");
  },

  firstDescendant: function(element) {
    element = $(element).firstChild;
    while (element && element.nodeType != 1) element = element.nextSibling;
    return $(element);
  },

  immediateDescendants: function(element) {
    if (!(element = $(element).firstChild)) return [];
    while (element && element.nodeType != 1) element = element.nextSibling;
    if (element) return [element].concat($(element).nextSiblings());
    return [];
  },

  previousSiblings: function(element) {
    return $(element).recursivelyCollect('previousSibling');
  },

  nextSiblings: function(element) {
    return $(element).recursivelyCollect('nextSibling');
  },

  siblings: function(element) {
    element = $(element);
    return element.previousSiblings().reverse().concat(element.nextSiblings());
  },

  match: function(element, selector) {
    if (Object.isString(selector))
      selector = new Selector(selector);
    return selector.match($(element));
  },

  up: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(element.parentNode);
    var ancestors = element.ancestors();
    return Object.isNumber(expression) ? ancestors[expression] :
      Selector.findElement(ancestors, expression, index);
  },

  down: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return element.firstDescendant();
    return Object.isNumber(expression) ? element.descendants()[expression] :
      Element.select(element, expression)[index || 0];
  },

  previous: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
    var previousSiblings = element.previousSiblings();
    return Object.isNumber(expression) ? previousSiblings[expression] :
      Selector.findElement(previousSiblings, expression, index);
  },

  next: function(element, expression, index) {
    element = $(element);
    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
    var nextSiblings = element.nextSiblings();
    return Object.isNumber(expression) ? nextSiblings[expression] :
      Selector.findElement(nextSiblings, expression, index);
  },

  select: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element, args);
  },

  adjacent: function() {
    var args = $A(arguments), element = $(args.shift());
    return Selector.findChildElements(element.parentNode, args).without(element);
  },

  identify: function(element) {
    element = $(element);
    var id = element.readAttribute('id'), self = arguments.callee;
    if (id) return id;
    do { id = 'anonymous_element_' + self.counter++ } while ($(id));
    element.writeAttribute('id', id);
    return id;
  },

  readAttribute: function(element, name) {
    element = $(element);
    if (Prototype.Browser.IE) {
      var t = Element._attributeTranslations.read;
      if (t.values[name]) return t.values[name](element, name);
      if (t.names[name]) name = t.names[name];
      if (name.include(':')) {
        return (!element.attributes || !element.attributes[name]) ? null :
         element.attributes[name].value;
      }
    }
    return element.getAttribute(name);
  },

  writeAttribute: function(element, name, value) {
    element = $(element);
    var attributes = { }, t = Element._attributeTranslations.write;

    if (typeof name == 'object') attributes = name;
    else attributes[name] = Object.isUndefined(value) ? true : value;

    for (var attr in attributes) {
      name = t.names[attr] || attr;
      value = attributes[attr];
      if (t.values[attr]) name = t.values[attr](element, value);
      if (value === false || value === null)
        element.removeAttribute(name);
      else if (value === true)
        element.setAttribute(name, name);
      else element.setAttribute(name, value);
    }
    return element;
  },

  getHeight: function(element) {
    return $(element).getDimensions().height;
  },

  getWidth: function(element) {
    return $(element).getDimensions().width;
  },

  classNames: function(element) {
    return new Element.ClassNames(element);
  },

  hasClassName: function(element, className) {
    if (!(element = $(element))) return;
    var elementClassName = element.className;
    return (elementClassName.length > 0 && (elementClassName == className ||
      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
  },

  addClassName: function(element, className) {
    if (!(element = $(element))) return;
    if (!element.hasClassName(className))
      element.className += (element.className ? ' ' : '') + className;
    return element;
  },

  removeClassName: function(element, className) {
    if (!(element = $(element))) return;
    element.className = element.className.replace(
      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
    return element;
  },

  toggleClassName: function(element, className) {
    if (!(element = $(element))) return;
    return element[element.hasClassName(className) ?
      'removeClassName' : 'addClassName'](className);
  },

  // removes whitespace-only text node children
  cleanWhitespace: function(element) {
    element = $(element);
    var node = element.firstChild;
    while (node) {
      var nextNode = node.nextSibling;
      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
        element.removeChild(node);
      node = nextNode;
    }
    return element;
  },

  empty: function(element) {
    return $(element).innerHTML.blank();
  },

  descendantOf: function(element, ancestor) {
    element = $(element), ancestor = $(ancestor);

    if (element.compareDocumentPosition)
      return (element.compareDocumentPosition(ancestor) & 8) === 8;

    if (ancestor.contains)
      return ancestor.contains(element) && ancestor !== element;

    while (element = element.parentNode)
      if (element == ancestor) return true;

    return false;
  },

  scrollTo: function(element) {
    element = $(element);
    var pos = element.cumulativeOffset();
    window.scrollTo(pos[0], pos[1]);
    return element;
  },

  getStyle: function(element, style) {
    element = $(element);
    style = style == 'float' ? 'cssFloat' : style.camelize();
    var value = element.style[style];
    if (!value || value == 'auto') {
      var css = document.defaultView.getComputedStyle(element, null);
      value = css ? css[style] : null;
    }
    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
    return value == 'auto' ? null : value;
  },

  getOpacity: function(element) {
    return $(element).getStyle('opacity');
  },

  setStyle: function(element, styles) {
    element = $(element);
    var elementStyle = element.style, match;
    if (Object.isString(styles)) {
      element.style.cssText += ';' + styles;
      return styles.include('opacity') ?
        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
    }
    for (var property in styles)
      if (property == 'opacity') element.setOpacity(styles[property]);
      else
        elementStyle[(property == 'float' || property == 'cssFloat') ?
          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
            property] = styles[property];

    return element;
  },

  setOpacity: function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;
    return element;
  },

  getDimensions: function(element) {
    element = $(element);
    var display = element.getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    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 originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  },

  makePositioned: function(element) {
    element = $(element);
    var pos = Element.getStyle(element, 'position');
    if (pos == 'static' || !pos) {
      element._madePositioned = true;
      element.style.position = 'relative';
      // Opera returns the offset relative to the positioning context, when an
      // element is position relative but top and left have not been defined
      if (Prototype.Browser.Opera) {
        element.style.top = 0;
        element.style.left = 0;
      }
    }
    return element;
  },

  undoPositioned: function(element) {
    element = $(element);
    if (element._madePositioned) {
      element._madePositioned = undefined;
      element.style.position =
        element.style.top =
        element.style.left =
        element.style.bottom =
        element.style.right = '';
    }
    return element;
  },

  makeClipping: function(element) {
    element = $(element);
    if (element._overflow) return element;
    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
    if (element._overflow !== 'hidden')
      element.style.overflow = 'hidden';
    return element;
  },

  undoClipping: function(element) {
    element = $(element);
    if (!element._overflow) return element;
    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
    element._overflow = null;
    return element;
  },

  cumulativeOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      
      if (element) {
        valueT += parseInt(Element.getStyle(element, 'borderTopWidth')) || 0;
        valueL += parseInt(Element.getStyle(element, 'borderLeftWidth')) || 0;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  positionedOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      element = element.offsetParent;
      if (element) {
        if (element.tagName.toUpperCase() == 'BODY') break;
        var p = Element.getStyle(element, 'position');
        if (p !== 'static') break;
      }
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  absolutize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'absolute') return element;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    var offsets = element.positionedOffset();
    var top     = offsets[1];
    var left    = offsets[0];
    var width   = element.clientWidth;
    var height  = element.clientHeight;

    element._originalLeft   = left - parseFloat(element.style.left  || 0);
    element._originalTop    = top  - parseFloat(element.style.top || 0);
    element._originalWidth  = element.style.width;
    element._originalHeight = element.style.height;

    element.style.position = 'absolute';
    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.width  = width + 'px';
    element.style.height = height + 'px';
    return element;
  },

  relativize: function(element) {
    element = $(element);
    if (element.getStyle('position') == 'relative') return element;
    // Position.prepare(); // To be done manually by Scripty when it needs it.

    element.style.position = 'relative';
    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);

    element.style.top    = top + 'px';
    element.style.left   = left + 'px';
    element.style.height = element._originalHeight;
    element.style.width  = element._originalWidth;
    return element;
  },

  cumulativeScrollOffset: function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.scrollTop  || 0;
      valueL += element.scrollLeft || 0;
      element = element.parentNode;
    } while (element);
    return Element._returnOffset(valueL, valueT);
  },

  getOffsetParent: function(element) {
    if (element.offsetParent) return $(element.offsetParent);
    if (element == document.body) return $(element);

    while ((element = element.parentNode) && element != document.body)
      if (Element.getStyle(element, 'position') != 'static')
        return $(element);

    return $(document.body);
  },

  viewportOffset: function(forElement) {
    var valueT = 0, valueL = 0;

    var element = forElement;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;

      // Safari fix
      if (element.offsetParent == document.body &&
        Element.getStyle(element, 'position') == 'absolute') break;

    } while (element = element.offsetParent);

    element = forElement;
    do {
      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
        valueT -= element.scrollTop  || 0;
        valueL -= element.scrollLeft || 0;
      }
    } while (element = element.parentNode);

    return Element._returnOffset(valueL, valueT);
  },

  clonePosition: function(element, source) {
    var options = Object.extend({
      setLeft:    true,
      setTop:     true,
      setWidth:   true,
      setHeight:  true,
      offsetTop:  0,
      offsetLeft: 0
    }, arguments[2] || { });

    // find page position of source
    source = $(source);
    var p = source.viewportOffset();

    // find coordinate system to use
    element = $(element);
    var delta = [0, 0];
    var parent = null;
    // delta [0,0] will do fine with position: fixed elements,
    // position:absolute needs offsetParent deltas
    if (Element.getStyle(element, 'position') == 'absolute') {
      parent = element.getOffsetParent();
      delta = parent.viewportOffset();
    }

    // correct by body offsets (fixes Safari)
    if (parent == document.body) {
      delta[0] -= document.body.offsetLeft;
      delta[1] -= document.body.offsetTop;
    }

    // set position
    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
    return element;
  }
};

Element.Methods.identify.counter = 1;

Object.extend(Element.Methods, {
  getElementsBySelector: Element.Methods.select,
  childElements: Element.Methods.immediateDescendants
});

Element._attributeTranslations = {
  write: {
    names: {
      className: 'class',
      htmlFor:   'for'
    },
    values: { }
  }
};

if (Prototype.Browser.Opera) {
  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
    function(proceed, element, style) {
      switch (style) {
        case 'left': case 'top': case 'right': case 'bottom':
          if (proceed(element, 'position') === 'static') return null;
        case 'height': case 'width':
          // returns '0px' for hidden elements; we want it to return null
          if (!Element.visible(element)) return null;

          // returns the border-box dimensions rather than the content-box
          // dimensions, so we subtract padding and borders from the value
          var dim = parseInt(proceed(element, style), 10);

          if (dim !== element['offset' + style.capitalize()])
            return dim + 'px';

          var properties;
          if (style === 'height') {
            properties = ['border-top-width', 'padding-top',
             'padding-bottom', 'border-bottom-width'];
          }
          else {
            properties = ['border-left-width', 'padding-left',
             'padding-right', 'border-right-width'];
          }
          return properties.inject(dim, function(memo, property) {
            var val = proceed(element, property);
            return val === null ? memo : memo - parseInt(val, 10);
          }) + 'px';
        default: return proceed(element, style);
      }
    }
  );

  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
    function(proceed, element, attribute) {
      if (attribute === 'title') return element.title;
      return proceed(element, attribute);
    }
  );
}

else if (Prototype.Browser.IE) {
  // IE doesn't report offsets correctly for static elements, so we change them
  // to "relative" to get the values, then change them back.
  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
    function(proceed, element) {
      element = $(element);
      // IE throws an error if element is not in document
      try { element.offsetParent }
      catch(e) { return $(document.body) }
      var position = element.getStyle('position');
      if (position !== 'static') return proceed(element);
      element.setStyle({ position: 'relative' });
      var value = proceed(element);
      element.setStyle({ position: position });
      return value;
    }
  );

  $w('positionedOffset viewportOffset').each(function(method) {
    Element.Methods[method] = Element.Methods[method].wrap(
      function(proceed, element) {
        element = $(element);
        try { element.offsetParent }
        catch(e) { return Element._returnOffset(0,0) }
        var position = element.getStyle('position');
        if (position !== 'static') return proceed(element);
        // Trigger hasLayout on the offset parent so that IE6 reports
        // accurate offsetTop and offsetLeft values for position: fixed.
        var offsetParent = element.getOffsetParent();
        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
          offsetParent.setStyle({ zoom: 1 });
        element.setStyle({ position: 'relative' });
        var value = proceed(element);
        element.setStyle({ position: position });
        return value;
      }
    );
  });

  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
    function(proceed, element) {
      try { element.offsetParent }
      catch(e) { return Element._returnOffset(0,0) }
      return proceed(element);
    }
  );

  Element.Methods.getStyle = function(element, style) {
    element = $(element);
    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
    var value = element.style[style];
    if (!value && element.currentStyle) value = element.currentStyle[style];

    if (style == 'opacity') {
      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
        if (value[1]) return parseFloat(value[1]) / 100;
      return 1.0;
    }

    if (value == 'auto') {
      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
        return element['offset' + style.capitalize()] + 'px';
      return null;
    }
    return value;
  };

  Element.Methods.setOpacity = function(element, value) {
    function stripAlpha(filter){
      return filter.replace(/alpha\([^\)]*\)/gi,'');
    }
    element = $(element);
    var currentStyle = element.currentStyle;
    if ((currentStyle && !currentStyle.hasLayout) ||
      (!currentStyle && element.style.zoom == 'normal'))
        element.style.zoom = 1;

    var filter = element.getStyle('filter'), style = element.style;
    if (value == 1 || value === '') {
      (filter = stripAlpha(filter)) ?
        style.filter = filter : style.removeAttribute('filter');
      return element;
    } else if (value < 0.00001) value = 0;
    style.filter = stripAlpha(filter) +
      'alpha(opacity=' + (value * 100) + ')';
    return element;
  };

  Element._attributeTranslations = {
    read: {
      names: {
        'class': 'className',
        'for':   'htmlFor'
      },
      values: {
        _getAttr: function(element, attribute) {
          return element.getAttribute(attribute, 2);
        },
        _getAttrNode: function(element, attribute) {
          var node = element.getAttributeNode(attribute);
          return node ? node.value : "";
        },
        _getEv: function(element, attribute) {
          attribute = element.getAttribute(attribute);
          return attribute ? attribute.toString().slice(23, -2) : null;
        },
        _flag: function(element, attribute) {
          return $(element).hasAttribute(attribute) ? attribute : null;
        },
        style: function(element) {
          return element.style.cssText.toLowerCase();
        },
        title: function(element) {
          return element.title;
        }
      }
    }
  };

  Element._attributeTranslations.write = {
    names: Object.extend({
      cellpadding: 'cellPadding',
      cellspacing: 'cellSpacing'
    }, Element._attributeTranslations.read.names),
    values: {
      checked: function(element, value) {
        element.checked = !!value;
      },

      style: function(element, value) {
        element.style.cssText = value ? value : '';
      }
    }
  };

  Element._attributeTranslations.has = {};

  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
  });

  (function(v) {
    Object.extend(v, {
      href:        v._getAttr,
      src:         v._getAttr,
      type:        v._getAttr,
      action:      v._getAttrNode,
      disabled:    v._flag,
      checked:     v._flag,
      readonly:    v._flag,
      multiple:    v._flag,
      onload:      v._getEv,
      onunload:    v._getEv,
      onclick:     v._getEv,
      ondblclick:  v._getEv,
      onmousedown: v._getEv,
      onmouseup:   v._getEv,
      onmouseover: v._getEv,
      onmousemove: v._getEv,
      onmouseout:  v._getEv,
      onfocus:     v._getEv,
      onblur:      v._getEv,
      onkeypress:  v._getEv,
      onkeydown:   v._getEv,
      onkeyup:     v._getEv,
      onsubmit:    v._getEv,
      onreset:     v._getEv,
      onselect:    v._getEv,
      onchange:    v._getEv
    });
  })(Element._attributeTranslations.read.values);
}

else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1) ? 0.999999 :
      (value === '') ? '' : (value < 0.00001) ? 0 : value;
    return element;
  };
}

else if (Prototype.Browser.WebKit) {
  Element.Methods.setOpacity = function(element, value) {
    element = $(element);
    element.style.opacity = (value == 1 || value === '') ? '' :
      (value < 0.00001) ? 0 : value;

    if (value == 1)
      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
        element.width++; element.width--;
      } else try {
        var n = document.createTextNode(' ');
        element.appendChild(n);
        element.removeChild(n);
      } catch (e) { }

    return element;
  };

  // Safari returns margins on body which is incorrect if the child is absolutely
  // positioned.  For performance reasons, redefine Element#cumulativeOffset for
  // KHTML/WebKit only.
  Element.Methods.cumulativeOffset = function(element) {
    var valueT = 0, valueL = 0;
    do {
      valueT += element.offsetTop  || 0;
      valueL += element.offsetLeft || 0;
      if (element.offsetParent == document.body)
        if (Element.getStyle(element, 'position') == 'absolute') break;

      element = element.offsetParent;
    } while (element);

    return Element._returnOffset(valueL, valueT);
  };
}

if (Prototype.Browser.IE || Prototype.Browser.Opera) {
  // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
  Element.Methods.update = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) return element.update().insert(content);

    content = Object.toHTML(content);
    var tagName = element.tagName.toUpperCase();

    if (tagName in Element._insertionTranslations.tags) {
      $A(element.childNodes).each(function(node) { element.removeChild(node) });
      Element._getContentFromAnonymousElement(tagName, content.stripScripts())
        .each(function(node) { element.appendChild(node) });
    }
    else element.innerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

if ('outerHTML' in document.createElement('div')) {
  Element.Methods.replace = function(element, content) {
    element = $(element);

    if (content && content.toElement) content = content.toElement();
    if (Object.isElement(content)) {
      element.parentNode.replaceChild(content, element);
      return element;
    }

    content = Object.toHTML(content);
    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();

    if (Element._insertionTranslations.tags[tagName]) {
      var nextSibling = element.next();
      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
      parent.removeChild(element);
      if (nextSibling)
        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
      else
        fragments.each(function(node) { parent.appendChild(node) });
    }
    else element.outerHTML = content.stripScripts();

    content.evalScripts.bind(content).defer();
    return element;
  };
}

Element._returnOffset = function(l, t) {
  var result = [l, t];
  result.left = l;
  result.top = t;
  return result;
};

Element._getContentFromAnonymousElement = function(tagName, html) {
  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
  if (t) {
    div.innerHTML = t[0] + html + t[1];
    t[2].times(function() { div = div.firstChild });
  } else div.innerHTML = html;
  return $A(div.childNodes);
};

Element._insertionTranslations = {
  before: function(element, node) {
    element.parentNode.insertBefore(node, element);
  },
  top: function(element, node) {
    element.insertBefore(node, element.firstChild);
  },
  bottom: function(element, node) {
    element.appendChild(node);
  },
  after: function(element, node) {
    element.parentNode.insertBefore(node, element.nextSibling);
  },
  tags: {
    TABLE:  ['<table>',                '</table>',                   1],
    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
    SELECT: ['<select>',               '</select>',                  1]
  }
};

(function() {
  Object.extend(this.tags, {
    THEAD: this.tags.TBODY,
    TFOOT: this.tags.TBODY,
    TH:    this.tags.TD
  });
}).call(Element._insertionTranslations);

Element.Methods.Simulated = {
  hasAttribute: function(element, attribute) {
    attribute = Element._attributeTranslations.has[attribute] || attribute;
    var node = $(element).getAttributeNode(attribute);
    return !!(node && node.specified);
  }
};

Element.Methods.ByTag = { };

Object.extend(Element, Element.Methods);

if (!Prototype.BrowserFeatures.ElementExtensions &&
    document.createElement('div')['__proto__']) {
  window.HTMLElement = { };
  window.HTMLElement.prototype = document.createElement('div')['__proto__'];
  Prototype.BrowserFeatures.ElementExtensions = true;
}

Element.extend = (function() {
  if (Prototype.BrowserFeatures.SpecificElementExtensions)
    return Prototype.K;

  var Methods = { }, ByTag = Element.Methods.ByTag;

  var extend = Object.extend(function(element) {
    if (!element || element._extendedByPrototype ||
        element.nodeType != 1 || element == window) return element;

    var methods = Object.clone(Methods),
      tagName = element.tagName.toUpperCase(), property, value;

    // extend methods for specific tags
    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);

    for (property in methods) {
      value = methods[property];
      if (Object.isFunction(value) && !(property in element))
        element[property] = value.methodize();
    }

    element._extendedByPrototype = Prototype.emptyFunction;
    return element;

  }, {
    refresh: function() {
      // extend methods for all tags (Safari doesn't need this)
      if (!Prototype.BrowserFeatures.ElementExtensions) {
        Object.extend(Methods, Element.Methods);
        Object.extend(Methods, Element.Methods.Simulated);
      }
    }
  });

  extend.refresh();
  return extend;
})();

Element.hasAttribute = function(element, attribute) {
  if (element.hasAttribute) return element.hasAttribute(attribute);
  return Element.Methods.Simulated.hasAttribute(element, attribute);
};

Element.addMethods = function(methods) {
  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;

  if (!methods) {
    Object.extend(Form, Form.Methods);
    Object.extend(Form.Element, Form.Element.Methods);
    Object.extend(Element.Methods.ByTag, {
      "FORM":     Object.clone(Form.Methods),
      "INPUT":    Object.clone(Form.Element.Methods),
      "SELECT":   Object.clone(Form.Element.Methods),
      "TEXTAREA": Object.clone(Form.Element.Methods)
    });
  }

  if (arguments.length == 2) {
    var tagName = methods;
    methods = arguments[1];
  }

  if (!tagName) Object.extend(Element.Methods, methods || { });
  else {
    if (Object.isArray(tagName)) tagName.each(extend);
    else extend(tagName);
  }

  function extend(tagName) {
    tagName = tagName.toUpperCase();
    if (!Element.Methods.ByTag[tagName])
      Element.Methods.ByTag[tagName] = { };
    Object.extend(Element.Methods.ByTag[tagName], methods);
  }

  function copy(methods, destination, onlyIfAbsent) {
    onlyIfAbsent = onlyIfAbsent || false;
    for (var property in methods) {
      var value = methods[property];
      if (!Object.isFunction(value)) continue;
      if (!onlyIfAbsent || !(property in destination))
        destination[property] = value.methodize();
    }
  }

  function findDOMClass(tagName) {
    var klass;
    var trans = {
      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
      "FrameSet", "IFRAME": "IFrame"
    };
    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName + 'Element';
    if (window[klass]) return window[klass];
    klass = 'HTML' + tagName.capitalize() + 'Element';
    if (window[klass]) return window[klass];

    window[klass] = { };
    window[klass].prototype = document.createElement(tagName)['__proto__'];
    return window[klass];
  }

  if (F.ElementExtensions) {
    copy(Element.Methods, HTMLElement.prototype);
    copy(Element.Methods.Simulated, HTMLElement.prototype, true);
  }

  if (F.SpecificElementExtensions) {
    for (var tag in Element.Methods.ByTag) {
      var klass = findDOMClass(tag);
      if (Object.isUndefined(klass)) continue;
      copy(T[tag], klass.prototype);
    }
  }

  Object.extend(Element, Element.Methods);
  delete Element.ByTag;

  if (Element.extend.refresh) Element.extend.refresh();
  Element.cache = { };
};

document.viewport = {
  getDimensions: function() {
    var dimensions = { }, B = Prototype.Browser;
    $w('width height').each(function(d) {
      var D = d.capitalize();
      if (B.WebKit && !document.evaluate) {
        // Safari <3.0 needs self.innerWidth/Height
        dimensions[d] = self['inner' + D];
      } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) {
        // Opera <9.5 needs document.body.clientWidth/Height
        dimensions[d] = document.body['client' + D]
      } else {
        dimensions[d] = document.documentElement['client' + D];
      }
    });
    return dimensions;
  },

  getWidth: function() {
    return this.getDimensions().width;
  },

  getHeight: function() {
    return this.getDimensions().height;
  },

  getScrollOffsets: function() {
    return Element._returnOffset(
      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
      window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop);
  }
};
/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
 * license.  Please see http://www.yui-ext.com/ for more information. */

var Selector = Class.create({
  initialize: function(expression) {
    this.expression = expression.strip();

    if (this.shouldUseSelectorsAPI()) {
      this.mode = 'selectorsAPI';
    } else if (this.shouldUseXPath()) {
      this.mode = 'xpath';
      this.compileXPathMatcher();
    } else {
      this.mode = "normal";
      this.compileMatcher();
    }

  },

  shouldUseXPath: function() {
    if (!Prototype.BrowserFeatures.XPath) return false;

    var e = this.expression;

    // Safari 3 chokes on :*-of-type and :empty
    if (Prototype.Browser.WebKit &&
     (e.include("-of-type") || e.include(":empty")))
      return false;

    // XPath can't do namespaced attributes, nor can it read
    // the "checked" property from DOM nodes
    if ((/(\[[\w-]*?:|:checked)/).test(e))
      return false;

    return true;
  },

  shouldUseSelectorsAPI: function() {
    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;

    if (!Selector._div) Selector._div = new Element('div');

    // Make sure the browser treats the selector as valid. Test on an
    // isolated element to minimize cost of this check.
    try {
      Selector._div.querySelector(this.expression);
    } catch(e) {
      return false;
    }

    return true;
  },

  compileMatcher: function() {
    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
        c = Selector.criteria, le, p, m;

    if (Selector._cache[e]) {
      this.matcher = Selector._cache[e];
      return;
    }

    this.matcher = ["this.matcher = function(root) {",
                    "var r = root, h = Selector.handlers, c = false, n;"];

    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          this.matcher.push(Object.isFunction(c[i]) ? c[i](m) :
            new Template(c[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.matcher.push("return h.unique(n);\n}");
    eval(this.matcher.join('\n'));
    Selector._cache[this.expression] = this.matcher;
  },

  compileXPathMatcher: function() {
    var e = this.expression, ps = Selector.patterns,
        x = Selector.xpath, le, m;

    if (Selector._cache[e]) {
      this.xpath = Selector._cache[e]; return;
    }

    this.matcher = ['.//*'];
    while (e && le != e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        if (m = e.match(ps[i])) {
          this.matcher.push(Object.isFunction(x[i]) ? x[i](m) :
            new Template(x[i]).evaluate(m));
          e = e.replace(m[0], '');
          break;
        }
      }
    }

    this.xpath = this.matcher.join('');
    Selector._cache[this.expression] = this.xpath;
  },

  findElements: function(root) {
    root = root || document;
    var e = this.expression, results;

    switch (this.mode) {
      case 'selectorsAPI':
        // querySelectorAll queries document-wide, then filters to descendants
        // of the context element. That's not what we want.
        // Add an explicit context to the selector if necessary.
        if (root !== document) {
          var oldId = root.id, id = $(root).identify();
          e = "#" + id + " " + e;
        }

        results = $A(root.querySelectorAll(e)).map(Element.extend);
        root.id = oldId;

        return results;
      case 'xpath':
        return document._getElementsByXPath(this.xpath, root);
      default:
       return this.matcher(root);
    }
  },

  match: function(element) {
    this.tokens = [];

    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
    var le, p, m;

    while (e && le !== e && (/\S/).test(e)) {
      le = e;
      for (var i in ps) {
        p = ps[i];
        if (m = e.match(p)) {
          // use the Selector.assertions methods unless the selector
          // is too complex.
          if (as[i]) {
            this.tokens.push([i, Object.clone(m)]);
            e = e.replace(m[0], '');
          } else {
            // reluctantly do a document-wide search
            // and look for a match in the array
            return this.findElements(document).include(element);
          }
        }
      }
    }

    var match = true, name, matches;
    for (var i = 0, token; token = this.tokens[i]; i++) {
      name = token[0], matches = token[1];
      if (!Selector.assertions[name](element, matches)) {
        match = false; break;
      }
    }

    return match;
  },

  toString: function() {
    return this.expression;
  },

  inspect: function() {
    return "#<Selector:" + this.expression.inspect() + ">";
  }
});

Object.extend(Selector, {
  _cache: { },

  xpath: {
    descendant:   "//*",
    child:        "/*",
    adjacent:     "/following-sibling::*[1]",
    laterSibling: '/following-sibling::*',
    tagName:      function(m) {
      if (m[1] == '*') return '';
      return "[local-name()='" + m[1].toLowerCase() +
             "' or local-name()='" + m[1].toUpperCase() + "']";
    },
    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
    id:           "[@id='#{1}']",
    attrPresence: function(m) {
      m[1] = m[1].toLowerCase();
      return new Template("[@#{1}]").evaluate(m);
    },
    attr: function(m) {
      m[1] = m[1].toLowerCase();
      m[3] = m[5] || m[6];
      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
    },
    pseudo: function(m) {
      var h = Selector.xpath.pseudos[m[1]];
      if (!h) return '';
      if (Object.isFunction(h)) return h(m);
      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
    },
    operators: {
      '=':  "[@#{1}='#{3}']",
      '!=': "[@#{1}!='#{3}']",
      '^=': "[starts-with(@#{1}, '#{3}')]",
      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
      '*=': "[contains(@#{1}, '#{3}')]",
      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
    },
    pseudos: {
      'first-child': '[not(preceding-sibling::*)]',
      'last-child':  '[not(following-sibling::*)]',
      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
      'checked':     "[@checked]",
      'disabled':    "[(@disabled) and (@type!='hidden')]",
      'enabled':     "[not(@disabled) and (@type!='hidden')]",
      'not': function(m) {
        var e = m[6], p = Selector.patterns,
            x = Selector.xpath, le, v;

        var exclusion = [];
        while (e && le != e && (/\S/).test(e)) {
          le = e;
          for (var i in p) {
            if (m = e.match(p[i])) {
              v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m);
              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
              e = e.replace(m[0], '');
              break;
            }
          }
        }
        return "[not(" + exclusion.join(" and ") + ")]";
      },
      'nth-child':      function(m) {
        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
      },
      'nth-last-child': function(m) {
        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
      },
      'nth-of-type':    function(m) {
        return Selector.xpath.pseudos.nth("position() ", m);
      },
      'nth-last-of-type': function(m) {
        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
      },
      'first-of-type':  function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
      },
      'last-of-type':   function(m) {
        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
      },
      'only-of-type':   function(m) {
        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
      },
      nth: function(fragment, m) {
        var mm, formula = m[6], predicate;
        if (formula == 'even') formula = '2n+0';
        if (formula == 'odd')  formula = '2n+1';
        if (mm = formula.match(/^(\d+)$/)) // digit only
          return '[' + fragment + "= " + mm[1] + ']';
        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
          if (mm[1] == "-") mm[1] = -1;
          var a = mm[1] ? Number(mm[1]) : 1;
          var b = mm[2] ? Number(mm[2]) : 0;
          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
          "((#{fragment} - #{b}) div #{a} >= 0)]";
          return new Template(predicate).evaluate({
            fragment: fragment, a: a, b: b });
        }
      }
    }
  },

  criteria: {
    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
    attr: function(m) {
      m[3] = (m[5] || m[6]);
      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
    },
    pseudo: function(m) {
      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
    },
    descendant:   'c = "descendant";',
    child:        'c = "child";',
    adjacent:     'c = "adjacent";',
    laterSibling: 'c = "laterSibling";'
  },

  patterns: {
    // combinators must be listed first
    // (and descendant needs to be last combinator)
    laterSibling: /^\s*~\s*/,
    child:        /^\s*>\s*/,
    adjacent:     /^\s*\+\s*/,
    descendant:   /^\s/,

    // selectors follow
    tagName:      /^\s*(\*|[\w\-]+)(\b|$)?/,
    id:           /^#([\w\-\*]+)(\b|$)/,
    className:    /^\.([\w\-\*]+)(\b|$)/,
    pseudo:
/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/,
    attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/,
    attr:         /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/
  },

  // for Selector.match and Element#match
  assertions: {
    tagName: function(element, matches) {
      return matches[1].toUpperCase() == element.tagName.toUpperCase();
    },

    className: function(element, matches) {
      return Element.hasClassName(element, matches[1]);
    },

    id: function(element, matches) {
      return element.id === matches[1];
    },

    attrPresence: function(element, matches) {
      return Element.hasAttribute(element, matches[1]);
    },

    attr: function(element, matches) {
      var nodeValue = Element.readAttribute(element, matches[1]);
      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
    }
  },

  handlers: {
    // UTILITY FUNCTIONS
    // joins two collections
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        a.push(node);
      return a;
    },

    // marks an array of nodes for counting
    mark: function(nodes) {
      var _true = Prototype.emptyFunction;
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = _true;
      return nodes;
    },

    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node._countedByPrototype = undefined;
      return nodes;
    },

    // mark each child node with its position (for nth calls)
    // "ofType" flag indicates whether we're indexing for nth-of-type
    // rather than nth-child
    index: function(parentNode, reverse, ofType) {
      parentNode._countedByPrototype = Prototype.emptyFunction;
      if (reverse) {
        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
          var node = nodes[i];
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
        }
      } else {
        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
      }
    },

    // filters out duplicates and extends all nodes
    unique: function(nodes) {
      if (nodes.length == 0) return nodes;
      var results = [], n;
      for (var i = 0, l = nodes.length; i < l; i++)
        if (!(n = nodes[i])._countedByPrototype) {
          n._countedByPrototype = Prototype.emptyFunction;
          results.push(Element.extend(n));
        }
      return Selector.handlers.unmark(results);
    },

    // COMBINATOR FUNCTIONS
    descendant: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, node.getElementsByTagName('*'));
      return results;
    },

    child: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        for (var j = 0, child; child = node.childNodes[j]; j++)
          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
      }
      return results;
    },

    adjacent: function(nodes) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        var next = this.nextElementSibling(node);
        if (next) results.push(next);
      }
      return results;
    },

    laterSibling: function(nodes) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        h.concat(results, Element.nextSiblings(node));
      return results;
    },

    nextElementSibling: function(node) {
      while (node = node.nextSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    previousElementSibling: function(node) {
      while (node = node.previousSibling)
        if (node.nodeType == 1) return node;
      return null;
    },

    // TOKEN FUNCTIONS
    tagName: function(nodes, root, tagName, combinator) {
      var uTagName = tagName.toUpperCase();
      var results = [], h = Selector.handlers;
      if (nodes) {
        if (combinator) {
          // fastlane for ordinary descendant combinators
          if (combinator == "descendant") {
            for (var i = 0, node; node = nodes[i]; i++)
              h.concat(results, node.getElementsByTagName(tagName));
            return results;
          } else nodes = this[combinator](nodes);
          if (tagName == "*") return nodes;
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.tagName.toUpperCase() === uTagName) results.push(node);
        return results;
      } else return root.getElementsByTagName(tagName);
    },

    id: function(nodes, root, id, combinator) {
      var targetNode = $(id), h = Selector.handlers;
      if (!targetNode) return [];
      if (!nodes && root == document) return [targetNode];
      if (nodes) {
        if (combinator) {
          if (combinator == 'child') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (targetNode.parentNode == node) return [targetNode];
          } else if (combinator == 'descendant') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Element.descendantOf(targetNode, node)) return [targetNode];
          } else if (combinator == 'adjacent') {
            for (var i = 0, node; node = nodes[i]; i++)
              if (Selector.handlers.previousElementSibling(targetNode) == node)
                return [targetNode];
          } else nodes = h[combinator](nodes);
        }
        for (var i = 0, node; node = nodes[i]; i++)
          if (node == targetNode) return [targetNode];
        return [];
      }
      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
    },

    className: function(nodes, root, className, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      return Selector.handlers.byClassName(nodes, root, className);
    },

    byClassName: function(nodes, root, className) {
      if (!nodes) nodes = Selector.handlers.descendant([root]);
      var needle = ' ' + className + ' ';
      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
        nodeClassName = node.className;
        if (nodeClassName.length == 0) continue;
        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
          results.push(node);
      }
      return results;
    },

    attrPresence: function(nodes, root, attr, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var results = [];
      for (var i = 0, node; node = nodes[i]; i++)
        if (Element.hasAttribute(node, attr)) results.push(node);
      return results;
    },

    attr: function(nodes, root, attr, value, operator, combinator) {
      if (!nodes) nodes = root.getElementsByTagName("*");
      if (nodes && combinator) nodes = this[combinator](nodes);
      var handler = Selector.operators[operator], results = [];
      for (var i = 0, node; node = nodes[i]; i++) {
        var nodeValue = Element.readAttribute(node, attr);
        if (nodeValue === null) continue;
        if (handler(nodeValue, value)) results.push(node);
      }
      return results;
    },

    pseudo: function(nodes, name, value, root, combinator) {
      if (nodes && combinator) nodes = this[combinator](nodes);
      if (!nodes) nodes = root.getElementsByTagName("*");
      return Selector.pseudos[name](nodes, value, root);
    }
  },

  pseudos: {
    'first-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.previousElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'last-child': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        if (Selector.handlers.nextElementSibling(node)) continue;
          results.push(node);
      }
      return results;
    },
    'only-child': function(nodes, value, root) {
      var h = Selector.handlers;
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
          results.push(node);
      return results;
    },
    'nth-child':        function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root);
    },
    'nth-last-child':   function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true);
    },
    'nth-of-type':      function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, false, true);
    },
    'nth-last-of-type': function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, formula, root, true, true);
    },
    'first-of-type':    function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, false, true);
    },
    'last-of-type':     function(nodes, formula, root) {
      return Selector.pseudos.nth(nodes, "1", root, true, true);
    },
    'only-of-type':     function(nodes, formula, root) {
      var p = Selector.pseudos;
      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
    },

    // handles the an+b logic
    getIndices: function(a, b, total) {
      if (a == 0) return b > 0 ? [b] : [];
      return $R(1, total).inject([], function(memo, i) {
        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
        return memo;
      });
    },

    // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type
    nth: function(nodes, formula, root, reverse, ofType) {
      if (nodes.length == 0) return [];
      if (formula == 'even') formula = '2n+0';
      if (formula == 'odd')  formula = '2n+1';
      var h = Selector.handlers, results = [], indexed = [], m;
      h.mark(nodes);
      for (var i = 0, node; node = nodes[i]; i++) {
        if (!node.parentNode._countedByPrototype) {
          h.index(node.parentNode, reverse, ofType);
          indexed.push(node.parentNode);
        }
      }
      if (formula.match(/^\d+$/)) { // just a number
        formula = Number(formula);
        for (var i = 0, node; node = nodes[i]; i++)
          if (node.nodeIndex == formula) results.push(node);
      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
        if (m[1] == "-") m[1] = -1;
        var a = m[1] ? Number(m[1]) : 1;
        var b = m[2] ? Number(m[2]) : 0;
        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
          for (var j = 0; j < l; j++)
            if (node.nodeIndex == indices[j]) results.push(node);
        }
      }
      h.unmark(nodes);
      h.unmark(indexed);
      return results;
    },

    'empty': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++) {
        // IE treats comments as element nodes
        if (node.tagName == '!' || node.firstChild) continue;
        results.push(node);
      }
      return results;
    },

    'not': function(nodes, selector, root) {
      var h = Selector.handlers, selectorType, m;
      var exclusions = new Selector(selector).findElements(root);
      h.mark(exclusions);
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node._countedByPrototype) results.push(node);
      h.unmark(exclusions);
      return results;
    },

    'enabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (!node.disabled && (!node.type || node.type !== 'hidden'))
          results.push(node);
      return results;
    },

    'disabled': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.disabled) results.push(node);
      return results;
    },

    'checked': function(nodes, value, root) {
      for (var i = 0, results = [], node; node = nodes[i]; i++)
        if (node.checked) results.push(node);
      return results;
    }
  },

  operators: {
    '=':  function(nv, v) { return nv == v; },
    '!=': function(nv, v) { return nv != v; },
    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
    '$=': function(nv, v) { return nv.endsWith(v); },
    '*=': function(nv, v) { return nv.include(v); },
    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
     '-').include('-' + (v || "").toUpperCase() + '-'); }
  },

  split: function(expression) {
    var expressions = [];
    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
      expressions.push(m[1].strip());
    });
    return expressions;
  },

  matchElements: function(elements, expression) {
    var matches = $$(expression), h = Selector.handlers;
    h.mark(matches);
    for (var i = 0, results = [], element; element = elements[i]; i++)
      if (element._countedByPrototype) results.push(element);
    h.unmark(matches);
    return results;
  },

  findElement: function(elements, expression, index) {
    if (Object.isNumber(expression)) {
      index = expression; expression = false;
    }
    return Selector.matchElements(elements, expression || '*')[index || 0];
  },

  findChildElements: function(element, expressions) {
    expressions = Selector.split(expressions.join(','));
    var results = [], h = Selector.handlers;
    for (var i = 0, l = expressions.length, selector; i < l; i++) {
      selector = new Selector(expressions[i].strip());
      h.concat(results, selector.findElements(element));
    }
    return (l > 1) ? h.unique(results) : results;
  }
});

if (Prototype.Browser.IE) {
  Object.extend(Selector.handlers, {
    // IE returns comment nodes on getElementsByTagName("*").
    // Filter them out.
    concat: function(a, b) {
      for (var i = 0, node; node = b[i]; i++)
        if (node.tagName !== "!") a.push(node);
      return a;
    },

    // IE improperly serializes _countedByPrototype in (inner|outer)HTML.
    unmark: function(nodes) {
      for (var i = 0, node; node = nodes[i]; i++)
        node.removeAttribute('_countedByPrototype');
      return nodes;
    }
  });
}

function $$() {
  return Selector.findChildElements(document, $A(arguments));
}
var Form = {
  reset: function(form) {
    $(form).reset();
    return form;
  },

  serializeElements: function(elements, options) {
    if (typeof options != 'object') options = { hash: !!options };
    else if (Object.isUndefined(options.hash)) options.hash = true;
    var key, value, submitted = false, submit = options.submit;

    var data = elements.inject({ }, function(result, element) {
      if (!element.disabled && element.name) {
        key = element.name; value = $(element).getValue();
        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
            submit !== false && (!submit || key == submit) && (submitted = true)))) {
          if (key in result) {
            // a key is already present; construct an array of values
            if (!Object.isArray(result[key])) result[key] = [result[key]];
            result[key].push(value);
          }
          else result[key] = value;
        }
      }
      return result;
    });

    return options.hash ? data : Object.toQueryString(data);
  }
};

Form.Methods = {
  serialize: function(form, options) {
    return Form.serializeElements(Form.getElements(form), options);
  },

  getElements: function(form) {
    return $A($(form).getElementsByTagName('*')).inject([],
      function(elements, child) {
        if (Form.Element.Serializers[child.tagName.toLowerCase()])
          elements.push(Element.extend(child));
        return elements;
      }
    );
  },

  getInputs: function(form, typeName, name) {
    form = $(form);
    var inputs = form.getElementsByTagName('input');

    if (!typeName && !name) return $A(inputs).map(Element.extend);

    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
      var input = inputs[i];
      if ((typeName && input.type != typeName) || (name && input.name != name))
        continue;
      matchingInputs.push(Element.extend(input));
    }

    return matchingInputs;
  },

  disable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('disable');
    return form;
  },

  enable: function(form) {
    form = $(form);
    Form.getElements(form).invoke('enable');
    return form;
  },

  findFirstElement: function(form) {
    var elements = $(form).getElements().findAll(function(element) {
      return 'hidden' != element.type && !element.disabled;
    });
    var firstByIndex = elements.findAll(function(element) {
      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
    }).sortBy(function(element) { return element.tabIndex }).first();

    return firstByIndex ? firstByIndex : elements.find(function(element) {
      return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase());
    });
  },

  focusFirstElement: function(form) {
    form = $(form);
    form.findFirstElement().activate();
    return form;
  },

  request: function(form, options) {
    form = $(form), options = Object.clone(options || { });

    var params = options.parameters, action = form.readAttribute('action') || '';
    if (action.blank()) action = window.location.href;
    options.parameters = form.serialize(true);

    if (params) {
      if (Object.isString(params)) params = params.toQueryParams();
      Object.extend(options.parameters, params);
    }

    if (form.hasAttribute('method') && !options.method)
      options.method = form.method;

    return new Ajax.Request(action, options);
  }
};

/*--------------------------------------------------------------------------*/

Form.Element = {
  focus: function(element) {
    $(element).focus();
    return element;
  },

  select: function(element) {
    $(element).select();
    return element;
  }
};

Form.Element.Methods = {
  serialize: function(element) {
    element = $(element);
    if (!element.disabled && element.name) {
      var value = element.getValue();
      if (value != undefined) {
        var pair = { };
        pair[element.name] = value;
        return Object.toQueryString(pair);
      }
    }
    return '';
  },

  getValue: function(element) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    return Form.Element.Serializers[method](element);
  },

  setValue: function(element, value) {
    element = $(element);
    var method = element.tagName.toLowerCase();
    Form.Element.Serializers[method](element, value);
    return element;
  },

  clear: function(element) {
    $(element).value = '';
    return element;
  },

  present: function(element) {
    return $(element).value != '';
  },

  activate: function(element) {
    element = $(element);
    try {
      element.focus();
      if (element.select && (element.tagName.toLowerCase() != 'input' ||
          !['button', 'reset', 'submit'].include(element.type)))
        element.select();
    } catch (e) { }
    return element;
  },

  disable: function(element) {
    element = $(element);
    element.disabled = true;
    return element;
  },

  enable: function(element) {
    element = $(element);
    element.disabled = false;
    return element;
  }
};

/*--------------------------------------------------------------------------*/

var Field = Form.Element;
var $F = Form.Element.Methods.getValue;

/*--------------------------------------------------------------------------*/

Form.Element.Serializers = {
  input: function(element, value) {
    switch (element.type.toLowerCase()) {
      case 'checkbox':
      case 'radio':
        return Form.Element.Serializers.inputSelector(element, value);
      default:
        return Form.Element.Serializers.textarea(element, value);
    }
  },

  inputSelector: function(element, value) {
    if (Object.isUndefined(value)) return element.checked ? element.value : null;
    else element.checked = !!value;
  },

  textarea: function(element, value) {
    if (Object.isUndefined(value)) return element.value;
    else element.value = value;
  },

  select: function(element, value) {
    if (Object.isUndefined(value))
      return this[element.type == 'select-one' ?
        'selectOne' : 'selectMany'](element);
    else {
      var opt, currentValue, single = !Object.isArray(value);
      for (var i = 0, length = element.length; i < length; i++) {
        opt = element.options[i];
        currentValue = this.optionValue(opt);
        if (single) {
          if (currentValue == value) {
            opt.selected = true;
            return;
          }
        }
        else opt.selected = value.include(currentValue);
      }
    }
  },

  selectOne: function(element) {
    var index = element.selectedIndex;
    return index >= 0 ? this.optionValue(element.options[index]) : null;
  },

  selectMany: function(element) {
    var values, length = element.length;
    if (!length) return null;

    for (var i = 0, values = []; i < length; i++) {
      var opt = element.options[i];
      if (opt.selected) values.push(this.optionValue(opt));
    }
    return values;
  },

  optionValue: function(opt) {
    // extend element because hasAttribute may not be native
    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
  }
};

/*--------------------------------------------------------------------------*/

Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
  initialize: function($super, element, frequency, callback) {
    $super(callback, frequency);
    this.element   = $(element);
    this.lastValue = this.getValue();
  },

  execute: function() {
    var value = this.getValue();
    if (Object.isString(this.lastValue) && Object.isString(value) ?
        this.lastValue != value : String(this.lastValue) != String(value)) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  }
});

Form.Element.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.Observer = Class.create(Abstract.TimedObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});

/*--------------------------------------------------------------------------*/

Abstract.EventObserver = Class.create({
  initialize: function(element, callback) {
    this.element  = $(element);
    this.callback = callback;

    this.lastValue = this.getValue();
    if (this.element.tagName.toLowerCase() == 'form')
      this.registerFormCallbacks();
    else
      this.registerCallback(this.element);
  },

  onElementEvent: function() {
    var value = this.getValue();
    if (this.lastValue != value) {
      this.callback(this.element, value);
      this.lastValue = value;
    }
  },

  registerFormCallbacks: function() {
    Form.getElements(this.element).each(this.registerCallback, this);
  },

  registerCallback: function(element) {
    if (element.type) {
      switch (element.type.toLowerCase()) {
        case 'checkbox':
        case 'radio':
          Event.observe(element, 'click', this.onElementEvent.bind(this));
          break;
        default:
          Event.observe(element, 'change', this.onElementEvent.bind(this));
          break;
      }
    }
  }
});

Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.Element.getValue(this.element);
  }
});

Form.EventObserver = Class.create(Abstract.EventObserver, {
  getValue: function() {
    return Form.serialize(this.element);
  }
});
if (!window.Event) var Event = { };

Object.extend(Event, {
  KEY_BACKSPACE: 8,
  KEY_TAB:       9,
  KEY_RETURN:   13,
  KEY_ESC:      27,
  KEY_LEFT:     37,
  KEY_UP:       38,
  KEY_RIGHT:    39,
  KEY_DOWN:     40,
  KEY_DELETE:   46,
  KEY_HOME:     36,
  KEY_END:      35,
  KEY_PAGEUP:   33,
  KEY_PAGEDOWN: 34,
  KEY_INSERT:   45,

  cache: { },

  relatedTarget: function(event) {
    var element;
    switch(event.type) {
      case 'mouseover': element = event.fromElement; break;
      case 'mouseout':  element = event.toElement;   break;
      default: return null;
    }
    return Element.extend(element);
  }
});

Event.Methods = (function() {
  var isButton;

  if (Prototype.Browser.IE) {
    var buttonMap = { 0: 1, 1: 4, 2: 2 };
    isButton = function(event, code) {
      return event.button == buttonMap[code];
    };

  } else if (Prototype.Browser.WebKit) {
    isButton = function(event, code) {
      switch (code) {
        case 0: return event.which == 1 && !event.metaKey;
        case 1: return event.which == 1 && event.metaKey;
        default: return false;
      }
    };

  } else {
    isButton = function(event, code) {
      return event.which ? (event.which === code + 1) : (event.button === code);
    };
  }

  return {
    isLeftClick:   function(event) { return isButton(event, 0) },
    isMiddleClick: function(event) { return isButton(event, 1) },
    isRightClick:  function(event) { return isButton(event, 2) },

    element: function(event) {
      event = Event.extend(event);

      var node          = event.target,
          type          = event.type,
          currentTarget = event.currentTarget;

      if (currentTarget && currentTarget.tagName) {
        // Firefox screws up the "click" event when moving between radio buttons
        // via arrow keys. It also screws up the "load" and "error" events on images,
        // reporting the document as the target instead of the original image.
        if (type === 'load' || type === 'error' ||
          (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
            && currentTarget.type === 'radio'))
              node = currentTarget;
      }
      if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
      return Element.extend(node);
    },

    findElement: function(event, expression) {
      var element = Event.element(event);
      if (!expression) return element;
      var elements = [element].concat(element.ancestors());
      return Selector.findElement(elements, expression, 0);
    },

    pointer: function(event) {
      var docElement = document.documentElement,
      body = document.body || { scrollLeft: 0, scrollTop: 0 };
      return {
        x: event.pageX || (event.clientX +
          (docElement.scrollLeft || body.scrollLeft) -
          (docElement.clientLeft || 0)),
        y: event.pageY || (event.clientY +
          (docElement.scrollTop || body.scrollTop) -
          (docElement.clientTop || 0))
      };
    },

    pointerX: function(event) { return Event.pointer(event).x },
    pointerY: function(event) { return Event.pointer(event).y },

    stop: function(event) {
      Event.extend(event);
      event.preventDefault();
      event.stopPropagation();
      event.stopped = true;
    }
  };
})();

Event.extend = (function() {
  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
    m[name] = Event.Methods[name].methodize();
    return m;
  });

  if (Prototype.Browser.IE) {
    Object.extend(methods, {
      stopPropagation: function() { this.cancelBubble = true },
      preventDefault:  function() { this.returnValue = false },
      inspect: function() { return "[object Event]" }
    });

    return function(event) {
      if (!event) return false;
      if (event._extendedByPrototype) return event;

      event._extendedByPrototype = Prototype.emptyFunction;
      var pointer = Event.pointer(event);
      Object.extend(event, {
        target: event.srcElement,
        relatedTarget: Event.relatedTarget(event),
        pageX:  pointer.x,
        pageY:  pointer.y
      });
      return Object.extend(event, methods);
    };

  } else {
    Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
    Object.extend(Event.prototype, methods);
    return Prototype.K;
  }
})();

Object.extend(Event, (function() {
  var cache = Event.cache;

  function getEventID(element) {
    if (element._prototypeEventID) return element._prototypeEventID[0];
    arguments.callee.id = arguments.callee.id || 1;
    return element._prototypeEventID = [++arguments.callee.id];
  }

  function getDOMEventName(eventName) {
    if (eventName && eventName.include(':')) return "dataavailable";
    return eventName;
  }

  function getCacheForID(id) {
    return cache[id] = cache[id] || { };
  }

  function getWrappersForEventName(id, eventName) {
    var c = getCacheForID(id);
    return c[eventName] = c[eventName] || [];
  }

  function createWrapper(element, eventName, handler) {
    var id = getEventID(element);
    var c = getWrappersForEventName(id, eventName);
    if (c.pluck("handler").include(handler)) return false;

    var wrapper = function(event) {
      if (!Event || !Event.extend ||
        (event.eventName && event.eventName != eventName))
          return false;

      Event.extend(event);
      handler.call(element, event);
    };

    wrapper.handler = handler;
    c.push(wrapper);
    return wrapper;
  }

  function findWrapper(id, eventName, handler) {
    var c = getWrappersForEventName(id, eventName);
    return c.find(function(wrapper) { return wrapper.handler == handler });
  }

  function destroyWrapper(id, eventName, handler) {
    var c = getCacheForID(id);
    if (!c[eventName]) return false;
    c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
  }

  function destroyCache() {
    for (var id in cache)
      for (var eventName in cache[id])
        cache[id][eventName] = null;
  }


  // Internet Explorer needs to remove event handlers on page unload
  // in order to avoid memory leaks.
  if (window.attachEvent) {
    window.attachEvent("onunload", destroyCache);
  }

  // Safari has a dummy event handler on page unload so that it won't
  // use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
  // object when page is returned to via the back button using its bfcache.
  if (Prototype.Browser.WebKit) {
    window.addEventListener('unload', Prototype.emptyFunction, false);
  }

  return {
    observe: function(element, eventName, handler) {
      element = $(element);
      var name = getDOMEventName(eventName);

      var wrapper = createWrapper(element, eventName, handler);
      if (!wrapper) return element;

      if (element.addEventListener) {
        element.addEventListener(name, wrapper, false);
      } else {
        element.attachEvent("on" + name, wrapper);
      }

      return element;
    },

    stopObserving: function(element, eventName, handler) {
      element = $(element);
      var id = getEventID(element), name = getDOMEventName(eventName);

      if (!handler && eventName) {
        getWrappersForEventName(id, eventName).each(function(wrapper) {
          element.stopObserving(eventName, wrapper.handler);
        });
        return element;

      } else if (!eventName) {
        Object.keys(getCacheForID(id)).each(function(eventName) {
          element.stopObserving(eventName);
        });
        return element;
      }

      var wrapper = findWrapper(id, eventName, handler);
      if (!wrapper) return element;

      if (element.removeEventListener) {
        element.removeEventListener(name, wrapper, false);
      } else {
        element.detachEvent("on" + name, wrapper);
      }

      destroyWrapper(id, eventName, handler);

      return element;
    },

    fire: function(element, eventName, memo) {
      element = $(element);
      if (element == document && document.createEvent && !element.dispatchEvent)
        element = document.documentElement;

      var event;
      if (document.createEvent) {
        event = document.createEvent("HTMLEvents");
        event.initEvent("dataavailable", true, true);
      } else {
        event = document.createEventObject();
        event.eventType = "ondataavailable";
      }

      event.eventName = eventName;
      event.memo = memo || { };

      if (document.createEvent) {
        element.dispatchEvent(event);
      } else {
        element.fireEvent(event.eventType, event);
      }

      return Event.extend(event);
    }
  };
})());

Object.extend(Event, Event.Methods);

Element.addMethods({
  fire:          Event.fire,
  observe:       Event.observe,
  stopObserving: Event.stopObserving
});

Object.extend(document, {
  fire:          Element.Methods.fire.methodize(),
  observe:       Element.Methods.observe.methodize(),
  stopObserving: Element.Methods.stopObserving.methodize(),
  loaded:        false
});

(function() {
  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
     Matthias Miller, Dean Edwards and John Resig. */

  var timer;

  function fireContentLoadedEvent() {
    if (document.loaded) return;
    if (timer) window.clearInterval(timer);
    document.fire("dom:loaded");
    document.loaded = true;
  }

  if (document.addEventListener) {
    if (Prototype.Browser.WebKit) {
      timer = window.setInterval(function() {
        if (/loaded|complete/.test(document.readyState))
          fireContentLoadedEvent();
      }, 0);

      Event.observe(window, "load", fireContentLoadedEvent);

    } else {
      document.addEventListener("DOMContentLoaded",
        fireContentLoadedEvent, false);
    }

  } else {
    document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
    $("__onDOMContentLoaded").onreadystatechange = function() {
      if (this.readyState == "complete") {
        this.onreadystatechange = null;
        fireContentLoadedEvent();
      }
    };
  }
})();
/*------------------------------- DEPRECATED -------------------------------*/

Hash.toQueryString = Object.toQueryString;

var Toggle = { display: Element.toggle };

Element.Methods.childOf = Element.Methods.descendantOf;

var Insertion = {
  Before: function(element, content) {
    return Element.insert(element, {before:content});
  },

  Top: function(element, content) {
    return Element.insert(element, {top:content});
  },

  Bottom: function(element, content) {
    return Element.insert(element, {bottom:content});
  },

  After: function(element, content) {
    return Element.insert(element, {after:content});
  }
};

var $continue = new Error('"throw $continue" is deprecated, use "return" instead');

// This should be moved to script.aculo.us; notice the deprecated methods
// further below, that map to the newer Element methods.
var Position = {
  // set to true if needed, warning: firefox performance problems
  // NOT neeeded for page scrolling, only if draggable contained in
  // scrollable elements
  includeScrollOffsets: false,

  // must be called before calling withinIncludingScrolloffset, every time the
  // page is scrolled
  prepare: function() {
    this.deltaX =  window.pageXOffset
                || document.documentElement.scrollLeft
                || document.body.scrollLeft
                || 0;
    this.deltaY =  window.pageYOffset
                || document.documentElement.scrollTop
                || document.body.scrollTop
                || 0;
  },

  // caches x/y coordinate pair to use with overlap
  within: function(element, x, y) {
    if (this.includeScrollOffsets)
      return this.withinIncludingScrolloffsets(element, x, y);
    this.xcomp = x;
    this.ycomp = y;
    this.offset = Element.cumulativeOffset(element);

    return (y >= this.offset[1] &&
            y <  this.offset[1] + element.offsetHeight &&
            x >= this.offset[0] &&
            x <  this.offset[0] + element.offsetWidth);
  },

  withinIncludingScrolloffsets: function(element, x, y) {
    var offsetcache = Element.cumulativeScrollOffset(element);

    this.xcomp = x + offsetcache[0] - this.deltaX;
    this.ycomp = y + offsetcache[1] - this.deltaY;
    this.offset = Element.cumulativeOffset(element);

    return (this.ycomp >= this.offset[1] &&
            this.ycomp <  this.offset[1] + element.offsetHeight &&
            this.xcomp >= this.offset[0] &&
            this.xcomp <  this.offset[0] + element.offsetWidth);
  },

  // within must be called directly before
  overlap: function(mode, element) {
    if (!mode) return 0;
    if (mode == 'vertical')
      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
        element.offsetHeight;
    if (mode == 'horizontal')
      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
        element.offsetWidth;
  },

  // Deprecation layer -- use newer Element methods now (1.5.2).

  cumulativeOffset: Element.Methods.cumulativeOffset,

  positionedOffset: Element.Methods.positionedOffset,

  absolutize: function(element) {
    Position.prepare();
    return Element.absolutize(element);
  },

  relativize: function(element) {
    Position.prepare();
    return Element.relativize(element);
  },

  realOffset: Element.Methods.cumulativeScrollOffset,

  offsetParent: Element.Methods.getOffsetParent,

  page: Element.Methods.viewportOffset,

  clone: function(source, target, options) {
    options = options || { };
    return Element.clonePosition(target, source, options);
  }
};

/*--------------------------------------------------------------------------*/

if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
  function iter(name) {
    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
  }

  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
  function(element, className) {
    className = className.toString().strip();
    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
  } : function(element, className) {
    className = className.toString().strip();
    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
    if (!classNames && !className) return elements;

    var nodes = $(element).getElementsByTagName('*');
    className = ' ' + className + ' ';

    for (var i = 0, child, cn; child = nodes[i]; i++) {
      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
          (classNames && classNames.all(function(name) {
            return !name.toString().blank() && cn.include(' ' + name + ' ');
          }))))
        elements.push(Element.extend(child));
    }
    return elements;
  };

  return function(className, parentElement) {
    return $(parentElement || document.body).getElementsByClassName(className);
  };
}(Element.Methods);

/*--------------------------------------------------------------------------*/

Element.ClassNames = Class.create();
Element.ClassNames.prototype = {
  initialize: function(element) {
    this.element = $(element);
  },

  _each: function(iterator) {
    this.element.className.split(/\s+/).select(function(name) {
      return name.length > 0;
    })._each(iterator);
  },

  set: function(className) {
    this.element.className = className;
  },

  add: function(classNameToAdd) {
    if (this.include(classNameToAdd)) return;
    this.set($A(this).concat(classNameToAdd).join(' '));
  },

  remove: function(classNameToRemove) {
    if (!this.include(classNameToRemove)) return;
    this.set($A(this).without(classNameToRemove).join(' '));
  },

  toString: function() {
    return $A(this).join(' ');
  }
};

Object.extend(Element.ClassNames.prototype, Enumerable);

/*--------------------------------------------------------------------------*/

Element.addMethods();
;
/* 
 * MATRIX CLASSES
 *
 * Matrix
 * Reservation
 * Restriction
 */

String.prototype.splitOnUnderscore = String.prototype.split.curry("_");

Array.prototype.multiSort = function(index) {
	for(var i=0; i<this.length; i++) {
		var temp = this[i].splice(index,1);
		this[i].unshift(temp);
	}
return merge_sort(this,function (a,b) {return a[0][0] == b[0][0] ? 0 : (a[0][0] < b[0][0] ? -1 : 1)});
//	return this.sort(function (a,b) {return a[0] == b[0] ? 0 : (a[0] < b[0] ? -1 : 1)});
} 

/**
 * Matrix Class
 */
function Matrix(requestedId, startDate, displayDays, languageCode, showOnRequest) {
	//full parameter is (requestedId, startDate, displayDays, languageCode, showOnRequest, skin, usePagination, resultPerPage)
	//normally send only (requestedId, startDate, displayDays, languageCode, showOnRequest)
	//for asiawebdirect.com destination page, we will show with skin "list" and show 12 hotels per page
	//so asiawebdirect.com will send (requestedId, startDate, displayDays, languageCode, showOnRequest, "list", true, 10)
	var tmp_fix_skin = (arguments[5]) ? arguments[5] : 'matrix';
	var tmp_usePagination = (arguments[6]) ? arguments[6] : false;
	var tmp_resultPerPage = (arguments[7]) ? arguments[7] : 5;
	if((tmp_fix_skin=="list") || (tmp_fix_skin=="matrix") || (tmp_fix_skin=="photo_list")) {}
	else {tmp_fix_skin = "matrix";}

	try {if(tmp_usePagination!=true) tmp_usePagination = false;} catch (e) {tmp_usePagination = false;}
	try {if(tmp_resultPerPage<=0) tmp_resultPerPage = 12;} catch (e) {tmp_resultPerPage = 12;}

	this.loaded = false;
    this.requestedId = requestedId;
    this.startDate = startDate;
    this.days = displayDays;
    this.endDate = "";
    this.languageCode = languageCode;
    this.showOnRequest = showOnRequest;
    this.matrix = null;
    this.checkIn = "";
    this.checkOut = "";
    
    this.renderFilter = true;
    
    this.extractQueryParams();

    this.reservation = null;
    this.lastRatePlan = null;
    this.lastRatePlanClone = null;
    this.lastRatePlanBlurb = null;
    this.restrictions = new Restriction();
    
    this.xDomain = true;
    this.destMatrix = false;
    this.domainName = 'localhost';
    this.skin = tmp_fix_skin;
    
    this.matrixType = "Hotel";
    
    this.matrixActions = new Array();
    this.matrixActions["Hotel"] = "hotel";
    this.matrixActions["Destination"] = "dest";
    this.matrixActions["MultipleHotel"] = "hotels";
    this.matrixActions["AccomType"] = "searchType";
    
    this.resultPerPage = tmp_resultPerPage;
    this.pageNo = 1;
    this.usePagination = tmp_usePagination;
    
    this.sortDirection = {"Popular": -1, "HotelName": 1, "Category": -1, "LowRate": 1, "GuestRating": -1, "Price": 1, "PriceDesc" : -1, "Header": 1};
    this.curSortBy = "Popular";
    this.mapResultFieldName = {"HotelName": "name", "Category": "rating", "GuestRating": "guest_rating", "Price": "lowestRateNum", "PriceDesc" : "lowestRateNum", "Header": "header_idx", "Popular" : "popularity" }
}

	Matrix.prototype.sortData = function(by) {
		// need to sort by hotel name first for price and star rating
		if (by=="Category" || by=="Price") {
			var current = this.curSortBy;
			var nameDirection = this.sortDirection[by];
			this.sortDirection["HotelName"] = 1;
			this.curSortBy = 'HotelName';
			this.sort();
			this.curSortBy = current;
			this.sortDirection["HotelName"] = nameDirection;
		}
		if (arguments[1] && this.curSortBy==by) this.sortDirection[by] = this.sortDirection[by] * -1;
		this.curSortBy = by;
		this.sort();

		// if there is grouping, sort again by group header
		if (this.data.places) {
			this.curSortBy = 'Header';
			this.sort();
			this.curSortBy = by;
		}
	}

	Matrix.prototype.sortBy = function(by) {
		this.sortData(by, true); // 2nd parameter force the sort data function not to toggle sort direction.
		this.renderFilter = false;
		this.setSkin(this.skin);
	}
	
	Matrix.prototype.sort = function() {
		// we sort both data.results and data.showResults
		// so we can filter and display it correctly afterward
		var by = this.curSortBy;
		var asc = this.sortDirection[by];
		var field_name = this.mapResultFieldName[by];
		if (typeof(this.data) != "undefined")
		{
			switch (by) {
			    case "Popular":
					this.data.results = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					break;

				case "Price":
					this.data.results = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					break;
					
				case "Category":
					this.data.results = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					break;
					
				case "HotelName":
					this.data.results = merge_sort(this.data.results, this._stringComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._stringComparator(field_name, asc)); 
					break;
					
				case "GuestRating":
					this.data.results = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					break;
					
				case "Header":
					this.data.results = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					this.data.showResults = merge_sort(this.data.results, this._numComparator(field_name, asc)); 
					break;

				default: return;
			}
		}
	}
	
	Matrix.prototype._numComparator = function(by, asc) {
		return function (a, b) { return (parseFloat(new Number(a[by])) - parseFloat(new Number(b[by]))) * asc; };
	}
	
	Matrix.prototype._stringComparator = function(by, asc) {
		return function (a, b) {
			if ( a[by].toLowerCase() < b[by].toLowerCase() ) return -1 * asc;
			if ( a[by].toLowerCase() > b[by].toLowerCase() ) return 1 * asc;
			return 0;
		};
	} 

	Matrix.prototype.filterData = function() {
		this.filter = new Filter();
		this.filter.currencyCode = this.data.currency;
		this.filter.loadFilters();
		
		this.data.totalShowResult = 0;
		this.data.showResults = new Array();
		for (var i=0; i<this.data.results.length; i++) {
			if (this.filter.computeFilters(this.data.results[i])) {
				this.data.results[i].Show = true;
				this.data.showResults.push(this.data.results[i]);
				this.data.totalShowResult +=1 ;
			} else {
				this.data.results[i].Show = false;
			}
		}
		
		// Featured Hotel
		this.data.showFeaturedHotels = new Array();
		if (this.data.featured) {
			for (var i=0; i < this.data.featured.length; i++) {
				if (this.filter.computeFilters(this.data.featured[i])) {
					this.data.featured[i].Show = true;
					this.data.showFeaturedHotels.push(this.data.featured[i]);
				} else {
					this.data.featured[i].Show = false;
				}
			}
		}
		
		// reset page
		this.pageNo = 1;
		this.renderFilter = false;
	}

	Matrix.prototype.doFilter = function() {
		this.filterData();
		if (this.matrixType != "MultipleHotel") {
		    document.fire("searchResults:listChange", this.data.showResults);
		}
		this.setSkin(this.skin);
	}
	
	Matrix.prototype.sortShow = function() {
		this.data.results.sort(function(a, b) { if(b.Show>a.Show) { return 1 } else { return 0 } });
	}

	Matrix.prototype.setUsePagination = function(setValue) {
		this.usePagination = setValue;
	}
	
	Matrix.prototype.movePrevious = function() {
		this.pageNo -=1;
		this.renderFilter = false;
		this.setSkin(this.skin);
	}

	Matrix.prototype.moveNext = function() {
		this.pageNo +=1;
		this.renderFilter = false;
		this.setSkin(this.skin);
	}
	
	Matrix.prototype.gotoPage = function(pageNo) {
		this.pageNo = parseInt(pageNo);
		this.renderFilter = false;
		this.setSkin(this.skin);
	}
	
	Matrix.prototype.setResultPerPage = function(resultNo) {
		this.resultPerPage = resultNo;
	}

    Matrix.prototype.enableXDomain = function(domainName) {
    	this.domainName = domainName;
    }
    
    Matrix.prototype.setMatrixType = function(matrixType) {
    	this.matrixType = matrixType;
    }
    
    Matrix.prototype.setRequestedId = function(requestedId) {
    	this.requestedId = requestedId;
    }

    Matrix.prototype.setParacheckInOut = function(_tmp_checkIn, _tmp_checkOut) {
		if(String(_tmp_checkIn)!="null")
		if(String(_tmp_checkOut)!="null")
		if(String(_tmp_checkIn).substring(0,1)=="2")
		if(String(_tmp_checkOut).substring(0,1)=="2")
		{
        	var checkIn = strToDate(_tmp_checkIn);
        	var my_date = new Date();
        	var to_day = new Date(my_date.getFullYear(), my_date.getMonth(), my_date.getDate());
        	if (checkIn >= to_day) {
				this.checkIn = _tmp_checkIn;
				this.checkOut = _tmp_checkOut;
				this.startDate = 'checkIn';
        	}
		}
		this.loadMatrix();
    }

    Matrix.prototype.setCookie = function(requestedId) {
		var url = "/asahi/setcookie.php?mtxSetCookie=1&mtxCheckIn=" + this.checkIn + "&mtxCheckOut=" + this.checkOut + "&tm="+(Math.random());
		this._doMatrixRequest(url);
    }

    Matrix.prototype.extractQueryParams = function() {
        var checkInParam  = queryParam("mtxCheckIn");
        var checkOutParam = queryParam("mtxCheckOut");
        if (checkInParam != '' && checkOutParam != '') {
        	var checkIn = strToDate(checkInParam);
        	var my_date = new Date();
        	var to_day = new Date(my_date.getFullYear(), my_date.getMonth(), my_date.getDate());
        	
        	if (checkIn < to_day) return;

        	this.checkIn   = checkInParam;
        	this.checkOut  = checkOutParam;
        	this.startDate = 'checkIn';
        }
        
        // allow query params to specify portal
        var portal = queryParam('mtxPortal');
        if (portal != '') {
        	window.mtx_portal = portal;
        }
    }
    
    Matrix.prototype.extractCookie = function() {
		if((this.checkIn==null) || (this.checkIn==""))
		{
			var url = "/asahi/setcookie.php?tm="+(Math.random());
			this._doMatrixRequest(url);
		}
		else
		{
			this.loadMatrix();
		}
	}

    Matrix.prototype.loadMatrix = function () {
    	var action = this.matrixActions[this.matrixType];
        url = "/asahi/frontend.php/rates/" + action + "/" +this.languageCode + "/"  + this.requestedId+ "/" + this.startDate  + "/" + this.days  + "/" + this.showOnRequest;
        
        if (this.checkIn && this.checkIn != "")
        	url = url + "/" + this.checkIn  + "/" + this.checkOut;
        document.fire("matrix:loading", this);
        this._doMatrixRequest(url);
    }

    Matrix.prototype._doMatrixRequest = function (url) {
        url = 'http://' + this.domainName + url;
        var headNode = document.getElementsByTagName("head")[0];
        var script   = document.createElement('script');
        script.type  = 'text/javascript';
        script.src   = url;
        headNode.appendChild(script);
    }
    
    Matrix.prototype.displayMatrix = function(data) {
    	this.displayReady = false;
    	this.waitingForDisplayCurrency = false;
    	if (this.matrixType=="AccomType" || this.matrixType=="MultipleHotel") {
    		// multi-currencies matrix, have to set display matrix
    		// if no display currency cookies, get currency by IP
    		if (!getCookie('ccCodePersist')) {
    			this.waitingForDisplayCurrency = true;
    			this._doMatrixRequest('/asahi/frontend_dev.php/geoip/currency');
    		}
    	}
        this.lastRatePlan = null;
        this.lastRatePlanClone = null;
        this.lastRatePlanBlurb = null;
        if (data["detail"]) {
        	this.beginDate = data["detail"]["beginDate"];
            this.startDate = data["detail"]["startDate"];
            this.endDate   = data["detail"]["endDate"];
            if (data["detail"]["checkIn"]) this.checkIn   = data["detail"]["checkIn"];
            if (data["detail"]["checkOut"]) this.checkOut  = data["detail"]["checkOut"];
            
            this.numCols   = data["detail"]["numCols"];
        }
        if (this.data != null) {
        	this.accomList = this.data.accomList;
        } else {
        	this.accomList = [];
        }
        this.data = data;
        this.filterData();
        if (this.matrixType != "Hotel") {
            this.sortData(this.curSortBy);
        }
        this.applySkin(data);
        this.accomList = this.data.accomList;
        if (!this.loaded) this.doInitialLoad(data);
        this.restrictions.initialise(data["results"]);
        
        if (getCookie('mtxBookCheckInDate')!=null && getCookie('mtxBookCheckOutDate')!=null) {
        	this.checkIn = getCookie('mtxBookCheckInDate');
        	this.checkOut = getCookie('mtxBookCheckOutDate');
        	delCookie('mtxBookCheckInDate');
        	delCookie('mtxBookCheckOutDate');
        }
        
        if (this.beginDate && this.checkIn && this.checkOut) {
        	// Check beginDate with checkInDate for each timezone.
            if (this.checkIn < this.beginDate) { this.checkIn = data["detail"]["beginDate"] }
            if (this.checkOut <= this.beginDate) { this.checkOut = formatDate(incrementDate(strToDate(data["detail"]["beginDate"]), 1)) }
        }
        
        if (this.checkIn && this.checkOut) {
        	if (!this.reservation) {
        		this.reservation = new Reservation();
        		this.reservation.setNights(strToDate(this.checkIn), strToDate(this.checkOut));
        	}
        }
        
        if (this.reservation) {
        	if (this.reservation.numNights>0) {
        		this.reservation.setRatePlanInvalidDates();
        	}
        	this.reservation.setDateSelected();
        }
        
        document.fire("matrix:checkInOutSetExternaly", this); // synchronize calendar dates with matrix checkin/checkout dates
        this.displayReady = true;
        this.loaded = true;
    	if (!this.waitingForDisplayCurrency) {
    		document.fire("matrix:loaded", this);
    	}
    	
        if (this.data.results.length == 0) {
        	document.fire("matrix:noResults", this);
        }
    }
    
    Matrix.prototype.setDisplayCurrencyExternally = function(currencyCode) {
    	if (this.displayReady) {
    		document.fire("matrix:loaded", this);
    	} else {
    		this.waitingForDisplayCurrency = false;
    	}
    }
    
    Matrix.prototype.doInitialLoad = function(data) {
		// tooltip
		var ttDiv = document.createElement("div");
		ttDiv.id = "mtx_rateBlurb";
		ttDiv.className = "popup-container";
		ttDiv.style.display = 'none';
		
		div_top = document.createElement("div");
		div_top.className = "popup-top";
		ttDiv.appendChild(div_top);
		
		div_body = document.createElement("div");
		div_body.className = "popup-body";
		ttDiv.appendChild(div_body);
		
		div_bottom = document.createElement("div");
		div_bottom.className = "popup-bottom";
		ttDiv.appendChild(div_bottom);
		
		

	    ttP = document.createElement("p");
	    ttP.id = "mtx_rateBlurbMsg";
	    div_body.appendChild(ttP);

		document.body.appendChild(ttDiv);

		// Error
		var ttDiv = document.createElement("div");
		ttDiv.id = "dealSelectionError";
		ttDiv.className = "dealselectionerror";
		ttDiv.style.display = 'none';

	    ttP = document.createElement("p");
	    ttP.id = "mtx_errorMsg";
	    ttDiv.appendChild(ttP);

        ttP = document.createElement("p");
        ttP.id = "mtx_errorMsgCloseButton";
        ttP.innerHTML = '<span id="mtx_errorButton"></span>&nbsp;<input id="errMsgCloseBtn" type="submit" value = "Close" onclick="hideDealSelectionError()" />';
        ttP.style.textAlign = 'center';
        ttDiv.appendChild(ttP);

        document.body.appendChild(ttDiv);
        
        var pink_popup = document.createElement("div");
        pink_popup.id = "pink_popup";
        pink_popup.className = "floatbox-cover";
        pink_popup.style.display = 'none';
        pink_popup.innerHTML = '<div class="floatbox-top"><span class="floatbox-header">Sorry, you can\'t book these dates</span></div><div id="pink_popup_message" class="floatbox-text"></div><div class="floatbox-bottom"></div>';        
        document.body.appendChild(pink_popup);
        
        initializeCalendar();
        //startapp(document.forms['checkAvailShort'], "");
    }
    
    Matrix.prototype.setSkin = function(name) {
    	this.skin = name;
    	var data = this.data;
    	this.applySkin(data);
    	document.fire("matrix:loaded", this);
    	document.fire("matrix:checkInOutSetExternaly", this); // synchronize calendar dates with matrix checkin/checkout dates
    }
    
    Matrix.prototype.applySkin_list = function(data) {
    	var mtx = tmpl_str(data.skins.list.template_list, data);
    	//if (!this.loaded) {
    		this.matrix.innerHTML = tmpl_str(data.skins.list.template_outline, {
    			'checkIn' : this.checkIn != null ? strToDate(this.checkIn) : null,
    			'checkOut' : this.checkOut != null ? strToDate(this.checkOut) : null
    		});

    	//}
    	//if (this.renderFilter) {
		   //fltr = tmpl_str(data.skins.matrix.template_filter, data);
		   //document.getElementById("div_mtx_filter").innerHTML = fltr;
    	//}
    	this.renderFilter = true;
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    	//startapp(document.forms['checkAvailShort'], "");
    	//document.fire("matrix:checkInOutSetExternaly", this); // synchronize calendar dates with matrix checkin/checkout dates
    }
    
    Matrix.prototype.applySkin_matrix = function(data) {
    	data['dateRow'] = tmpl_str(data.skins.matrix.template_dateRow, data);
    	var mtx = tmpl_str(data.skins.matrix.template_matrix, data);
    	if (!this.loaded) {
    		//this.matrix.innerHTML = tmpl_str(data.skins.matrix.template_outline, {'domainName' : 'localhost'});
    		this.matrix.innerHTML = tmpl_str(data.skins.matrix.template_outline, data);
    		/*
    		if (data.skins.matrix.template_filter) {
    			fltr = tmpl_str(data.skins.matrix.template_filter, data);
    			document.getElementById("div_mtx_filter").innerHTML = fltr;
    		}
    		*/
    	}
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    	startapp(document.forms['checkAvailShort'], "");
    }
    
    Matrix.prototype.applySkin_photo_list = function(data) {
    	var mtx = tmpl_str(data.skins.list.template_photo_list, data);
    		this.matrix.innerHTML = tmpl_str(data.skins.list.template_outline, {
    			'checkIn' : this.checkIn != null ? strToDate(this.checkIn) : null,
    			'checkOut' : this.checkOut != null ? strToDate(this.checkOut) : null
    		});
//    	this.renderFilter = true;
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    }
    
    Matrix.prototype.applySkin_places = function(data) {
    	var mtx = tmpl_str(data.skins.places.template_places, data);
    	/*
    		this.matrix.innerHTML = tmpl_str(data.skins.list.template_outline, {
    			'checkIn' : this.checkIn != null ? strToDate(this.checkIn) : null,
    			'checkOut' : this.checkOut != null ? strToDate(this.checkOut) : null
    		});
    		*/
//    	this.renderFilter = true;
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    }
    
    Matrix.prototype.applySkin = function(data) {
    	var featuredNum = 0;
    	if (this.data.featured) { featuredNum = this.data.featured.length; }
		if (typeof(data) == "undefined") return false;
		this.saveCriteria();
    	this.matrix = document.getElementById("_mtx_container");
    	data.showPagination = false;
    	data.pagination = new Array();
    	data.pagination.startRecord = 0;
    	data.pagination.endRecord = data.totalShowResult;
    	if (this.usePagination) {
    		data.showPagination = true;
	    	data.pagination.showPreviousButton = false;
	    	data.pagination.showNextButton = false;
	    	if (featuredNum > 0) {
	    		if (this.pageNo == 1) {
	    			data.pagination.startRecord = (this.pageNo-1)*this.resultPerPage;
	    			data.pagination.endRecord = (this.resultPerPage*this.pageNo) - featuredNum;
	    		} else {
	    			data.pagination.startRecord = ((this.pageNo-1)*this.resultPerPage) - featuredNum;
	    			data.pagination.endRecord = (this.resultPerPage*this.pageNo) - featuredNum;
	    		}
	    	} else {
	    		data.pagination.startRecord = (this.pageNo-1)*this.resultPerPage;
	    		data.pagination.endRecord = this.resultPerPage*this.pageNo;
	    	}
	    	data.pagination.numberOfPage = parseInt(data.totalShowResult/this.resultPerPage);
	    	if (data.pagination.numberOfPage*this.resultPerPage < data.totalShowResult) data.pagination.numberOfPage += 1;
	    	data.pagination.pageNo = this.pageNo;
	    	if (data.pagination.endRecord > data.totalShowResult) data.pagination.endRecord = data.totalShowResult;
	    	if (data.totalShowResult > data.pagination.endRecord) data.pagination.showNextButton = true;
	    	if (this.pageNo > 1) data.pagination.showPreviousButton = true;
    	}
    	this['applySkin_' + this.skin](data);
    	this.restoreCriteria();
    }
    
    Matrix.prototype.restoreCriteria = function() {
    	if (typeof(matrixFilter) != "undefined") matrixFilter.Restore();
    }
    
    Matrix.prototype.saveCriteria = function() {
    	matrixFilter = new MatrixFilter();
    	matrixFilter.KeepCurrent();
    }

    Matrix.prototype.loadCheckInMatrix = function (checkIn, checkOut) {
        this.checkIn = checkIn;
        this.checkOut = checkOut;
        this.startDate = "checkIn";
        
        /* change the reservation dates to the new dates */
        if (this.reservation) {
        	this.reservation.clearNights();
        	if (this.checkIn && this.checkOut) { 
        		this.reservation.setNights(strToDate(this.checkIn), strToDate(this.checkOut));
        	}
        }
        this.loadMatrix();
    }
    
    Matrix.prototype.nextPage = function(incDays) { this.changePage(incDays); }
    Matrix.prototype.prevPage = function(incDays) { this.changePage(-incDays); }

    Matrix.prototype.changePage = function(days) {
        this.startDate = incDateStr(this.startDate, days);
        this.endDate = incDateStr(this.startDate, this.days);
        this.loadMatrix();        
    }
    
    Matrix.prototype.selectRatePlan = function(ratePlanId, ratePlanName, offerId, bookingAdapter) {
    	if (!this.reservation) {
    		this.reservation = new Reservation(ratePlanId, ratePlanName, '0', offerId, this.requestedId, this.restrictions, bookingAdapter);
    	} else {
    		this.reservation.ratePlanId = ratePlanId;
    		this.reservation.restrictions = this.restrictions;
    	}
    	
		mtxHelper.updateTotals();
		this.showCurrentRatePlan(ratePlanId);
    }
    
    Matrix.prototype.showCurrentRatePlan = function(ratePlanId) {
          var ratePlan = document.getElementById(ratePlanId);
  
          if (ratePlan) {
        	  //this.checkLastSelected(ratePlan, ratePlanId);
              
        	  haveBlurb = this.showRatePlanBlurb(ratePlan, ratePlanId);
              $(ratePlanId + '_moreinfo').style.display = "none";
              $(ratePlanId + '_hideinfo').style.display = "";
  
              highlightRow(ratePlan, !haveBlurb); 
          }
    }

    /**
     * Checks that all current reservation nights displayed in the matrix have rates.
     */
    Matrix.prototype.allNightsAvailable = function() {
    	if (this.reservation && this.reservation.numNights > 0) {
	 	    for (dateIndex = this.startDate; dateIndex <= this.endDate; dateIndex = incDateStr(dateIndex, 1)) {
	 	    	if (this.isInReservationPeriod(dateIndex)) {
		 	    	rateCell = document.getElementById(this.reservation.ratePlanId + "_" + dateIndex);
		 	        if (!rateCell) return false;
	 	    	}
	  	    }
    	}
    	return true;
    }
    
    Matrix.prototype.setDailyInclusions = function(blurb, idx) {
    	if (!$(blurb)); return;
    	var dv = $(blurb).getElementsByClassName('inclusions');
    	if (!dv) return;
        eIncl = dv[0];
        if (!eIncl) return;
        
    	var inclusionsText = '';
        
        if (typeof(this.checkIn) != 'null') {
        	var selectedDates = [];
        	var inclusions = [];
        	var rates = this.data.results[idx].rates;
        	var numNights = this.reservation.numNights;

        	for(var i = 0; i < numNights ; i++) {
        		if (this.data.results[idx].invSrc != "L") {
        			curr_date = new Date(this.reservation.checkIn.getTime() + (i * 24 * 60 * 60 * 1000));
        			if (typeof(rates[curr_date.format('%Y%m%d')]) != 'undefined') {
        				inclusions.push([rates[curr_date.format('%Y%m%d')].dailyInclusions, curr_date]);
        			}
        		}
        	}
        	var start_date;
        	var text;
        	
        	// sort into same text date range
        	var sortedInclusions = [];
        	var start = '';
        	var end = '';
        	var text = '';
        	
        	for (i = 0; i < inclusions.length; i++) {
    			if (i == 0) { // seed
        			start = inclusions[i][1];
        			end = inclusions[i][1]; 
        			text = inclusions[i][0];
    			}

    			if (inclusions[i][0] != text) { // text is different
    				sortedInclusions.push({'start': start, 'end': end, 'text': text});
    				start = inclusions[i][1];
    				end = inclusions[i][1];
        			text = inclusions[i][0];
    				
    			} else { // text is the same
    				end = inclusions[i][1];
    			}
    			
    			if (i + 1 == inclusions.length) { // last iteration, always save
    				sortedInclusions.push({'start': start, 'end': end, 'text': text});
    			}
    			
        	}
        	
        	var inclusionsText = '';
        	
        	for(i = 0; i < sortedInclusions.length; i++) {
    			start_date = sortedInclusions[i].start;
    			end_date = sortedInclusions[i].end;
    			text = sortedInclusions[i].text;
    			if (text == '') {
    				continue;
    			}
    			
    			if (start_date.getMonth() == end_date.getMonth()) { // same month
    				if (end_date.getDate() - start_date.getDate() > 0) { // multiple days
    					inclusionsText += '<div class="inclusion">' + start_date.getDate() + '-' + end_date.getDate() + " " + lookupMonth(start_date.getMonth()) + ' ' + start_date.getFullYear() + ' - ' + text + '</div>';
    				} else {
    					inclusionsText += '<div class="inclusion">' + start_date.getDate() + " " + lookupMonth(start_date.getMonth()) + ' ' + start_date.getFullYear() + ' - ' + text + '</div>';
    				}
    				
    			} else { // different month
    				inclusionsText += '<div class="inclusion">' + start_date.getDate() + " " + lookupMonth(start_date.getMonth()) + ' - ' + end_date.getDate() + ' ' + lookupMonth(end_date.getMonth()) + ' ' + start_date.getFullYear() + ' - ' + text + '</div>';
    				
    			}
        	}
        }
        
        $(eIncl).update(inclusionsText);
    };

    Matrix.prototype.setNotAvailable = function(idx) {
    	var deal_id = mtxHelper.ratesMapping[idx];
    	var eNotAvailable = $$("#ratePlanBlurb_" + deal_id + " div.not-available")[0];
//        var eNotAvailable = $(this.lastRatePlanBlurb).getElementsByClassName('not-available')[0];
        var msg = '';
        
        if (mtxHelper.rates[idx].numberOfNights > 0) {
        	msg = mtxHelper.deals[idx].errMsg;
        }
        eNotAvailable.update(msg);
    	if (msg) {
    		eNotAvailable.show();

    	} else {
    		eNotAvailable.hide();
    	}
    	
    };
    
    Matrix.prototype.showRatePlanBlurb = function(ratePlanRow, ratePlanId, even) {
        var blurb = this.getBlurb([ratePlanId]);
        var idx = blurb[1];
        this.blurbIdx = idx;
        
        if (blurb) {
        	var na = '';
            var table = ratePlanRow.parentNode;
            this.lastRatePlanBlurb = table.insertRow(ratePlanRow.rowIndex + 1);
            this.lastRatePlanBlurb.id = "ratePlanBlurb_" + ratePlanId;
            this.lastRatePlanBlurb.className = 'rate_plan_blurb_row';
            
            var odd_even = blurb[0] ? "even" : "odd";
            
            var free_nights_desc = '';
            if (this.data.results[idx].promotions) {
            	for (var x=0; x<this.data.results[idx].promotions.length; x++) {
            		if (this.data.results[idx].promotions[x].type=="FreeNights") {
            			var period_start = "";
            			var period_end = "";
            			
            			if (this.data.results[idx].promotions[x].start_date=="" && this.data.results[idx].promotions[x].end_date=="") {
            				period = "";
            			} else {
            				if (this.data.results[idx].promotions[x].start_date=="") {
            					period_start = "Now";
            				} else {
            					var start_date = strToDate(this.data.results[idx].promotions[x].start_date);
            					period_start = start_date.getDate() + ' ' + lookupMonth(start_date.getMonth()) + ' ' + start_date.getFullYear();
            				}
            				if (this.data.results[idx].promotions[x].end_date=="") {
            					period = "From " + period_start + ": ";
            				} else {
            					var end_date = strToDate(this.data.results[idx].promotions[x].end_date);
            					period_end = end_date.getDate() + ' ' + lookupMonth(end_date.getMonth()) + ' ' + end_date.getFullYear();
            					period = period_start + " - " + period_end + ": ";
            				}
            			}
            			if (free_nights_desc != '') free_nights_desc += "<br />";
            			free_nights_desc += period + this.data.results[idx].promotions[x].text;
            		}
            	}
            }
            
            //make sure data in the same currency
            mtxHelper.verify();
            var data 					= Object.clone(this.data.results[idx]);
            data.odd_even				= odd_even;
            data.symbol 				= mtxHelper.rates[idx].currencySymbol;
            data.total 					= addComma(mtxHelper.rates[idx].totalRate);
            data.tatal_local			= mtxHelper.rates[idx].totalRateLocal;
            data.saved 					= addComma(mtxHelper.rates[idx].savedRate);
            data.saved_local			= mtxHelper.rates[idx].savedRateLocal;
            data.rack_rate				= addComma(mtxHelper.rates[idx].totalRackRate);
            data.rack_rate_local		= mtxHelper.rates[idx].totalRackRateLocal;
            data.cancellation_policy	= mtxHelper.updateCancellation(idx);
            data.bookState				= mtxHelper.deals[idx].state;
            data.free_nights_description = free_nights_desc;
            data.total_nights			= mtxHelper.rates[idx].numberOfNights;
            
            if (mtxHelper.rates[idx].savedRate > 0) {
            	data.display = 'block';
        	} else {
            	data.display = 'none';
            }
            
            var cell0 = this.lastRatePlanBlurb.insertCell(0);
            cell0.innerHTML = tmpl_str(this.data.skins.matrix.template_ratePlanBlurb0, data);
            cell0.className = "mtx_blurb_add_left mtx_blurb_even";
            var cell1 = this.lastRatePlanBlurb.insertCell(1);
            cell1.innerHTML = tmpl_str(this.data.skins.matrix.template_ratePlanBlurb1, data);
            cell1.className = "mtx_blurb_add_right mtx_blurb_even";
            cell1.colSpan = table.rows[2].cells.length-1;
            
            mtxHelper.ratePlanBlurbId = this.lastRatePlanBlurb.id;
            mtxHelper.ratePlanBlurbIndex = idx;
            
            this.checkRatePlanDateSelection(ratePlanId);
            
            this.setDailyInclusions(this.lastRatePlanBlurb, idx);
        } else {
            this.lastRatePlanBlurb = null;
            mtxHelper.ratePlanBlurbId = null;
            mtxHelper.ratePlanBlurbIndex = null;
        }
        this.setNotAvailable(idx);
    	        
        return blurb[1];
    }
    
    Matrix.prototype.checkRatePlanDateSelection = function(ratePlanId) {
    	var tr_id = "ratePlanBlurb_" + ratePlanId;
    	var idx = mtxHelper.ratesMapping.indexOf(ratePlanId);
    	if ($(tr_id)) {
    		if (mtxHelper.rates[idx].numberOfNights>0) {
    			$$("#" + tr_id + " div.mtxRatePlanNoDateSelected")[0].style.display = 'none';
    			
    			if (mtxHelper.deals[idx].active) {
    				$$("#" + tr_id + " div.total-cover")[0].show();
    			} else {
    				/** 
    				 * TODO: Change this to
    				 * $("rate_section").hide();
    				 */
    				$$("#" + tr_id + " div.total-cover")[0].hide();
    			}
    			
    		} else {
    			$$("#" + tr_id + " div.mtxRatePlanNoDateSelected")[0].style.display = '';
    			$$("#" + tr_id + " div.total-cover")[0].hide();
    		}
		}
    }


    Matrix.prototype.getBlurb = function(ratePlanId) {
    	for (var i = 0; i < this.data.results.length; i++) {
    		if (this.data.results[i].id == ratePlanId) {
    			var even = (i % 2) == 1;
    			return [even, i];
    		}
    	}
    	return null;
    }              
                  
    Matrix.prototype.checkLastSelected = function(ratePlanRow, ratePlanId) {
    	this.replaceSelectedRatePlan();
        
        this.lastRatePlan = ratePlanRow;
        this.lastRatePlanClone = ratePlanRow.cloneNode(true);
    }
    
    Matrix.prototype.replaceSelectedRatePlan = function(ratePlanId, remove) {
        	// we have to refetch the 'lastRatePlan' row as the innerHTML may have been
        	// reset, rendering any DOM nodes we hold onto useless.
        	var lastRatePlanVer2 = $(ratePlanId);
            var table = lastRatePlanVer2.parentNode;
            if (table) {
            	/* TBR
                table.replaceChild(this.lastRatePlanClone, lastRatePlanVer2);
                */
            	var blurb = $("ratePlanBlurb_" + ratePlanId);
                if (blurb && remove===true) {
                	table.removeChild(blurb);
                }
                
                mtxHelper.updateTotals(this.lastRatePlanClone);
            } 
    }
    
    Matrix.prototype.hideRatePlanSelection = function(ratePlanId) {
    	if (ratePlanId) {
        	$(ratePlanId + "_hideinfo").style.display = 'none';
        	$(ratePlanId + "_moreinfo").style.display = '';
    	}
    	this.replaceSelectedRatePlan(ratePlanId, true);
        this.lastRatePlan = null;
        this.lastratePlanClone = null;
        this.lastRatePlanBlurb = null;
        mtxHelper.ratePlanBlurbId = null;
        mtxHelper.ratePlanBlurbIndex = null;

    }
    
    Matrix.prototype.unhideRatePlanSelection = function() {
    	if (this.reservation != null) this.showCurrentRatePlan();
    }
    
    Matrix.prototype.isInReservationPeriod = function(dateStr) {
        return this.reservation && this.reservation.numNights > 0 && this.reservation.isInReservation(strToDate(dateStr));
    }  



/**
 * Reservation Class
 */
function Reservation(ratePlanId, ratePlanName, rtId, offerId, hotelId, restrictions, bookingAdapter) {
    this.hotelId = hotelId;
    this.ratePlanId = ratePlanId;
    this.ratePlanName = ratePlanName;
    this.rtId = rtId;
    this.offerId = offerId;
    this.restrictions = restrictions;
    this.bookingAdapter = bookingAdapter;
    this.checkIn = null;
    this.numNights = 0;
    this.errorMsg = "";
    this.ratePlanInvalidDates = new Object();


    Reservation.prototype.setNights = function(checkIn, checkOut) {
        var dateIndex = checkIn; 
        while (dateIndex.valueOf() < checkOut.valueOf()) {
            this.toggleDate(dateIndex);            
            dateIndex = incrementDate(dateIndex, 1);
        }    
    }

    Reservation.prototype.setDateSelected = function() {
    	if (this.numNights == 0) return;
    	
    	// clear other date selected remember by Chrome and Safari
    	var d = strToDate(matrix.startDate);
    	for (i=0; i<matrix.days; i++) {
    		var cb = $(d.format('%Y%m%d') + "_cb");
    		if (cb) {
    			cb.checked = false;
    		}
    		
    		d = incrementDate(d, 1)
    	}
    	
    	for (var dateIndex=this.checkIn; dateIndex < this.getCheckOut(); dateIndex = incrementDate(dateIndex, 1)) {
    		var cb = $(dateIndex.format('%Y%m%d') + "_cb");
    		if (cb) {
    			cb.checked = true;
    			setMatrixDateCheckBoxes(cb, dateIndex.format('%Y%m%d'));
    		}
    	}
    }

    Reservation.prototype.clearNights = function() {
        this.checkIn = null;
        this.numNights = 0;
        this.ratePlanInvalidDates = new Object();
    }

    Reservation.prototype.getLastNight = function() {
        return incrementDate(this.checkIn, this.numNights - 1);
    }
    
    Reservation.prototype.getCheckOut = function() {
        return incrementDate(this.checkIn, this.numNights);
    }
    
    Reservation.prototype.toggleDate = function (newDate) {
    	if (!this.checkIn || this.isDayBeforeCheckIn(newDate)) {
    		this.checkIn = newDate;
    		this.numNights++;
    	} else if (this.isCheckIn(newDate)) {
    		this.checkIn = (this.numNights > 1)?incrementDate(this.checkIn, 1):null;
    		this.numNights--;
    	} else if (this.isCheckOut(newDate)) {
    		this.numNights++;
    	} else if (this.isLastNight(newDate)) {
    		this.numNights--;
    	} else {
    		return false;
    	}
    	
    	if (this.checkIn == null) {
    		matrix.checkIn =  null;
    		matrix.checkOut =  null;
    	} else {
    		matrix.checkIn =  this.checkIn.format('%Y%m%d');
    		matrix.checkOut = this.getCheckOut().format('%Y%m%d');
    	}
    	
    	//find no rates date for each ratePlan
    	this.findRatePlanInvalidDates(newDate);
    	
    	/* move to updateTotals
    	if (typeof(matrix.blurbIdx) != 'undefined') {
    		matrix.setDailyInclusions();
    		//matrix.setNotAvailable();
    	}
    	*/
    	return true;
    }
    
    Reservation.prototype.setRatePlanInvalidDates = function() {
    	this.ratePlanInvalidDates = new Object();
    	for (var i=0; i<matrix.data.results.length; i++) {
    		var ratePlanId = matrix.data.results[i].id;
    		if (matrix.data.results[i].ratePlanInvalidDates) {
    			this.ratePlanInvalidDates[ratePlanId] = matrix.data.results[i].ratePlanInvalidDates;
    		}
    	}
    }
    
    Reservation.prototype.findRatePlanInvalidDates = function(newDate) {
    	var dateStr = newDate.format('%Y%m%d');
    	for (var i=0; i<matrix.data.results.length; i++) {
    		var ratePlanId = matrix.data.results[i].id;
    		if (!matrix.data.results[i].rates[dateStr]) {
    			if (!this.ratePlanInvalidDates[ratePlanId]) {
    				this.ratePlanInvalidDates[ratePlanId] = new Array();
    				this.ratePlanInvalidDates[ratePlanId].push(dateStr);
    			} else {
    				var found = false;
    				for (var j=0; j<this.ratePlanInvalidDates[ratePlanId].length; j++) {
    					if (this.ratePlanInvalidDates[ratePlanId][j] == dateStr) {
    						this.ratePlanInvalidDates[ratePlanId].splice(j, 1);
    						found = true;
    						break;
    					}
    				}
    				
    				if (!found) this.ratePlanInvalidDates[ratePlanId].push(dateStr);
    			}
    		}
    	}
    }

    Reservation.prototype.isLastNight = function(newDate) {
        return newDate.valueOf() == this.getLastNight().valueOf();
    }

    Reservation.prototype.isCheckOut = function(newDate) {
    	return newDate.valueOf() == incrementDate(this.checkIn, this.numNights).valueOf();
    }

    Reservation.prototype.isCheckIn = function(newDate) {
        return newDate.valueOf() == this.checkIn.valueOf();
    }

    Reservation.prototype.isDayBeforeCheckIn = function(newDate) {
        return newDate.valueOf() == incrementDate(this.checkIn, -1).valueOf();
    }
    
    Reservation.prototype.rateAvailable = function() {
    	if (this.ratePlanInvalidDates[this.ratePlanId] && this.ratePlanInvalidDates[this.ratePlanId].length>0) {
    		return false;
    	}
    	
    	return true;
    }

    Reservation.prototype.isValid = function () {
        if (this.numNights <= 0) {
        	this.errorMsg = "You need to check the dates you wish to stay before you book. <br><br>You can also scroll forward or back to select more dates.";
            return false;
        } else if (!this.rateAvailable()) {
        	this.errorMsg = "This room is sold out for some of the nights you want to stay.";
            return false;
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MinNightsStay")) {
            this.errorMsg = "This offer requires a minimum booking of at least " + this.restrictions.lastError.numNights + " nights. Select more dates to stay or a different offer.";
            return false;
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MaxNightsStay")) {
            this.errorMsg = "This offer does not allow bookings of more than " + this.restrictions.lastError.numNights + " nights. Choose fewer dates to stay or a different offer.";
            return false;
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "DayUse") && this.numNights > 1) {
            this.errorMsg = "This offer cannot be booked overnight. It is available for day use only.";
            return false;
        } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.checkIn, this.checkIn, "ClosedToArrival", this.numNights)) {
        	suffix = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0)?" unless you book at least " + this.restrictions.lastError.numNights + " nights":"";
            this.errorMsg = "The hotel does not allow arrivals on the first day you want to stay" + suffix + ". Choose another first date or a different offer.";
            return false;
        } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.getCheckOut(), this.getCheckOut(), "ClosedToDeparture", this.numNights )) {
            suffix = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0)?" unless you book at least " + this.restrictions.lastError.numNights + " nights":"";
            this.errorMsg = "The hotel does not allow check-outs on the last day you want to stay" + suffix + ". Choose another date to check out or a different offer.";
            return false;
        } 
        return true;
    }  

    Reservation.prototype.getRestrictionLabel = function () {
    	var label = '';
        if (!this.rateAvailable()) {
        	label = "Not Available";
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MinNightsStay")) {
            label = this.restrictions.lastError.numNights + "+ nts";
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MaxNightsStay")) {
//            label = "Max " + this.restrictions.lastError.numNights + " nts";
        	label = "Not Available";
        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "DayUse") && this.numNights > 1) {
//            label = "Day Use";
        	label = "Not Available";
      } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.checkIn, this.checkIn, "ClosedToArrival", this.numNights)) {
//        	label = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0) ? "Min " + this.restrictions.lastError.numNights + " nts" : "No Check In";
        	label = "Not Available";
      } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.getCheckOut(), this.getCheckOut(), "ClosedToDeparture", this.numNights )) {
//        	label = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0) ? "Min " + this.restrictions.lastError.numNights + " nts" : "No Check Out";
        	label = "Not Available";
      } 
        return label;
    }
    
    Reservation.prototype.getNotAvailableMessage = function () {
    	var msg = '';
    	if (this.numNights<=0) return msg;
        if (!this.rateAvailable()) {
        	msg = "This room is sold out for some of the nights you want to stay.";

        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MinNightsStay")) {
            msg = "This offer requires a minimum booking of at least " + this.restrictions.lastError.numNights + " nights. Select more dates to stay or a different offer.";

        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "MaxNightsStay")) {
            msg = "This offer does not allow bookings of more than " + this.restrictions.lastError.numNights + " nights. Choose fewer dates to stay or a different offer.";

        } else if (this.restrictions.reservationIsRestricted(this, this.ratePlanId, "DayUse") && this.numNights > 1) {
            msg = "This offer cannot be booked overnight. It is available for day use only.";

        } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.checkIn, this.checkIn, "ClosedToArrival", this.numNights)) {
        	suffix = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0)?" unless you book at least " + this.restrictions.lastError.numNights + " nights":"";
            msg = "The hotel does not allow arrivals on the first day you want to stay" + suffix + ". Choose another first date or a different offer.";

        } else if (this.restrictions.restrictionApplies(this.ratePlanId, this.getCheckOut(), this.getCheckOut(), "ClosedToDeparture", this.numNights )) {
            suffix = (this.restrictions.lastError.numNights && this.restrictions.lastError.numNights > 0)?" unless you book at least " + this.restrictions.lastError.numNights + " nights":"";
            msg = "The hotel does not allow check-outs on the last day you want to stay" + suffix + ". Choose another date to check out or a different offer.";

        } 
        return msg;
    };
    
    
    Reservation.prototype.book = function () {
    	if (!window.mtx_portal) {
    		window.mtx_portal = 'asiawebdirect.com';
    	}
    	if (!window.mtx_rsvn) {
    		window.mtx_rsvn = '2.0';
    	}
    	
    	if (this.isValid()) {
    		// special cookie use when customer click back
    		var expire = new Date(new Date().getTime() + (24 * 1000 * 60 * 60 * 24));
    		setCookie('mtxBookCheckInDate', this.checkIn.format('%Y%m%d'), expire);
    		setCookie('mtxBookCheckOutDate', this.getCheckOut().format('%Y%m%d'), expire);
    		
            var bookUrl = "/asahi/frontend.php/book?" +
                          "bookingAdapter=" + this.bookingAdapter +
                          "&ratePlanName=" + this.encodeRatePlanName(this.ratePlanName) + 
                          "&startMonth=" + (this.checkIn.getMonth() + 1) +
                          "&startDay=" + this.checkIn.getDate() + 
                          "&startYear=" + this.checkIn.getFullYear() +
                          "&numNights=" + this.numNights +
                          "&hotelId=" +  this.hotelId +
                          "&rtId=" + this.rtId +
                          "&offerId=" + this.offerId +
                          "&portal=" + mtx_portal +
                          "&rsvn=" + mtx_rsvn +
                          "&onRequest=" + this.restrictions.reservationIsRestricted(this, this.ratePlanId, "OnRequest");
            
            if (matrix.xDomain) {
            	bookUrl = 'http://' + matrix.domainName + bookUrl;
            }
            
        	// send preferred currency
        	var ccode = getCookie('ccCodePersist');
        	if (ccode && ccode.length==3) {
        		bookUrl += "&PreferredCurrency=" + ccode;
        	}
        	
        	document.location.href = bookUrl;
            //window.open(bookUrl);
            return true;
    	} else {
    		return false;
    	}           
          
    }
    
    Reservation.prototype.encodeRatePlanName = function(name) {
    	return name.replace(/\+/g,'%2B');
    }

    Reservation.prototype.isInReservation = function(date) {
        return date.valueOf() >= this.checkIn.valueOf() && date.valueOf() <= this.getLastNight().valueOf();
    }
    
}


/**
 * Restriction rules class.
 */
function Restriction() {
	this._restrictionTypes = ["OnRequest", "ClosedToArrival", "ClosedToDeparture", "MinNightsStay", "MaxNightsStay", "DayUse"];
  
	/* An array of restriction arrays */
    this.restrictions = new Array();  
    this.lastError = null;
    this.weekDays = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
        
    Restriction.prototype.initialise = function(results) {
        for (var i = 0; i < results.length; i++) {
            this.restrictions[results[i].id] = new Array();
        	for (var typeIndex = 0; typeIndex < this._restrictionTypes.length; typeIndex++) {
        		var restrictionType = this._restrictionTypes[typeIndex];
                if (results[i].restrictions[restrictionType] && (results[i].restrictions[restrictionType].length > 0)) {
                    this.restrictions[results[i].id][restrictionType] = results[i].restrictions[restrictionType];
                }
        	}
        }
    }

    Restriction.prototype.reservationIsRestricted = function(reservation, ratePlanId, restrictionType) {
        return this.restrictionApplies(ratePlanId, reservation.checkIn, reservation.getLastNight(), restrictionType, reservation.numNights);             
    }

    Restriction.prototype.restrictionApplies = function(ratePlanId, startDate, endDate, restrictionType, numNights) {
        if (this.restrictions[ratePlanId] && this.restrictions[ratePlanId][restrictionType]) {

            var rules = this.restrictions[ratePlanId][restrictionType];
             
            for (var i = 0; i < rules.length; i++) {
            	  if (this.isByRestrictedByNumNights(rules[i], numNights) && 
            	      ( (this.isRestrictedByDate(rules[i], startDate, endDate) || this.isRestrictedByDay(rules[i], startDate)) || this.isGlobalRestriction(rules[i])) )
            	  {
                      this.lastError = rules[i];
                      return true;
                  }                 
    
            }

        }
        return false;            
    }

    Restriction.prototype.isByRestrictedByNumNights = function(rule, numNights) {
        return rule["numNights"] == null || 
               ((rule["relation"] == "atLeast" && (rule["numNights"] == 0 || numNights < rule["numNights"])) ||
                (rule["relation"] == "atMost" && numNights > rule["numNights"]));
    }

    Restriction.prototype.isRestrictedByDay = function(rule, startDate) {
        return rule["dayOfWeek"] != null  && rule["dayOfWeek"] == this.weekDays[startDate.getDay()];
    }

    Restriction.prototype.isRestrictedByDate = function(rule, startDate, endDate) {
        if (rule['startDate'] == null || rule['endDate'] == null) return false;
        
        var endDateToCheck = endDate;
        if (rule['periodCheck'] && rule['periodCheck'] == "checkIn") {
            endDateToCheck = startDate; // only check if the rule overlaps with the check-in date
        }
        return rule["startDate"] != null && 
               rule["endDate"] != null  && 
               startDate.valueOf() <= strToDate(rule["endDate"]).valueOf() && 
               endDateToCheck.valueOf() >= strToDate(rule["startDate"].valueOf());
    }

    Restriction.prototype.isGlobalRestriction = function(rule) {
        return rule["startDate"] == null && 
               rule["endDate"] == null  &&
               rule["dayOfWeek"] == null;
    }
    
} 


/***** external includes ****/

function queryParam(name)
{
  name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
  var regexS = "[\\?&]"+name+"=([^&#]*)";
  var regex = new RegExp( regexS );
  var results = regex.exec( window.location.href );
  if( results == null )
    return "";
  else
    return results[1];
}


/**
 * Date.format()
 * string format ( string format )
 * Formatting rules according to http://php.net/strftime
 *
 * Copyright (C) 2006  Dao Gottwald
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Contact information:
 *   Dao Gottwald  <dao at design-noir.de>
 *
 * @version  0.7
 * @todo     %g, %G, %U, %V, %W, %z, more/better localization
 * @url      http://design-noir.de/webdev/JS/Date.format/
 */

var _lang = (navigator.systemLanguage || navigator.userLanguage || navigator.language || navigator.browserLanguage || '').replace(/-.*/,'');
switch (_lang) {
	case 'de':
		Date._l10n = {
			days: ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'],
			months: ['Januar','Februar','M\u00E4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'],
			date: '%e.%m.%Y',
			time: '%H:%M:%S'};
		break;
	case 'es':
		Date._l10n = {
			days: ['Domingo','Lunes','Martes','Mi�rcoles','Jueves','Viernes','S\u00E1bado'],
			months: ['enero','febrero','marcha','abril','puede','junio','julio','agosto','septiembre','octubre','noviembre','diciembre'],
			date: '%e.%m.%Y',
			time: '%H:%M:%S'};
		break;
	case 'fr':
		Date._l10n = {
			days: ['dimanche','lundi','mardi','mercredi','jeudi','vendredi','samedi'],
			months: ['janvier','f\u00E9vrier','mars','avril','mai','juin','juillet','ao\u00FBt','septembre','octobre','novembre','decembre'],
			date: '%e/%m/%Y',
			time: '%H:%M:%S'};
		break;
	case 'it':
		Date._l10n = {
			days: ['domenica','luned\u00EC','marted\u00EC','mercoled\u00EC','gioved\u00EC','venerd\u00EC','sabato'],
			months: ['gennaio','febbraio','marzo','aprile','maggio','giugno','luglio','agosto','settembre','ottobre','novembre','dicembre'],
			date: '%e/%m/%y',
			time: '%H.%M.%S'};
		break;
	case 'pt':
		Date._l10n = {
			days: ['Domingo','Segunda-feira','Ter\u00E7a-feira','Quarta-feira','Quinta-feira','Sexta-feira','S\u00E1bado'],
			months: ['Janeiro','Fevereiro','Mar\u00E7o','Abril','Maio','Junho','Julho','Agosto','Setembro','Outubro','Novembro','Dezembro'],
			date: '%e/%m/%y',
			time: '%H.%M.%S'};
		break;
	case 'en':
	default:
		Date._l10n = {
			days: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
			months: ['January','February','March','April','May','June','July','August','September','October','November','December'],
			date: '%Y-%m-%e',
			time: '%H:%M:%S'};
		break;
}
Date._pad = function(num, len) {
	for (var i = 1; i <= len; i++)
		if (num < Math.pow(10, i))
			return new Array(len-i+1).join(0) + num;
	return num;
};
Date.prototype.format = function(format) {
	if (format.indexOf('%%') > -1) { // a literal `%' character
		format = format.split('%%');
		for (var i = 0; i < format.length; i++)
			format[i] = this.format(format[i]);
		return format.join('%');
	}
	format = format.replace(/%D/g, '%m/%d/%y'); // same as %m/%d/%y
	format = format.replace(/%r/g, '%I:%M:%S %p'); // time in a.m. and p.m. notation
	format = format.replace(/%R/g, '%H:%M:%S'); // time in 24 hour notation
	format = format.replace(/%T/g, '%H:%M:%S'); // current time, equal to %H:%M:%S
	format = format.replace(/%x/g, Date._l10n.date); // preferred date representation for the current locale without the time
	format = format.replace(/%X/g, Date._l10n.time); // preferred time representation for the current locale without the date
	var dateObj = this;
	return format.replace(/%([aAbhBcCdegGHIjmMnpStuUVWwyYzZ])/g, function(match0, match1) {
		return dateObj.format_callback(match0, match1);
	});
}
Date.prototype.format_callback = function(match0, match1) {
	switch (match1) {
		case 'a': // abbreviated weekday name according to the current locale
			return Date._l10n.days[this.getDay()].substr(0,3);
		case 'A': // full weekday name according to the current locale
			return Date._l10n.days[this.getDay()];
		case 'b':
		case 'h': // abbreviated month name according to the current locale
			return Date._l10n.months[this.getMonth()].substr(0,3);
		case 'B': // full month name according to the current locale
			return Date._l10n.months[this.getMonth()];
		case 'c': // preferred date and time representation for the current locale
			return this.toLocaleString();
		case 'C': // century number (the year divided by 100 and truncated to an integer, range 00 to 99)
			return Math.floor(this.getFullYear() / 100);
		case 'd': // day of the month as a decimal number (range 01 to 31)
			return Date._pad(this.getDate(), 2);
		case 'e': // day of the month as a decimal number
			return this.getDate();
		/*case 'g': // like %G, but without the century
			return ;
		case 'G': // The 4-digit year corresponding to the ISO week number (see %V). This has the same format and value as %Y, except that if the ISO week number belongs to the previous or next year, that year is used instead
			return ;*/
		case 'H': // hour as a decimal number using a 24-hour clock (range 00 to 23)
			return Date._pad(this.getHours(), 2);
		case 'I': // hour as a decimal number using a 12-hour clock (range 01 to 12)
			return Date._pad(this.getHours() % 12, 2);
		case 'j': // day of the year as a decimal number (range 001 to 366)
			return Date._pad(this.getMonth() * 30 + Math.ceil(this.getMonth() / 2) + this.getDay() - 2 * (this.getMonth() > 1) + (!(this.getFullYear() % 400) || (!(this.getFullYear() % 4) && this.getFullYear() % 100)), 3);
		case 'm': // month as a decimal number (range 01 to 12)
			return Date._pad(this.getMonth() + 1, 2);
		case 'M': // minute as a decimal number
			return Date._pad(this.getMinutes(), 2);
		case 'n': // newline character
			return '\n';
		case 'p': // either `am' or `pm' according to the given time value, or the corresponding strings for the current locale
			return this.getHours() < 12 ? 'am' : 'pm';
		case 'S': // second as a decimal number
			return Date._pad(this.getSeconds(), 2);
		case 't': // tab character
			return '\t';
		case 'u': // weekday as a decimal number [1,7], with 1 representing Monday
			return this.getDay() || 7;
		/*case 'U': // week number of the current year as a decimal number, starting with the first Sunday as the first day of the first week
			return ;
		case 'V': // The ISO 8601:1988 week number of the current year as a decimal number, range 01 to 53, where week 1 is the first week that has at least 4 days in the current year, and with Monday as the first day of the week. (Use %G or %g for the year component that corresponds to the week number for the specified timestamp.)
			return ;
		case 'W': // week number of the current year as a decimal number, starting with the first Monday as the first day of the first week
			return ;*/
		case 'w': // day of the week as a decimal, Sunday being 0
			return this.getDay();
		case 'y': // year as a decimal number without a century (range 00 to 99)
			return this.getFullYear().toString().substr(2);
		case 'Y': // year as a decimal number including the century
			return this.getFullYear();
		/*case 'z':
		case 'Z': // time zone or name or abbreviation
			return ;*/
		default:
			return match0;
	}
}

function Filter() {
	this.filters = null;		// keep all filter need to compute
	this.currencyCode = null;
	this.failed = false;
	
	// initial function, load all require filter function into array
	this.loadFilters = function() {
		this.filters = new Array();
		this.filters.push(this.filterAccom);
		this.filters.push(this.filterFacility);
		this.filters.push(this.filterRating);
		this.filters.push(this.filterPrice);
		this.filters.push(this.filterArea);
	}
	
	// function use to filter each result record
	this.computeFilters = function(record) {
		for (var i=0; i<this.filters.length; i++) {
			if (!this.filters[i](record, this)) {
				return false;
			}
		}
		return true;
	}
	
	this.filterFacility = function(result) {
		var facility_mapping = {'restaurant': "Restaurant", 'pool': "Swimming Pool", 'broadbandInternet': "Broadband Internet", 'kitchenette': "Kitchenette", 'fitnessCentre': "Fitness Centre", 'satelliteCableTV': "Satellite/Cable TV" };
		for (key in facility_mapping) {
			if ($(key)) {
				if ($(key).checked) {
					var okay = false;
					if(result.facilities!=null)
					{
						for (var j=0; j<result.facilities.length; j++) {
							if (result.facilities[j] == facility_mapping[key]) {
								okay = true;
								break;
							}
						}
					}
					if (!okay) return false;
				}
			}
		}
		return true;
	}
	
	this.filterAccom = function (result) {
		if (!window.matrix.data.accomList) return true;
		for (var i=0; i<window.matrix.data.accomList.length; i++) {
			var chk = $('mtx_fltr_accom_'+i);
			if (!chk) return true;
			if (chk.checked) {
				var okay = false;
				var sValue = chk.value;
				for (var j=0; j<result.accomType.length; j++) {
					if (result.accomType[j] == sValue) {
						okay = true;
						break;
					}
				}
				if (!okay) return false;
			}
		}
		return true;
	}
	
	this.filterRating = function (result) {
		if (!$('minCategory')) return true;
		var min_value = parseInt($('minCategory').innerHTML);
		var max_value = parseInt($('maxCategory').innerHTML);
		var chk_value = parseFloat(result.rating);
		if (result.rating == null) chk_value = 1;  // allow to show no rating hotels
		
		if (chk_value>=min_value && chk_value<=max_value) return true;
		return false;
	}
	
	this.filterPrice = function (result, parent) {
		if (!$('minPrice') || !$('maxPrice') || !$('priceRangeCurrency')) return true;
		var min_value = parseInt($('minPrice').innerHTML.replace(/,/g, ''));
		var max_value = parseInt($('maxPrice').innerHTML.replace(/,/g, ''));
		var chk_value = parseFloat(result.lowestRateNum);
		
		if (min_value==0 && isNaN(max_value)) return true;	// this means show all
		
		var filter_currency = $('priceRangeCurrency').value;
		if (result.currency != filter_currency) {
			// find currency index
			var fc_idx = null;
			var cu_idx = null;
			for (var i=0; i<matrix.data.currencies.length; i++) {
				if (matrix.data.currencies[i]['code'] == filter_currency) fc_idx = i;
				if (matrix.data.currencies[i]['code'] == result.currency) cu_idx = i;
			}

			if (fc_idx==null || cu_idx==null) {
				parent.failed = true;
				return true;
			}

			min_value = min_value*matrix.data.currencies[fc_idx]['rate']/matrix.data.currencies[cu_idx]['rate'];
			max_value = max_value*matrix.data.currencies[fc_idx]['rate']/matrix.data.currencies[cu_idx]['rate'];
			chk_value = chk_value*matrix.data.currencies[cu_idx]['rate']/matrix.data.currencies[fc_idx]['rate'];
		}
		
		var lowest_rate = parseFloat(result.lowestRateNum);
		var highest_rate = parseFloat(result.highestRateNum);

		if ((highest_rate >= min_value) && ((lowest_rate <= max_value) || isNaN(max_value))) {
			return true;
		}
		return false;
	}
	
	this.filterArea = function (result) {
		if (!$('mtxSubDestCount')) return true;
		var okay = true;
		for (var i=0; i<$('mtxSubDestCount').value; i++) {
			var chk = $('mtxSubDest_'+i);
			if (!chk) return true;
			
			if (chk.checked) {
				if (result.Area == chk.value) {
					return true;
				} else {
					okay = false;
				}
			}
		}
		return okay;
	}

}

// chrome's implementation of Array.sort() is not stable (has unexpected results) 
// for arrays with more than 22 elements so use these functions instead
// functions merge_sort and merge are from http://en.literateprograms.org/Special:Downloadcode/Merge_sort_%28JavaScript%29?oldid=16699

/* Copyright (c) 2011 the authors listed at the following URL, and/or
2 the authors of referenced articles or incorporated external code:
3 http://en.literateprograms.org/Merge_sort_(JavaScript)?action=history&offset=20100428205145
4 
5 Permission is hereby granted, free of charge, to any person obtaining
6 a copy of this software and associated documentation files (the
7 "Software"), to deal in the Software without restriction, including
8 without limitation the rights to use, copy, modify, merge, publish,
9 distribute, sublicense, and/or sell copies of the Software, and to
10 permit persons to whom the Software is furnished to do so, subject to
11 the following conditions:
12 
13 The above copyright notice and this permission notice shall be
14 included in all copies or substantial portions of the Software.
15 
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 
24 Retrieved from: http://en.literateprograms.org/Merge_sort_(JavaScript)?oldid=16699
25 */
function merge_sort(array,comparison)
{
	if(array.length < 2)
		return array;
	var middle = Math.ceil(array.length/2);
	return merge(merge_sort(array.slice(0,middle),comparison),
			merge_sort(array.slice(middle),comparison),
			comparison);
}
function merge(left,right,comparison)
{
	var result = new Array();
	while((left.length > 0) && (right.length > 0))
	{
		if(comparison(left[0],right[0]) < 0)
			result.push(left.shift());
		else
			result.push(right.shift());
	}
	while(left.length > 0)
		result.push(left.shift());
	while(right.length > 0)
		result.push(right.shift());
	return result;
}


/**
 * DestinationMatrix Class
 */
function DestinationMatrix(requestedId, startDate, displayDays, languageCode, skin, resultPerPage) {

	this.loaded       = false;
    this.requestedId  = requestedId;
    this.startDate    = startDate;
    this.days         = displayDays;
    this.endDate      = "";
    this.languageCode = languageCode;
    this.matrix       = null;
    this.checkIn      = "";
    this.checkOut     = "";

    this.sortTypeMapping = {
        'Popularity' : 'byPopularity',
        'Category'   : 'byStar',
        'Price'      : 'byPrice',
        'PriceDesc'  : 'byPriceDesc',
        'HotelName'  : 'byName'
    };

    this.facilityCodeMapping = {
        'restaurant'        : 'RR',
        'pool'              : 'SP',
        'broadbandInternet' : 'BB',
        'kitchenette'       : 'KC',
        'fitnessCentre'     : 'FC',
        'satelliteCableTV'  : 'TV'
    }
    
    // XXX: do we need this?
    this.renderFilter = true;
    
    this.extractQueryParams();
    
    /** Matrix results are defined by these parameters */
	this.params = {};
	this.params['requestedId']  = requestedId;
	this.params['startDate']    = startDate;
	this.params['days']         = displayDays;
	this.params['languageCode'] = languageCode;
	this.params['checkIn']      = this.checkIn;
	this.params['checkOut']     = this.checkOut;
	this.params['serviceHost']  = 'rates.asiawebdirect.com';
	this.params['pageSize']     = resultPerPage;
	this.params['pageNum']      = 1;
	this.params['sortBy']       = 'byPopularity';
	this.params['facilities']   = [];
	this.params['accomTypes']   = [];
	this.params['minPrice']     = 0;
	this.params['maxPrice']     = -1;
	this.params['priceFilterCurrency'] = 'THB';
	
	this.extractExtraQueryParams();

    // XXX: do we need this?
    this.reservation = null;

    // XXX: do we need this?
    this.restrictions = new Restriction();
    
    this.xDomain = true;
    this.destMatrix = true;
    this.domainName = 'rates.asiawebdirect.com'; // should be removed in favor of params['serviceHost']
    this.skin = skin;
    
    this.matrixType = "Destination"; // should be removed as this is a specific class for the destination matrix only
    
    this.matrixActions = new Array(); // should be removed
    this.matrixActions["Destination"] = "dest"; // should be removed
    
    this.resultPerPage = resultPerPage; // should be removed in favor of this.params
    this.pageNo = 1; // should be removed in favor of this.params
    this.usePagination = true;
}

	DestinationMatrix.prototype.sortBy = function(by) {
		this.params['sortBy'] = this.sortTypeMapping[by];
		this.params['pageNum'] = 1;
		this.loadMatrix();
	}

	DestinationMatrix.prototype.doFilter = function() {
        // filter facilities
		var facilityCodes = [];
        for (var key in this.facilityCodeMapping) {
        	if ($(key) && $(key).checked) {
        		facilityCodes.push(this.facilityCodeMapping[key]);
        	}
        }
        this.params['facilities'] = facilityCodes;
        
        // filter accom types
        var accomTypeIds = [];
		if (window.matrix.data.accomList) {
		    for (var i = 0; i < window.matrix.data.accomList.length; i++) {
		    	var chk = $('mtx_fltr_accom_' + i);
			    if (!chk) continue;
			    if (chk.checked) {
				    var name = chk.value;
				    for (var j = 0; j < window.matrix.data.accomList.length; j++) {
				    	var sType = window.matrix.data.accomList[j];
				    	if (sType.name == name) {
				    		accomTypeIds.push(sType.id);
				    	}
				    }
			    }
		    }
		}
		this.params['accomTypes'] = accomTypeIds;
        
		// filter min and max price
		var minPrice = parseInt($('minPrice').innerHTML.replace(/,/g, ''), 10);
		var maxPrice = parseInt($('maxPrice').innerHTML.replace(/,/g, ''), 10);
		if (isNaN(minPrice) || minPrice < 0) minPrice = 0;
		if (isNaN(maxPrice) || maxPrice < 0) maxPrice = 0;

		// convert min and max prices to the destination currency
		var filter_currency = $('priceRangeCurrency').value;
		if (this.data.currency != filter_currency) {
			// find currency index
			var fc_idx = null;
			var cu_idx = null;
			for (var i = 0; i < this.data.currencies.length; i++) {
				if (this.data.currencies[i]['code'] == filter_currency) fc_idx = i;
				if (this.data.currencies[i]['code'] == this.data.currency) cu_idx = i;
			}

			if (fc_idx != null && cu_idx != null) {
			    minPrice = Math.ceil(minPrice * this.data.currencies[fc_idx]['rate'] / this.data.currencies[cu_idx]['rate']);
			    maxPrice = Math.floor(maxPrice * this.data.currencies[fc_idx]['rate'] / this.data.currencies[cu_idx]['rate']);
			}
		}
		this.params['minPrice'] = minPrice;
		this.params['maxPrice'] = maxPrice;

        // reset to first page
        this.params['pageNum'] = 1;
        
        // reload results
        this.loadMatrix();
	}

	// should be removed as pagination is now mandatory
	DestinationMatrix.prototype.setUsePagination = function(setValue) {
		this.usePagination = true; // always force pagination
	}

	DestinationMatrix.prototype.movePrevious = function() {
		if (this.params['pageNum'] != 1) {
		    this.params['pageNum'] = this.params['pageNum'] - 1;
		}
		this.loadMatrix();
	}

	DestinationMatrix.prototype.moveNext = function() {
		this.params['pageNum'] = this.params['pageNum'] + 1;
		this.loadMatrix();
	}

	DestinationMatrix.prototype.gotoPage = function(pageNo) {
		this.params['pageNum'] = parseInt(pageNo);
		this.loadMatrix();
	}
	
	DestinationMatrix.prototype.setResultPerPage = function(resultNo) {
		this.params['pageSize'] = resultNo;
		this.params['pageNum'] = 1;
		this.loadMatrix();
	}

    DestinationMatrix.prototype.enableXDomain = function(domainName) {
    	this.domainName = domainName; // XXX Remove me
    	this.params['serverHost'] = domainName;
    }
    
    // XXX do we need this function?
    DestinationMatrix.prototype.setMatrixType = function(matrixType) {
    	this.matrixType = "Destination"; // only allow this matrix type
    }
    
    // XXX do we need this function?
    DestinationMatrix.prototype.setRequestedId = function(requestedId) {
    	this.requestedId = requestedId;
    	this.params['requestedId'] = requestedId;
    	this.loadMatrix();
    }

    DestinationMatrix.prototype.setParacheckInOut = function(_tmp_checkIn, _tmp_checkOut) {
		if(String(_tmp_checkIn)!="null")
		if(String(_tmp_checkOut)!="null")
		if(String(_tmp_checkIn).substring(0,1)=="2")
		if(String(_tmp_checkOut).substring(0,1)=="2")
		{
        	var checkIn = strToDate(_tmp_checkIn);
        	var my_date = new Date();
        	var to_day = new Date(my_date.getFullYear(), my_date.getMonth(), my_date.getDate());
        	if (checkIn >= to_day) {
				this.checkIn = _tmp_checkIn;
				this.checkOut = _tmp_checkOut;
				this.startDate = 'checkIn';
				this.params['checkIn'] = this.checkIn;
				this.params['checkOut'] = this.checkOut;
				this.params['startDate'] = 'checkIn';
        	}
		}
		this.loadMatrix();
    }

    DestinationMatrix.prototype.setCookie = function(requestedId) {
		var url = "/asahi/setcookie.php?mtxSetCookie=1&mtxCheckIn=" + this.checkIn + "&mtxCheckOut=" + this.checkOut + "&tm="+(Math.random());
		this._doMatrixRequest(url);
    }

    DestinationMatrix.prototype.extractQueryParams = function() {
        var checkInParam  = queryParam("mtxCheckIn");
        var checkOutParam = queryParam("mtxCheckOut");
        if (checkInParam != '' && checkOutParam != '') {
        	var checkIn = strToDate(checkInParam);
        	var my_date = new Date();
        	var to_day = new Date(my_date.getFullYear(), my_date.getMonth(), my_date.getDate());
        	
        	if (checkIn < to_day) return;

        	this.checkIn   = checkInParam;
        	this.checkOut  = checkOutParam;
        	this.startDate = 'checkIn';
        }
        
        // allow query params to specify portal
        var portal = queryParam('mtxPortal');
        if (portal != '') {
        	window.mtx_portal = portal;
        }
    }
    
    DestinationMatrix.prototype.extractExtraQueryParams = function() {
    	// facility params
        if (queryParam('mtxRestaurant')) {
            this.params['facilities'].push(this.facilityCodeMapping['restaurant']);
        }
        if (queryParam('mtxSatelliteCableTV')) {
        	this.params['facilities'].push(this.facilityCodeMapping['satelliteCableTV']);
        }
        if (queryParam('mtxKitchenette')) {
        	this.params['facilities'].push(this.facilityCodeMapping['kitchenette']);
        }
        if (queryParam('mtxPool')) {
        	this.params['facilities'].push(this.facilityCodeMapping['pool']);
        }
        if (queryParam('mtxFitnessCentre')) {
        	this.params['facilities'].push(this.facilityCodeMapping['fitnessCentre']);
        }
        if (queryParam('mtxBroadbandInternet')) {
        	this.params['facilities'].push(this.facilityCodeMapping['broadbandInternet']);
        }

    }
    
    DestinationMatrix.prototype.extractCookie = function() {
		if((this.checkIn==null) || (this.checkIn==""))
		{
			var url = "/asahi/setcookie.php?tm="+(Math.random());
			this._doMatrixRequest(url);
		}
		else
		{
			this.loadMatrix();
		}
	}

    DestinationMatrix.prototype.loadMatrix = function () {
    	var facilityCodes = 'None';
    	if (this.params['facilities'].length > 0) {
    		facilityCodes = this.params['facilities'].join(',');
    	}
    	
    	var accomTypeIds = 'None';
    	if (this.params['accomTypes'].length > 0) {
    		accomTypeIds = this.params['accomTypes'].join(',');
    	}
    	
    	if (this.portal == 1) {
    		var urltarget = "pdestf";
    	} else {
    		var urltarget = "destf";
    	}
    	
        url = "/asahi/frontend.php/rates/"+ urltarget +"/" +
              this.params['sortBy'] + "/" +
              this.params['languageCode'] + "/AWD/"  +
              this.params['requestedId'] + "/" +
              this.params['startDate'] + "/" +
              this.params['days'] + "/" +
              this.params['pageSize'] + "/" +
              this.params['pageNum'] + "/" +
              facilityCodes + "/" +
              accomTypeIds + "/" +
              this.params['minPrice'] + "/" +
              this.params['maxPrice'] + "/" +
              this.params['priceFilterCurrency']

        if (this.params['checkIn'] && this.params['checkOut'] != "")
        	url = url + "/" + this.params['checkIn']  + "/" + this.params['checkOut'];
        document.fire("matrix:loading", this);
        this._doMatrixRequest(url);
    }

    DestinationMatrix.prototype._doMatrixRequest = function (url) {
        url = 'http://' + this.params['serviceHost'] + url;
        var headNode = document.getElementsByTagName("head")[0];
        var script   = document.createElement('script');
        script.type  = 'text/javascript';
        script.src   = url;
        headNode.appendChild(script);
    }
    
    DestinationMatrix.prototype.displayMatrix = function(data) {
    	this.displayReady = false;
    	this.waitingForDisplayCurrency = false;

        if (data["detail"]) {
            this.startDate = data["detail"]["startDate"];
            this.endDate   = data["detail"]["endDate"];
            if (data["detail"]["checkIn"]) this.checkIn    = data["detail"]["checkIn"];
            if (data["detail"]["checkOut"]) this.checkOut  = data["detail"]["checkOut"];
            this.numCols   = data["detail"]["numCols"];
        }
        if (this.data != null) {
        	this.accomList = this.data.accomList;
        } else {
        	this.accomList = [];
        }
        this.data = data;
        this.data.showResults = this.data.results;
        this.applySkin(data);

        if (!this.loaded) this.doInitialLoad(data);
        
        if (getCookie('mtxBookCheckInDate') != null && getCookie('mtxBookCheckOutDate') != null) {
        	this.checkIn = getCookie('mtxBookCheckInDate');
        	this.checkOut = getCookie('mtxBookCheckOutDate');
        	delCookie('mtxBookCheckInDate');
        	delCookie('mtxBookCheckOutDate');
        }
        
        // XXX do we need this?
        if (this.checkIn && this.checkOut) {
        	if (!this.reservation) {
        		this.reservation = new Reservation();
        		this.reservation.setNights(strToDate(this.checkIn), strToDate(this.checkOut));
        	}
        }
        
        // XXX do we need this?
        if (this.reservation) {
        	if (this.reservation.numNights > 0) {
        		this.reservation.setRatePlanInvalidDates();
        	}
        }

        document.fire("matrix:checkInOutSetExternaly", this); // synchronize calendar dates with matrix checkin/checkout dates
        this.displayReady = true;

    	if (!this.waitingForDisplayCurrency) {
    		document.fire("matrix:loaded", this);
    	}
    	
        if (this.data.results.length == 0) {
        	document.fire("matrix:noResults", this);
        }
        this.loaded = true;
    }
    
    DestinationMatrix.prototype.setDisplayCurrencyExternally = function(currencyCode) {
    	if (this.displayReady) {
    		document.fire("matrix:loaded", this);
    	} else {
    		this.waitingForDisplayCurrency = false;
    	}
    }
    
    DestinationMatrix.prototype.doInitialLoad = function(data) {
		// tooltip
		var ttDiv = document.createElement("div");
		ttDiv.id = "mtx_rateBlurb";
		ttDiv.className = "popup-container";
		ttDiv.style.display = 'none';

		div_top = document.createElement("div");
		div_top.className = "popup-top";
		ttDiv.appendChild(div_top);

		div_body = document.createElement("div");
		div_body.className = "popup-body";
		ttDiv.appendChild(div_body);

		div_bottom = document.createElement("div");
		div_bottom.className = "popup-bottom";
		ttDiv.appendChild(div_bottom);

	    ttP = document.createElement("p");
	    ttP.id = "mtx_rateBlurbMsg";
	    div_body.appendChild(ttP);

		document.body.appendChild(ttDiv);

		// Error
		var ttDiv = document.createElement("div");
		ttDiv.id = "dealSelectionError";
		ttDiv.className = "dealselectionerror";
		ttDiv.style.display = 'none';

	    ttP = document.createElement("p");
	    ttP.id = "mtx_errorMsg";
	    ttDiv.appendChild(ttP);

        ttP = document.createElement("p");
        ttP.id = "mtx_errorMsgCloseButton";
        ttP.innerHTML = '<span id="mtx_errorButton"></span>&nbsp;<input id="errMsgCloseBtn" type="submit" value = "Close" onclick="hideDealSelectionError()" />';
        ttP.style.textAlign = 'center';
        ttDiv.appendChild(ttP);

        document.body.appendChild(ttDiv);
        initializeCalendar();
    }
    
    DestinationMatrix.prototype.setSkin = function(name) {
    	this.skin = name;
    	var data = this.data;
    	this.applySkin(data);
    	document.fire("matrix:loaded", this);
    	document.fire("matrix:checkInOutSetExternaly", this); // synchronize calendar dates with matrix checkin/checkout dates
    }
    
    DestinationMatrix.prototype.applySkin_list = function(data) {
    	var mtx = tmpl_str(data.skins.list.template_list, data);

    	this.matrix.innerHTML = tmpl_str(data.skins.list.template_outline, {
    		'checkIn' : this.checkIn != null ? strToDate(this.checkIn) : null,
    		'checkOut' : this.checkOut != null ? strToDate(this.checkOut) : null
    	});

    	this.renderFilter = true;
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    }
    
    DestinationMatrix.prototype.applySkin_matrix = function(data) {
    	data['dateRow'] = tmpl_str(data.skins.matrix.template_dateRow, data);
    	var mtx = tmpl_str(data.skins.matrix.template_matrix, data);
    	if (!this.loaded) {
    		this.matrix.innerHTML = tmpl_str(data.skins.matrix.template_outline, data);
    	}
    	document.getElementById("thematrixdiv").innerHTML = mtx;
    	startapp(document.forms['checkAvailShort'], ""); // XXX Is this needed?
    }
    
    DestinationMatrix.prototype.applySkin_photo_list = function(data) {
    	var mtx = tmpl_str(data.skins.list.template_photo_list, data);
    	this.matrix.innerHTML = tmpl_str(data.skins.list.template_outline, {
    		'checkIn' : this.checkIn != null ? strToDate(this.checkIn) : null,
    		'checkOut' : this.checkOut != null ? strToDate(this.checkOut) : null
    	});

    	document.getElementById("thematrixdiv").innerHTML = mtx;
    }
    
    DestinationMatrix.prototype.applySkin = function(data) {
    	var featuredNum = 0;
    	if (this.data.featured) { featuredNum = this.data.featured.length; }
		if (typeof(data) == "undefined") return false;
		this.saveCriteria();
    	this.matrix = document.getElementById("_mtx_container");

    	data.showPagination = true;
    	data.pagination = new Array();
    	data.pagination.startRecord = 0;
    	data.pagination.endRecord = data.results.length - 1;
    	data.pagination.showPreviousButton = this.params['pageNum'] > 1;
    	data.pagination.numberOfPage = data.numPages;
    	data.pagination.showNextButton = this.params['pageNum'] != data.pagination.numberOfPage;
    	data.pagination.pageNo = this.params['pageNum'];

    	this['applySkin_' + this.skin](data);
    	this.restoreCriteria();
    }
    
    DestinationMatrix.prototype.restoreCriteria = function() {
    	if (typeof(matrixFilter) != "undefined") matrixFilter.Restore();
    }
    
    DestinationMatrix.prototype.saveCriteria = function() {
    	matrixFilter = new MatrixFilter();
    	matrixFilter.KeepCurrent();
    }

    DestinationMatrix.prototype.loadCheckInMatrix = function (checkIn, checkOut) {
        this.checkIn = checkIn;
        this.checkOut = checkOut;
        this.startDate = "checkIn";
        this.params['checkIn'] = checkIn;
        this.parans['checkOut'] = checkOut;
        this.params['startDate'] = 'checkIn';
        
        /* change the reservation dates to the new dates */
        if (this.reservation) {
        	this.reservation.clearNights();
        	if (this.checkIn && this.checkOut) { 
        		this.reservation.setNights(strToDate(this.checkIn), strToDate(this.checkOut));
        	}
        }
        this.loadMatrix();
    }
    
    DestinationMatrix.prototype.nextPage = function(incDays) { this.changePage(incDays); }
    DestinationMatrix.prototype.prevPage = function(incDays) { this.changePage(-incDays); }

    DestinationMatrix.prototype.changePage = function(days) {
        this.startDate = incDateStr(this.startDate, days);
        this.endDate = incDateStr(this.startDate, this.days);
        this.params['startDate'] = this.startDate;
        this.loadMatrix();        
    }

    DestinationMatrix.prototype.resetSelectedNights = function(dateStr) {
    	if (this.reservation) {
    		for (var i=0; i<this.data.dates.length; i++) {
    			cb = $(this.data.dates[i].fmt + '_cb');
    			if (cb) {
    				cb.checked = (this.data.dates[i].fmt == dateStr);
    				hilightSelectedColumn(cb, i+1);
    			}
    		}
    	}
    }

    DestinationMatrix.prototype.isInReservationPeriod = function(dateStr) {
        return this.reservation && this.reservation.numNights > 0 && this.reservation.isInReservation(strToDate(dateStr));
    }
    
    DestinationMatrix.prototype.hideRatePlanSelection = function() {
        // this function does nothing but is required to exist by the currency conversion utility
    }
    
    DestinationMatrix.prototype.unhideRatePlanSelection = function() {
        // this function does nothing but is required to exist by the currency conversion utility
    }
    
    function MatrixHelper() {
    	this.DealRow 			= $$("tr.deal_row");
    	this.firstSelectedCol 	= null;
    	this.lastSelectedCol 	= null;
    	this.previousTotalCol 	= null;
    	this.totalTemplate 		= new Template('<div class="total_wrapper"><div class="total" style="width: #{Width}%;#{ExtraStyle}"><nobr><span class="currency_symbol_sup" name="ccs">#{Symbol}</span><span class="per_night_value" name="drc">#{FormattedAverageRate}<span class="or">#{AverageRate}</span></span></nobr><br/><span class="per_night">per night</span></div></div>');
    	this.dateNotAvailableTemplate = new Template('<div class="total_wrapper"><div class="date_not_available_wrapper" style="width: #{Width}%" onmouseover="#{OnMouseOver}" onmouseout="hidePinkPopup()" onclick="#{OnClick}"><div class="date_not_available">#{Label}</div><div class="date_not_available_icon">&nbsp;</div></div>');
    	this.ratePlanBlurbIndex	= null;
    	this.ratePlanBlurbId 	= null;
    	this.rackRates 			= 0;
    	this.Cancellation		= new Array();
    	this.cancellationPolicy = '';
    	
    	this.state = {
            columns: [	{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false },
            			{ selected: false }
            		],
            totalDays: 14,
            firstSelectedColumn: null,
            lastSelectedColumn: null,
    		firstCheckedDate: null,
    		lastCheckedDate: null,
    		hiddenSelectOffset: 0,
            selectedColumns: []
    	}
    	
    	this.mouseOver = new Array();
    	
    	this.nodes = {
    			checkboxes: $$("#checkbox_row td input"),
    			dates: $$("#thematrix .mtxDateRow"),
    			checks: $$("#thematrix .mtx_date_select_td"),
    			dealTables: $('thematrix'),
    			hotelRows: $$("#thematrix tr.deal_row"),
    			deals: $$("#thematrix td.mtx_rate")
    	};
    	
    	this.rates 			= new Array();
    	this.dates 			= new Array();
    	this.deals 			= new Array();
    	this.ratesMapping	= new Array();
    	
    	MatrixHelper.prototype.checkCol = function(idx) {
    		var checkboxes = $$("#checkbox_row td input");
    		if (checkboxes[idx]) {
    			checkboxes[idx].checked = true;
    		}
    	}
    	
    	MatrixHelper.prototype.clearSelectedColumns = function() {
    		var checkboxes = $$("#checkbox_row td input");
    		checkboxes.each(function(item) {
    			item.checked = false;
    		});
    		
    		this.state.firstSelectedColumn = null;
    		this.state.lastSelectedColumn = null;    		
    		this.state.firstCheckedDate = null;
    		this.state.lastCheckedDate = null;
//    		this.state.hiddenSelectOffset = 0;

    		for (var i=0; i<this.state.columns.length; i++) {
    			if (this.state.columns[i].selected) {
    				this.deSelectColumn(i);
    			}
    		}
    	}
    	
    	MatrixHelper.prototype.initialize = function() {
    		if (!window.matrix || !window.matrix.loaded || window.matrix.matrixType!="Hotel") {
    			return;
    		}
    		this.state.firstSelectedColumn = null;
    		this.state.lastSelectedColumn = null;
    		this.state.firstCheckedDate = null;
    		this.state.lastCheckedDate = null;
    		this.state.hiddenSelectOffset = 0;
    		
    		this.state.columns.each(function(item) {
    			item.selected = false;
    		});
    		
    		if (window.matrix) {
    	    	matrix.data.allDates.each(function(item, idx) {
    	    		this[idx] = item.fmt;
        		}, this.dates);
    	    	for (var i=0; i<matrix.data.results.length; i++) {
    	    		this.rates[i]			= new RateObject(matrix.data.allDates, matrix.data.results[i].rates, matrix.data.results[i].promotions, matrix.data.results[i].rack_rate, matrix.data.currency, matrix.data.currency_symbol);
    	    		this.ratesMapping[i]	= matrix.data.results[i].id;
    	    		this.Cancellation[i]	= new Cancellation(matrix.data.results[i].cancellation_policy);
    	    		this.deals[i]			= { state: 'Book', active: true , errMsg: '' }
    	    	}

	    		if (window.matrix.checkIn && window.matrix.checkOut) {
	    			// pre-check for checkIn/chcekOut date in cookie
	    			var start = this.dates.indexOf(window.matrix.checkIn);
	    			this.state.firstCheckedDate = start;
	    			this.state.hiddenSelectOffset = this.dates.indexOf(window.matrix.startDate);
					var c_out = strToDate(window.matrix.checkOut);
					c_out.setDate(c_out.getDate()-1);
    				var end = this.dates.indexOf(c_out.format("%Y%m%d"));
    				this.state.lastCheckedDate = end;
    				end = end-this.state.hiddenSelectOffset;
    				
    				if (end > this.state.totalDays-1) {
    					end = this.state.totalDays-1;
    				}
    				
    				if (start>=0 && end>=0) {
	    				var $checkboxes = $$("#checkbox_row td input");
    			
    					for (var i=start; i<=end; i++) {
    						$checkboxes[i].checked = true;
    						this.selectColumn(i);
    					}
 						this.updateTotals();
 						this.reOrderMatrix();
    				} else {
    					this.setBookButtonState();
    				}
     			}
     		}
    	}
    	
    	MatrixHelper.prototype.setBookButtonState = function() {
    		$hotelRows = $$("#thematrix tr.deal_row");
        	for (i = 0; i < $hotelRows.length; i++) {
    	    	// remove existing total div
    	    	var tr_id = $($hotelRows[i]).id;
    	    	var rate_idx = this.ratesMapping.indexOf(tr_id);
    	    	var valid = true;
    	    	
    	    	var res						= new Reservation();
    	    	res.ratePlanId				= tr_id;
    	    	res.restrictions			= matrix.restrictions;
    	    	res.numNights				= matrix.reservation.numNights;
    	    	res.checkIn					= matrix.reservation.checkIn;
    	    	res.checkOut				= matrix.reservation.checkOut;
    	    	res.ratePlanInvalidDates	= matrix.reservation.ratePlanInvalidDates;
    	    	
    	    	if (res.numNights<1 || 
    	    			(res.numNights>0 && !res.isValid())) {
    	    		valid = false;
    	    	}
    	    	
    	    	this.deals[rate_idx].active = valid;

    	    	if (res.numNights > 0 && matrix.restrictions.reservationIsRestricted(matrix.reservation, tr_id, "OnRequest")) {
    	    		this.deals[rate_idx].state = 'Request Now';
    	    	} else {
    	    		this.deals[rate_idx].state = 'Book Now';
    	    	}

    	    	var btn = document.getElementById(tr_id + "_submit");
    	    	if (btn) {
    	    		if (this.deals[rate_idx].state == 'Book Now') {
    	    			if (this.deals[rate_idx].active) {
    	    				btn.className = "submitBook";
    	    			} else {
    	    				btn.className = "submitDisabled";
    	    			}
    	    		} else {
    	    			if (this.deals[rate_idx].active) {
    	    				btn.className = "submitRequest";
    	    			} else {
    	    				btn.className = "requestDisabled";
    	    			}
    	    		}
    	    	}
        	}
    	}
    	
    	MatrixHelper.prototype.verify = function() {
    		/* verify that calculated data is in the same currency as the updated selected currency */
    		if (!window.currencyConverter || !window.currencyConverter.indicativeCurrency) return;
    		if (this.rates[0].currencyCode.valueOf() != window.currencyConverter.indicativeCurrency.isoCode.valueOf()) {
    			this.refreshTotal();
    		}
    	}
    	
    	MatrixHelper.prototype.updateCancellation = function(idx) {
    		if (this.state.firstSelectedColumn===null && this.state.firstCheckedDate===null) return;
    		
    		//this.cancellationPolicy = this.Cancellation[idx].update(this.dates[this.state.firstSelectedColumn], this.dates[this.state.lastSelectedColumn]);
    		this.cancellationPolicy = this.Cancellation[idx].update(this.dates[this.state.firstCheckedDate], this.dates[this.state.lastCheckedDate]);
    		
    		return this.cancellationPolicy;
    	}
    	
    	MatrixHelper.prototype.refreshTotal = function() {
    		this.rates.each(function(item) { item.refresh(); });
    	}
    	
    	MatrixHelper.prototype.checkCheckboxes = function() {
			var checkboxes = $$("#checkbox_row td input");
	
			for (var i=0; i<checkboxes.length; i++) {
				if (this.state.columns[i].selected) {
					checkboxes[i].checked = true;
				}
			}
    	}
    	
    	MatrixHelper.prototype.toggleColumn = function(col) {
    		if (col< 0) return;
    		
    		if (this.state.columns[col].selected) {
    			this.deSelectColumn(col);
    		} else {
    			this.selectColumn(col);
    		}
    		this.updateTotals();
			this.reOrderMatrix();
    	}
    	
    	MatrixHelper.prototype.selectColumn = function(col) {
    		this.state.columns[col].selected = true;
    		if ((!this.state.firstSelectedColumn && this.state.firstSelectedColumn!=0) || col < this.state.firstSelectedColumn) {
    			this.state.firstSelectedColumn = col;			
    		}
    		if (!this.state.lastSelectedColumn || col > this.state.lastSelectedColumn) {
    			this.state.lastSelectedColumn = col;
    		}

    		if ((!this.state.firstCheckedDate && this.state.firstCheckedDate!=0) || col < this.state.firstCheckedDate-this.state.hiddenSelectOffset) {
    			this.state.firstCheckedDate = col+this.state.hiddenSelectOffset;
    		}
    		if (!this.state.lastCheckedDate || col+this.state.hiddenSelectOffset > this.state.lastCheckedDate) {
    			this.state.lastCheckedDate = col+this.state.hiddenSelectOffset;
    		}

        	$dates = $$("#thematrix .mtxDateRow");
        	$checks = $$("#thematrix .mtx_date_select_td");
    		
    		$dates[col].addClassName("selected");
    		$checks[col].addClassName("selected");
    		$('thematrix').addClassName("select" + col);

    		// remove mouse over
    		$hotelRows = $$("#thematrix tr.deal_row");
    		for (i = 0; i < $hotelRows.length; i++) {
    			var tr_id = $($hotelRows[i]).id;
    			var rate_idx = this.ratesMapping.indexOf(tr_id);
        		var deals = $$("#" + tr_id + " td.mtx_rate");
        		
        		if (!this.mouseOver[tr_id]) {
        			this.mouseOver[tr_id] = new Array();
        		}
        		this.mouseOver[tr_id][col] = deals[col].onmouseover;
        		deals[col].onmouseover = function() { return true; };
        	}
    	}
    	
    	MatrixHelper.prototype.deSelectColumn = function (col) {
    		this.state.columns[col].selected = false;		

        	$dates = $$("#thematrix .mtxDateRow");
        	$checks = $$("#thematrix .mtx_date_select_td");
        	
    		$dates[col].removeClassName("selected");
    		$checks[col].removeClassName("selected");
    		
    		// update first and last column references
    		var isFirst = col === (this.state.firstCheckedDate-this.state.hiddenSelectOffset);
    		var isLast = col === (this.state.lastCheckedDate-this.state.hiddenSelectOffset);
    		if (isFirst && isLast) {
    			this.state.firstSelectedColumn = null;
    			this.state.lastSelectedColumn = null;
    			if (this.state.hiddenSelectOffset == 0) {
    				this.state.firstCheckedDate = null;
    				this.state.lastCheckedDate = null;
    			}
    		} else {
    			if (isFirst) {
    				this.state.firstSelectedColumn++;
    				this.state.firstCheckedDate++;
    				if (this.state.firstSelectedColumn<this.state.totalDays) {
    					// in case of remove the last selected night in current window but still date selected for the future
    					$(this.nodes.checkboxes[this.state.firstSelectedColumn]).removeAttribute("disabled");
    				}
    			}
    			if (isLast) {
    				this.state.lastSelectedColumn--;
    				this.state.lastCheckedDate--;
    				if (this.state.lastSelectedColumn >= 0) {
    					$(this.nodes.checkboxes[this.state.lastSelectedColumn]).removeAttribute("disabled"); 				
    				}
    			}
    		}
    		$('thematrix').removeClassName("select" + col);

    		// put back mouse over
    		$hotelRows = $$("#thematrix tr.deal_row");
    		for (i = 0; i < $hotelRows.length; i++) {
    			var tr_id = $($hotelRows[i]).id;
        		var deals = $$("#" + tr_id + " td.mtx_rate");
        		deals[col].onmouseover = this.mouseOver[tr_id][col];
        	}
        }
    	
    	MatrixHelper.prototype.removeHoverMsg = function (oRow) {
    		// remove mouseover popup for selected column
    		if (this.state.firstSelectedColumn === null) return;
    		
    		$hotelRows = $$("#thematrix tr.deal_row");
        	for (i = 0; i < $hotelRows.length; i++) {
        		if (oRow && oRow.id!=$hotelRows[i].id) {
        			continue;
        		}
        		
    	    	// remove existing total div
    	    	var tr_id = $($hotelRows[i]).id;
    	    	var rate_idx = this.ratesMapping.indexOf(tr_id);
    	    	var deals = $$("#" + tr_id + " td.mtx_rate");
       
        		var startPos = this.state.firstSelectedColumn;
        		var endPos = startPos + (this.state.lastSelectedColumn - this.state.firstSelectedColumn);
        		
	    		for (var idx=startPos; idx<=endPos; idx++) {
	    			deals[idx].onmouseover = function() { return true; };
	    		}

    	    	if (oRow && oRow.id==$hotelRows[i].id) {
    	    		break;
    	    	}
    	    }
    	}
    	
    	MatrixHelper.prototype.updateTotals = function (cloned_row) {
        	// Loop through rows
    		$hotelRows = $$("#thematrix tr.deal_row");
        	for (i = 0; i < $hotelRows.length; i++) {
        		if (cloned_row && cloned_row.id!=$hotelRows[i].id) {
        			continue;
        		}
        		
    	    	// remove existing total div
    	    	var tr_id = $($hotelRows[i]).id;
    	    	var rate_idx = this.ratesMapping.indexOf(tr_id);
    	    	var deals = $$("#" + tr_id + " td.mtx_rate");
    	    	var total_wrappers = $$("#" + tr_id + " div.total_wrapper");
    	    	total_wrappers.each(function(item) {
    	    		item.remove();
    	    	});
    	    
    	    	var valid = true;
    	    	
    	    	var res						= new Reservation();
    	    	res.ratePlanId				= tr_id;
    	    	res.restrictions			= matrix.restrictions;
    	    	res.numNights				= matrix.reservation.numNights;
    	    	res.checkIn					= matrix.reservation.checkIn;
    	    	res.checkOut				= matrix.reservation.checkOut;
    	    	res.ratePlanInvalidDates	= matrix.reservation.ratePlanInvalidDates;
    	    	
    	    	if (res.numNights<1 || 
    	    			(res.numNights>0 && !res.isValid())) {
    	    		valid = false;
    	    	}
    	    	
    	    	this.deals[rate_idx].active = valid;
    	    	this.deals[rate_idx].errMsg = res.getNotAvailableMessage();

    	    	if (res.numNights > 0 && matrix.restrictions.reservationIsRestricted(matrix.reservation, tr_id, "OnRequest")) {
    	    		this.deals[rate_idx].state = 'Request Now';
    	    	} else {
    	    		this.deals[rate_idx].state = 'Book Now';
    	    	}

    	    	var btn = document.getElementById(tr_id + "_submit");
    	    	if (btn) {
    	    		if (this.deals[rate_idx].state == 'Book Now') {
    	    			if (this.deals[rate_idx].active) {
    	    				btn.className = "submitBook";
    	    			} else {
    	    				btn.className = "submitDisabled";
    	    			}
    	    		} else {
    	    			if (this.deals[rate_idx].active) {
    	    				btn.className = "submitRequest";
    	    			} else {
    	    				btn.className = "requestDisabled";
    	    			}
    	    		}
    	    	}
    	    	
    	    	if (this.state.firstSelectedColumn===null && this.state.firstCheckedDate===null) {
    	    		this.rates[rate_idx].numberOfNights = res.numNights;
    	    		continue;
    	    	}
    	    	
    	    	var rowPrice = 0;
    	    	rowPrice = this.rates[rate_idx].getTotal(this.dates[this.state.firstCheckedDate], this.dates[this.state.lastCheckedDate]);
    	    	
    	    	if (this.state.firstSelectedColumn===null || this.state.firstSelectedColumn>=this.state.totalDays) {
    	    		continue;
    	    	}
       
        		var extra_style = '';
        		var startPos = this.state.firstSelectedColumn;
        		var endPos = startPos + (this.state.lastSelectedColumn - this.state.firstSelectedColumn);
        		var days = endPos - startPos + 1;
        		var merge = true;
        		
        		if (endPos-startPos < 0) continue
				
        		if (endPos-startPos == 0) {
					// only one column show as selected
					extra_style = 'position:static;padding-top:14px';
					merge = false;
	        		if (this.state.firstCheckedDate!==null && this.state.lastCheckedDate!=null) {
	        			if (this.state.lastCheckedDate-this.state.firstCheckedDate) {
	        				merge = true;
	        			}
	        		}
				}
    	    	
    	    	if (!valid) {
    	    		merge = true;
    	    	}
    	    	
    	    	if (valid && merge) {
    	    		var symbol = this.rates[rate_idx].currencySymbol;
    	    		
    	    		if (window.currencyConverter.indicativeCurrency) {
    	    			symbol = window.currencyConverter.indicativeCurrency.symbol;
    	    		}
    	    		var data = {
    	    			Width: days*100,
    	    			AverageRate: this.rates[rate_idx].averageRateLocal,
    	    			FormattedAverageRate: addComma(this.rates[rate_idx].averageRate),
    	    			Symbol: symbol,
    	    			ExtraStyle: extra_style
    	    		};

    		    	$(deals[endPos]).innerHTML += this.totalTemplate.evaluate(data);
    		    	$($hotelRows[i]).addClassName("valid");
    	    	}
    	    	if (!valid) {
    		    	if (res.errorMsg != '') {
	    		    	var mouseOver = "showPinkPopup(this,'" + res.errorMsg + "', event)";
	    		    	var onClick = "toggleRatePlan('" + window.matrix.data.results[rate_idx].id + "', '" + escape(window.matrix.data.results[rate_idx].name) + "', '" + window.matrix.data.results[rate_idx].rpCode + "', '" + window.matrix.data.results[rate_idx].invSrc + "')";
    		    	}
    		    	$($hotelRows[i]).addClassName("valid");
    		    	
    		    	var label = res.getRestrictionLabel();

    		    	var data = {
    		    			Label: label,
        	    			Width: days*100,            	    			
        	    			Title: res.errorMsg,
        	    			OnMouseOver: mouseOver,
        	    			OnClick: onClick
        	    		};
		    		$(deals[endPos]).innerHTML += this.dateNotAvailableTemplate.evaluate(data);
    	    	}
    	    	if (!merge) {
    		    	$($hotelRows[i]).removeClassName("valid");
    	    	}
    	    	
    	    	if (cloned_row && cloned_row.id==$hotelRows[i].id) {
    	    		// remove mouseover on selected columns
    	    		for (var idx=startPos; idx<=endPos; idx++) {
    	    			deals[idx].onmouseover = function() { return true; };
    	    		}
    	    		break;
    	    	}
    	    }
        	
        	this.updateBlurbTotals();
        	
        }
    	
    	MatrixHelper.prototype.updateBlurbTotals = function() {
        	/* update expanded more info */
    		/* all rates are updated with updateTotals method */
    		var blurbs = $$('#thematrix tr.rate_plan_blurb_row');
    		if (blurbs && blurbs.length>0) {
    			for (var i=0; i<blurbs.length; i++) {
    				var row_id = blurbs[i].id;
    				var deal_id = row_id.split("_")[1];
    				var rate_idx = this.ratesMapping.indexOf(deal_id);
    				
    				if (rate_idx < 0) continue;
    				
    				matrix.checkRatePlanDateSelection(deal_id);
    				matrix.setDailyInclusions(blurbs[i], rate_idx);
    				
    				var d = $$("#" + row_id + " div.total-cover");
    				if (!this.deals[rate_idx].active) {
        				if (d && d[0]) {
    						d[0].hide();
        				}
        				matrix.setNotAvailable(rate_idx);
        				continue;
    				}
    				if (d && d[0]) {
    					d[0].show();
    				}
    				
    				var sp = $$("#" + row_id + " span.text-total-number");
    				if (sp && sp[0]) {
    					sp[0].update(addComma(this.rates[rate_idx].totalRate));
    					sp[0].update(addComma(this.rates[rate_idx].totalRate) + '<span class="or">' + this.rates[rate_idx].totalRateLocal + '</span>');
    				}
    				
    				var sp = $$("#" + row_id + " span.saved_currency_symbol");
    				if (sp && sp[0]) {
    					sp[0].update(this.rates[rate_idx].currencySymbol);
    				}
    				
    				var sp = $$("#" + row_id + " span.total_currency_symbol_sup");
    				if (sp && sp[0]) {
    					sp[0].update(this.rates[rate_idx].currencySymbol);
    				}
    				var save_span = $$("#" + row_id + " span.save-text");
    				if (save_span && save_span[0]) {
    					save_span[0].update(addComma(this.rates[rate_idx].savedRate) + '<span class="or">' + this.rates[rate_idx].savedRateLocal + '</span>');
    				}
    				
    				var save_span = $$("#" + row_id + " span.rack_rate");
    				if (save_span && save_span[0]) {
    					save_span[0].update(this.rates[rate_idx].currencySymbol);
    					save_span[1].update(addComma(this.rates[rate_idx].totalRackRate) + '<span class="or">' + this.rates[rate_idx].totalRackRateLocal + '</span>');
    				}
    				//total-nights-number
    				var sp = $$("#" + row_id + " span.total-nights-number");
    				if (sp && sp[0]) {
    					var text = this.rates[rate_idx].numberOfNights + " ";
    					text += (this.rates[rate_idx].numberOfNights>1) ? 'nights' : 'night';
    					sp[0].update(text);
    				}
    				
    				var d = $$("#" + row_id + " div.save-icon");
    				if (d && d[0]) {
    					if (this.rates[rate_idx].savedRate <=0) {
    						d[0].hide();
    					} else {
    						d[0].show();
    					}
    				}
    				
    				var cancellation_policy = this.updateCancellation(rate_idx);
    				var sp = $$("#" + row_id + " span.cancellation_policy");
    				if (sp && sp[0]) {
    					sp[0].update(cancellation_policy);
    				}
    				
    				var sp = $$("#" + row_id + " div.btn-pink");
    				if (sp && sp[0]) {
    					sp[0].update(this.deals[rate_idx].state);
    				}
    				matrix.setNotAvailable(rate_idx);
    			}
    		}
    	}
    	MatrixHelper.prototype.reOrderMatrix = function () {
	    	if (this.state.firstSelectedColumn===null && this.state.firstCheckedDate===null) {
	    		return;
	    	}

    		$hotelRows = $$("#thematrix tr.deal_row");
    		var activeDeals = Array();var aD=0;
        	for (i = 0; i < $hotelRows.length; i++) {
        		var deal_id = $hotelRows[i].id;
        		var rate_idx = this.ratesMapping.indexOf(deal_id);
    	    	var rowPrice = 0;
    	    	rowPrice = this.rates[rate_idx].getTotal(this.dates[this.state.firstCheckedDate], this.dates[this.state.lastCheckedDate]);
    	    	if (this.deals[rate_idx].active && rowPrice>0) {
    	    		var sortOrder = rowPrice+i.toPaddedString(4);
    	    		activeDeals[aD] = [ Number(sortOrder) , i ];aD++;
    	    	}
        	}

        	$ratePlanBlurbs = $$("#thematrix tr.rate_plan_blurb_row");
        	var activeBlurbs = Array();var aB=0;
        	for (i = 0; i < $ratePlanBlurbs.length; i++) {
        		var blurb_id = $ratePlanBlurbs[i].id;
        		var ids = blurb_id.splitOnUnderscore();
        		if(ids[1]>0) {
        			activeBlurbs[aB] = [ Number(ids[1]), Number(i) ];aB++;
        		}
        	}

    		var x = activeDeals.multiSort(0);

    		if(aD>0) {
	    		for(var i=(aD-1);i>=0;i--) {
	    			$('checkbox_row').insert({after : $hotelRows[x[i][1]] });
	    		}

	    		if(aB>0) {
	    			for(var i=0;i<aB;i++) {
	    				var deal_id = activeBlurbs[i][0].toString();
	   					$(deal_id).insert({after : $ratePlanBlurbs[activeBlurbs[i][1]] });
	    			}
	    		}
    		}
    	}
    }
    
    function RateObject (dates, rates, promotions, rack_rate, currency, currencySymbol) {
    	this.rates 				= rates;
    	this.valid 				= false;
    	this.promotions			= promotions;
    	this.rackRate 			= parseFloat(rack_rate);
    	this.dates 				= new Array();
    	this.totalRate 			= 0;
    	this.totalRateLocal 	= 0;
    	this.averageRateLocal	= 0;
    	this.averageRate		= 0;
    	this.savedRate 			= 0;
    	this.savedRateLocal		= 0;
    	this.numberOfNights		= 0;
    	this.currencyCode		= currency;
    	this.currencySymbol 	= currencySymbol;
    	this.totalRackRate		= 0;
    	this.totalRackRateLocal	= 0;
    	
    	dates.each(function(item, idx) {
    		this[idx] = item.fmt;
    	}, this.dates);
    	
    	RateObject.prototype.refresh = function() {
    		if (window.currencyConverter && window.currencyConverter.indicativeCurrency) {
    			this.currencyCode	= window.currencyConverter.indicativeCurrency.isoCode;
    			this.currencySymbol	= window.currencyConverter.indicativeCurrency.symbol;
    			this.totalRate 		= convertCurrency(this.totalRateLocal+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.averageRate	= convertCurrency(this.averageRateLocal+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.totalRackRate	= convertCurrency(this.totalRackRateLocal+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.savedRate		= this.totalRackRate-this.totalRate;
    		}
    	}
    	
    	RateObject.prototype.getTotal = function(checkIn, checkOut) {
    		this.numberOfNights = this.dateDiff(checkIn, checkOut)+1;
    		var total = 0;
    		this.valid = true;
    		var start = this.dates.indexOf(checkIn);
    		var end = this.dates.indexOf(checkOut);
    		var p_rates = new Array();
    		var idx = 0;
    		
    		for (var i=start; i<=end; i++) {
    			var fmt = this.dates[i];
    			if (!this.rates[fmt]) {
    				this.valid = false;
    				return 0;
    			} else {
    				total += parseFloat(this.rates[fmt]['p']);
    			}
    			p_rates[idx] = parseFloat(this.rates[fmt]['p']);
    			idx++;
    		}
    		
    		// apply promotion discount
    		var discount = 0;
    		for (var i=0; i<this.promotions.length; i++) {
    			if (this.promotions[i].type == "FreeNights") {
    				var fn = new FreeNights(this.promotions[i]);
    				discount += fn.getDiscount(checkIn, checkOut, p_rates);
    			}
    			
    		}
    		
    		if (this.rackRate > 0) {
    			this.totalRackRate = this.rackRate*this.numberOfNights;
    		} else {
    			this.totalRackRate = total;
    		}
    		
    		this.totalRackRate = this.totalRackRate.floor();
    		this.totalRackRateLocal = this.totalRackRate;
    		
    		total -= discount;
    		this.averageRate = total/this.numberOfNights;
    		
    		this.averageRate = this.averageRate.floor();
    		this.averageRateLocal = this.averageRate;
    		
    		this.totalRate = total.floor();
    		this.totalRateLocal = this.totalRate;
    		
    		this.savedRate		= this.totalRackRate-this.totalRate;
    		this.savedRate		= this.savedRate.floor();
    		this.savedRateLocal = this.savedRate;

    		total = total.floor();
    		
    		if (window.currencyConverter && window.currencyConverter.indicativeCurrency) {
    			this.currencyCode	= window.currencyConverter.indicativeCurrency.isoCode;
    			this.currencySymbol	= window.currencyConverter.indicativeCurrency.symbol;
    			this.totalRate 		= convertCurrency(this.totalRate+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.averageRate	= convertCurrency(this.averageRate+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.totalRackRate	= convertCurrency(this.totalRackRate+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			total				= convertCurrency(total+'', window.currencyConverter.defaultCurrency.relativeRate, window.currencyConverter.indicativeCurrency.relativeRate);
    			this.savedRate		= this.totalRackRate-this.totalRate;
    		}
    		
    		return total;
    	}
    	
    	RateObject.prototype.dateDiff = function(date1, date2) {
    		var d1 = strToDate(date1);
    		var d2 = strToDate(date2);
    		return (d2-d1)/(24*60*60*1000);
    	}
    }
    
    function FreeNights(obj) {
    	this.rule = obj;
    	this.nightCount = 0;
    	
    	FreeNights.prototype.getDiscount = function(checkIn, checkOut, rates) {
    		var discount = 0;
    		this.startDate = strToDate(this.rule.start_date);
    		this.endDate = strToDate(this.rule.end_date);
    		this.numberOfNights = this.rule.number_of_nights;
    		this.data = this.rule.data;
    		var c_in = strToDate(checkIn);
    		var c_out = strToDate(checkOut);
    		
		if (this.startDate>c_out || this.endDate<c_in) {
			return 0;
		}

    		if (c_in>=this.startDate) {
			var d = new Date(c_in);
		} else {
			var d = new Date(this.startDate);
		}
    		
    		while (d<=c_out) {
			if (d > this.endDate) break;

    			if (d>=this.startDate || d<=this.endDate) this.nightCount++;
    			d.setDate(d.getDate()+1);
    		}
    		if (this.nightCount>= this.numberOfNights) {
    			var data = this.data.split(",");
    			var limit = data[1];
    			
    			for (var i=this.numberOfNights-1; i<this.nightCount; i+=this.numberOfNights) {
    				discount += parseFloat(rates[i])*parseFloat(data[0]);
    				if (limit!="*" && i >= parseFloat(limit)) break;
    			}
    		}
    		
    		return discount;
    	}
    	
    	FreeNights.prototype.strToDate = function(str) {
    		return new Date(str.substr(0,4), str.substr(4,2), str.substr(6,2));
    	}
    }

    function Cancellation(obj) {
    	this.details	= obj;    	
    	this.startDate	= null;
    	this.endDate	= null;
    	
    	Cancellation.prototype.update = function(startDate, endDate) {
    		this.startDate = strToDate(startDate);
    		this.endDate = strToDate(endDate);
    		
    		var applies = new Array();
    		for (var i=0; i<this.details.length; i++) {
    			var start = strToDate(this.details[i].start_date);
    			var end = strToDate(this.details[i].end_date);
    			
				if (start<= this.endDate && end>=this.startDate) {
    				applies.push(this.details[i]);
    				if (this.details[i].type == "F") {
    					break;
    				}
    			}
    		}
    		
    		if (applies.length >0) {
    			this.apply(applies);
    		}
    		
    		return this.policy;
    	}
    	
    	Cancellation.prototype.apply = function(policies) {
			for (var i=0; i<policies.length; i++) {
				var policy = policies[i];
				if (policy.type == "F") {
					break;
				}
			}
    		
			if (!policy) {
				return false;
			}
    		
			this.decode(policy);
    	}
    	
    	Cancellation.prototype.decode = function(policy) {
    		if (policy.type == "T") {
    			this.policy = policy.text;
    			return;
    		}
    		if (policy.type == "O") {
    			if (policy.due_date == -1) {
    				this.policy = "This property's policy is that any cancellations or changes of dates to bookings will forfeit the full amount of the first changed or cancelled night.";
    			} else {
    				this.policy = "This rate requires any cancellation or date changes to be made at least " + (policy.due_date+1) + " days prior to check-in time to avoid forfeiting the full amount of the first changed or cancelled night.";    				
    			}
    		} else if (policy.type == "F") {
    			if (policy.due_date == -1) {
    				this.policy = "This property will not offer a refund if you want to cancel your booking for any reason.";
    			} else {
    				this.policy = "This rate requires any cancellation or date changes to be made at least " + (policy.due_date+1) + " days prior to check-in time to avoid forfeiting the full amount of the booking.";
    			}
    		}
    	}
    	
    	Cancellation.prototype.formatDate = function (oDate) {
    	    var d = oDate.getDate();
    	    var dateStr = (d<10) ? "0" + d : d;
    	    var m = oDate.getMonth() + 1;
    	    dateStr += " " + ((m<10) ? "0" + m : m);
    	    dateStr += " " + oDate.getFullYear();
    	    return dateStr;  

    	}
    }

;
//function checkShortlistIcon() { }
/**** MATRIX EVENT LISTENERS ****/

document.observe("matrix:loading", function(e) {
	var d = document.getElementById("mtx_loading");
	if (d) {
	    d.style.display = "inline";
	}
});

document.observe("matrix:loaded", function(e) {
	var d = document.getElementById("mtx_loading");
	if (d) {
	    d.style.display = "none";
	}
	
	var reqFilter = $('mtx_filterOnRequest');
	if (reqFilter != null) {
        reqFilter.checked = !matrix.showOnRequest;
	}
	if ($('mtx_rateBlurb')) $('mtx_rateBlurb').style.display = 'none';
	
	if (!window.mtxHelper) {
		mtxHelper = new MatrixHelper();
	}
	mtxHelper.initialize();
	
	
});

document.observe("matrix:checkInOutSetExternaly", function(e) {
	syncronizeCalendar();
	_f_set_mtx_s_ChangeMY();
});

document.observe("dom:loaded", function(e) {
	if (window.matrix && !window.mtxHelper) {
		mtxHelper = new MatrixHelper();
		mtxHelper.initialize();
	}
	
	preLoadImages();
});

/**** MATRIX WRAPPER FUNCTIONS ****/

function setMatrixSkin(skinName) {
	window.matrix.setSkin(skinName);
}

function loadCheckInMatrix() {
    var checkIn = document.getElementById("mtx_checkInYear").value + document.getElementById("mtx_checkInMonth").value + document.getElementById("mtx_checkInDate").value;
    var checkOut = document.getElementById("mtx_checkOutYear").value + document.getElementById("mtx_checkOutMonth").value + document.getElementById("mtx_checkOutDate").value;

    matrix.loadCheckInMatrix(checkIn, checkOut);
}

function loadCheckInMatrixMonth(obj) {
	var checkIn = obj.value;
	var checkOut = obj.value;

	var _today = new Date();
	var _cMonth = _today.getMonth() + 1;
	var _cYear = _today.getFullYear();
	if(_cYear<2000) _cYear = _cYear + 1900;
	_cMonth = "0" + _cMonth;
	_cMonth = _cMonth.substring(_cMonth.length - 2, _cMonth.length);
	var checkIn;
	var checkOut;
	var _cDate;
	if(obj.value == (_cYear + "" + _cMonth))
	{
		_cDate = "0" + _cDate;
		_cDate = _cDate.substring(_cDate.length - 2, _cDate.length);
		_cDate = obj.value + "" + _cDate;
	}
	else
	{
		_cDate = obj.value + "01";
	}

	matrix.startDate = _cDate;
	matrix.endDate = incDateStr(matrix.startDate, matrix.days);
	matrix.loadMatrix();  
}

function loadCheckInDestMatrix() {
	loadCheckInMatrix();
}

function foobarbaz() { loadCheckInMatrix(); }

function resetCheckIn() {
	document.getElementById("mtx_loading").style.display = "none";
	matrix.checkIn = "";
	matrix.checkOut = "";
	matrix.reservation = null;
    matrix.loadMatrix();
}

function loadMatrixFrom(startDate) {
	matrix.startDate = startDate;  
    matrix.loadMatrix();
}

function mtx_nextPage() { matrix.nextPage(1); }
function mtx_prevPage() { matrix.prevPage(1); }
function mtx_nextPageJump() { matrix.nextPage(7); }
function mtx_prevPageJump() { matrix.prevPage(7); }

function mtx_gotoPage(pageNum) {
	window.matrix.gotoPage(pageNum);
	gotoMtxTop();
}

function mtx_moveNext() {
	window.matrix.moveNext();
	gotoMtxTop();
}

function mtx_movePrevious() {
	window.matrix.movePrevious();
	gotoMtxTop();
}

function gotoMtxTop() {
	var currentHref = window.location.href;
	window.location.href = currentHref.substr(0, currentHref.lastIndexOf('#')) + '#mtxTop';
}

function selectRatePlan(ratePlanId, ratePlanName, offerId, bookingAdapter, even) { matrix.selectRatePlan(ratePlanId, ratePlanName, offerId, bookingAdapter); }

function toggleRatePlan(ratePlanId, ratePlanName, offerId, bookingAdapter, even) {
	if (matrix.lastRatePlan && matrix.lastRatePlan.id==ratePlanId) {
		closeInfo(ratePlanId);
	} else {
		matrix.selectRatePlan(ratePlanId, ratePlanName, offerId, bookingAdapter);
	}
}

function displayMatrix(data) {
	matrix.displayMatrix(data);
	if ($("ssSearchBtn")) $("ssSearchBtn").disabled = "";
}

function setDisplayCurrencyExternally(currencyCode) {
	var expire = new Date(new Date().getTime() + (24 * 1000 * 60 * 60 * 24));
	setCookie('ccCodePersist', currencyCode, expire);
	matrix.setDisplayCurrencyExternally(currencyCode);
}


/**** USER INTERACTION FUNCTIONS ****/

/**
 *  Actioned when a user clicks a rate checkbox 
 */

function findParentNodeByTagName(obj, name) {
	  var pr = obj.parentNode;
	  while (pr.tagName.toLowerCase() != name) {
		  pr = pr.parentNode;
	  }
	  return pr;
}

function toggleDate(checkbox, dateStr, idx) {
	if (!matrix.reservation) matrix.reservation = new Reservation();
	var changed = matrix.reservation.toggleDate(strToDate(dateStr));
	matrix.setCookie();

  if (!changed) {
	  var xy = getXYpos(checkbox);
      document.getElementById("mtx_errorMsg").innerHTML = "<strong>You may only book consecutive dates.</strong><br />Please ensure that the room you have chosen is available on each night you wish to stay, and check the corresponding boxes for each night. If you wish, you may clear all currently selected dates.";
      
      // extra clear button
      document.getElementById("mtx_errorButton").innerHTML = "<input type='submit' value='Clear' onclick='mtx_clearOtherDates(" + 
	  	"\"" + dateStr + "\"," + idx + ");document.getElementById(\"dealSelectionError\").style.display = \"none\";'>";
      
      // change value for default close button
      $('errMsgCloseBtn').value = 'Keep my selected dates';
    	  
    showDealSelectionErrorAt(xy["y"], xy["x"]);
  } else {
	  mtxHelper.toggleColumn(idx);
  }
 
  return changed;
}

function closeInfo(ratePlanId) {
	matrix.hideRatePlanSelection(ratePlanId);
	var a;
	eval("a = $('" + ratePlanId + "');");
	if(a!=null)
	{
		unHilightRow(a);
	}
	lowLightRow(ratePlanId);
}

function mtx_clearOtherDates(dateStr, idx) {
	matrix.checkIn = "";
	matrix.checkOut = "";
	matrix.reservation.clearNights();
	mtxHelper.clearSelectedColumns();
	mtxHelper.checkCol(idx);
	return toggleDate(null, dateStr, idx);
}



function book(ratePlanId, ratePlanName, rtId, offerId, bookingAdapter) {
	if (!matrix.reservation) {
		matrix.reservation = new Reservation (ratePlanId, ratePlanName, rtId, offerId, matrix.requestedId, matrix.restrictions, bookingAdapter);
	} else {
		matrix.reservation.hotelId = matrix.requestedId;
		matrix.reservation.ratePlanId = ratePlanId;
		matrix.reservation.ratePlanName = ratePlanName;
		matrix.reservation.rtId = rtId;
		matrix.reservation.offerId = offerId;
		matrix.reservation.restrictions = matrix.restrictions;
		matrix.reservation.bookingAdapter = bookingAdapter;
	}

    if (!matrix.reservation.book()) {
    	btn = document.getElementById(matrix.reservation.ratePlanId + "_submit");
        var xy = getXYpos(btn); 
        document.getElementById("mtx_errorMsg").innerHTML = matrix.reservation.errorMsg;
        document.getElementById("mtx_errorButton").innerHTML = '';
        // change value for default close button
        $('errMsgCloseBtn').value = 'Close';
        showDealSelectionErrorAt(xy["y"], xy["x"]); 
    }
    
}  

var tempClassName;

function showRateBlurbField(element, msgField, ev) {
    var incl = document.getElementById(msgField);
    // var xy = getXYpos(element); 
    var xy = getMOUSEpos(ev);
    document.getElementById("mtx_rateBlurbMsg").innerHTML = incl.value;
    showRateBlurbAt(xy["y"], xy["x"]);
}

function showRateBlurb(element, msg) {
    var xy = getXYpos(element); 
    document.getElementById("mtx_rateBlurbMsg").innerHTML = msg;
    showRateBlurbAt(xy["y"], xy["x"]);
}

function showPinkPopup(element, msg, ev) {
    document.getElementById("pink_popup_message").innerHTML = msg;
    showPinkPopupAt(Event.pointerY(ev), Event.pointerX(ev));
}

function hidePinkPopup() {
	document.getElementById('pink_popup').style.display = 'none';
}

function showPinkPopupAt(styleTop, styleLeft) {
  var errorMsg = document.getElementById('pink_popup');
  var padding = 0;
  var windowWidth = document.viewport.getWidth();  
  var objWidth = $("pink_popup").getWidth();
  if (styleLeft+objWidth-padding+15 > windowWidth)
  {
      styleLeft = styleLeft-objWidth-15;
  }
  
  var windowHeight = document.viewport.getHeight();
  var objHeight = $('pink_popup').getHeight();
  var offsetTop = document.viewport.getScrollOffsets().top;
  if ((styleTop+objHeight+15) > (windowHeight+offsetTop)) {
	  styleTop = styleTop-objHeight-15;
  }
  if (styleTop < 0) {
      styleTop = 0;
  }
  
  styleLeft += 15;
  styleTop += 15;

  errorMsg.style.top		= styleTop + 'px';
  errorMsg.style.left		= styleLeft + 'px';
  errorMsg.style.margin		= '0';
  errorMsg.style.display	= 'block';
}

function hideRateBlurb(element) {
	document.getElementById('mtx_rateBlurb').style.display = 'none';
}

function toggleOnRequest(checkbox) {
    matrix.showOnRequest = !checkbox.checked;
    matrix.loadMatrix();  
}


var rateBlurbMessageWidth = 160;

function showRateBlurbAt(styleTop, styleLeft) {
  var errorMsg = document.getElementById('mtx_rateBlurb');
  var padding = 0;
  dealSelectionErrorMessageWidth = $('mtx_rateBlurb').getWidth();
  var windowWidth = document.viewport.getWidth();
  if (styleLeft + dealSelectionErrorMessageWidth+ 15 - padding > windowWidth)
  {
	  styleLeft = styleLeft-dealSelectionErrorMessageWidth-15;
  }
  
  var windowHeight = document.viewport.getHeight();
  var objHeight = $('mtx_rateBlurb').getHeight();
  var offsetTop = document.viewport.getScrollOffsets().top;
  if ((styleTop+objHeight+15) > (windowHeight+offsetTop)) {
	  styleTop = styleTop-objHeight-15;
  }
  if (styleTop < 0) {
	  styleTop = 0;
  }
  
  styleLeft += 15;
  styleTop += 15;

  errorMsg.style.margin		= '0';
  errorMsg.style.top 		= styleTop + 'px';
  errorMsg.style.left		= styleLeft + 'px';
  errorMsg.style.zIndex		= '100';
  errorMsg.style.display	= 'block';
}

var dealSelectionErrorMessageWidth = 230;

function showDealSelectionErrorAt(styleTop, styleLeft) {
  var errorMsg = document.getElementById('dealSelectionError');
  var padding = 20;
  var windowWidth = (document.body) ? document.body.offsetWidth : window.innerWidth;
  if (styleLeft + dealSelectionErrorMessageWidth - padding > windowWidth)
  {
      styleLeft = windowWidth - dealSelectionErrorMessageWidth - padding;
  }
  if (styleTop < 0) {
      styleTop = 0;
  }
  
  styleLeft = (windowWidth/2) - padding;

  errorMsg.style.width = dealSelectionErrorMessageWidth + 'px';
  errorMsg.style.top = styleTop + 'px';
  errorMsg.style.left = styleLeft + 'px';
  errorMsg.style.display = 'block';
  
  // hack to prevent matrix disappear on IE6
  $('outerdiv').style.display = 'none';
  $('outerdiv').style.display = 'block';
}

function hideDealSelectionError() {
	document.getElementById('dealSelectionError').style.display = 'none';
	
	// hack to prevent matrix disappear on IE6
	$('outerdiv').style.display = 'none';
	$('outerdiv').style.display = 'block';
}

function toggleRatePlanBlurb(toggleBtn) {
	var ratePlanBlurb = document.getElementById('ratePlanBlurb');
	
	if (ratePlanBlurb.style.display == "none") {
		toggleBtn.innerHTML = "&#9650;";
		ratePlanBlurb.style.display = "block";
		toggleBtn.title = "Hide offer information";	
	} else {
		toggleBtn.innerHTML = "&#9660;";
		ratePlanBlurb.style.display = "none";
		toggleBtn.title = "Show offer information";
	}
	
}

function setDateSelects(checkIn, checkOut) {
	setSelectedByIndex("mtx_checkInDate", (parseInt(checkIn.substring(6, 8), 10) - 1));
	setSelectedByIndex("mtx_checkInMonth", (parseInt(checkIn.substring(4, 6), 10) - 1));
	setSelectedByValue("mtx_checkInYear", checkIn.substring(0, 4));
	updateCheckAvailAllShort('sDate');
	setSelectedByIndex("mtx_checkOutDate", (parseInt(checkOut.substring(6, 8), 10) - 1));
	setSelectedByIndex("mtx_checkOutMonth", (parseInt(checkOut.substring(4, 6), 10) - 1));
	setSelectedByValue("mtx_checkOutYear", checkOut.substring(0, 4));
	updateCheckAvailAllShort('eDate');
}	

function _f_set_mtx_s_ChangeMY()
{
	if (typeof(matrix) == "undefined") return false;
	if (typeof(matrix.startDate) == "undefined") return false;
	var _cYearMonth = matrix.startDate;
	_cYearMonth = _cYearMonth.substring(0, 6);

	var select = document.getElementById("mtx_s_ChangeMY");
	if(select!=null)
	{
		for (var i = select.options.length - 1; i >= 0; i--) {
			if (select.options[i].value == _cYearMonth) {
				select.selectedIndex = i;
				break;
			}
		}
	}
}

/**** UTILITY FUNCTIONS *****/
  
  /**
   * @purpose            : Get the (x,y) coordinate of a DOM element
   * @param  object elem : a DOM element or null
   * @return object      : an associative array, where indicies "x"
   *                       and "y" hold their respective coordinates
   * @note               : function is recursive
   */
function getXYpos(elem) {
    if (!elem) {
	    return {"x":0,"y":0};
	}
 	var xy={"x":elem.offsetLeft,"y":elem.offsetTop}
    var par=getXYpos(elem.offsetParent);
    for (var key in par) {
        xy[key]+=par[key];
    }

    return xy;
}

 /*
 ******************************************************
 */
 function getScrollXY()
{
  var scrOfX = 0, scrOfY = 0;
  if( typeof( window.pageYOffset ) == 'number' ) {
    //Netscape compliant
    scrOfY = window.pageYOffset;
    scrOfX = window.pageXOffset;
  } else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
    //DOM compliant
    scrOfY = document.body.scrollTop;
    scrOfX = document.body.scrollLeft;
  } else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
    //IE6 standards compliant mode
    scrOfY = document.documentElement.scrollTop;
    scrOfX = document.documentElement.scrollLeft;
  }
  return {x:scrOfX, y:scrOfY};
}

function mouseCoords(ev) {
	var oScroll = getScrollXY();
	if (ev.pageX || ev.pageY) {
		return {x:ev.pageX, y:ev.pageY};
	}
	return {
		x:(ev.clientX + document.body.scrollLeft - document.body.clientLeft)+oScroll.x,
		y:(ev.clientY + document.body.scrollTop  - document.body.clientTop)+oScroll.y
	};
}

 function getMOUSEpos(ev)
{
	var mousePos = mouseCoords(ev);
	var xy = new Array();
	xy['x'] = mousePos.x;
	xy['y'] = mousePos.y;
	// alert(mousePos.y);
	return xy;
}
 /*
 ******************************************************
 */        

function highlightRow(tr) {
   var tds = tr.getElementsByTagName('td');
   var total = tds.length;
  
   $(tds[0]).addClassName('expanded_row_left');
   $(tds[total - 1]).addClassName('expanded_row_right');

   for (var i = 0; i < total; i++) {
      if (tds[i].parentNode == tr) {
    	  $(tds[i]).addClassName('expanded_row');
      }
   }
}
function lowLightRow(row_id) {
	   var tds = $(row_id).getElementsByTagName('td');
	   var total = tds.length;
	  
	   $(tds[0]).removeClassName('expanded_row_left');
	   $(tds[total - 1]).removeClassName('expanded_row_right');

	   for (var i = 0; i < total; i++) {
    	  $(tds[i]).removeClassName('expanded_row');
	   }
	
}
   
   
function incDateStr(startDateStr, numDays) {
    var d = strToDate(startDateStr); 
    d.setDate(d.getDate() + numDays);
    return formatDate(d);
}

/* YYYYMMDD */
function formatDate(date) {
    var dateStr = date.getFullYear();
    var m = date.getMonth() + 1;
    dateStr = dateStr + ((m < 10)?"0" + m: "" + m);
    var d = date.getDate();
    dateStr = dateStr + ((d < 10)?"0" + d: "" + d);  
    return dateStr;  
}

/* YYYYMMDD */
function strToDate(dateStr) {
    return new Date(dateStr.substring(0, 4),(parseInt(dateStr.substring(4, 6), 10) - 1), parseInt(dateStr.substring(6, 8), 10))
}

function incrementDate(date, numDays) {
    return new Date(date.getTime() + (numDays * 86400000));
}

function setSelectedByValue(selectId, value) {
	select = document.getElementById(selectId);
	if (select) {
		for (i = select.options.length - 1; i >= 0; i--) {
			if (select.options[i].innerHTML == value) {
				select.selectedIndex = i;
				break;
			}
		}
	}
	return select;
}

function setSelectedByIndex(selectId, index) {
	select = document.getElementById(selectId);
	if (select) select.selectedIndex = index;
	return select;
}

function setSortFields(field) {
	$('sortHotelName').style.fontWeight = 'normal';   	
    $('sortCategory').style.fontWeight = 'normal';
    $('sortHotelName').style.textDecoration = 'underline';
    $('sortCategory').style.textDecoration = 'underline';
    $('arrowHotelName').style.visibility = 'hidden';   	
    $('arrowCategory').style.visibility = 'hidden';
    $('sort' + field).style.fontWeight = 'bold';
    $('sort' + field).style.textDecoration = 'none';
    $('arrow' + field).style.visibility = 'visible';
}

function toggleSort(field, fieldName) {
	var arrowField = $('arrow' + field);
	if (fieldName == matrix.curSortBy) {
		/* Is upsidedown because searchResults.sortDirection is yet to be toggled */
    	if (matrix.sortDirection[fieldName] == 1) {
    		arrowField.update('&darr;');
    	} else {
    		arrowField.update('&uarr;');
    	}
    	
    } else {
    	setSortFields(field);    
    }

}

function doSortBy(field) {
	toggleSort(field, field);
    matrix.sortBy(field);
}
function adjustStars(min, max) {
    for (i = 5; i > 0; i--) {
        minStar = $('min' + i);
        maxStar = $('max' + i);
        
        if  (i > min) {
        	minStar.style.visibility = 'hidden';
        	minStar.style.width = 0;
        } else {
        	minStar.style.width = '11px';
        	minStar.style.visibility = 'visible';
        }
        
        if  (i > max) {
        	maxStar.style.visibility = 'hidden';
        	maxStar.style.width = 0;
        } else {
        	maxStar.style.width = '11px';
        	maxStar.style.visibility = 'visible';
        }
        
    }
}

/**
 * Class for search sliders.
 */
/*
function SearchSliders() {
	if (!$('minCatHandle')) return;
	
	this.searchEnabled = true;
    this.catHandles = ['minCatHandle', 'maxCatHandle'];
    this.catSlider = new Control.MtxSlider(this.catHandles, 'catSlider', {
	    range: $R(1, 5),
	    values: [1,2,3,4,5],
	    sliderValue: [1, 5],
	    spans: ["catSliderSpan"],
	    restricted: true
	});

	// Setting the callbacks later on
	var minCategory = $('minCategory');
	var maxCategory = $('maxCategory');
	  
	this.catSlider.options.onSlide = function(value) {
	  minCategory.update(value[0]);
	  maxCategory.update(value[1]);
	  adjustStars(value[0], value[1]);
	};
	
	this.catSlider.options.onChange = function(value) {
	  doFilter();
	};

}

SearchSliders.prototype.setSearchEnabled = function(value) {
    this.searchEnabled = value;
}

SearchSliders.prototype.setValue = function(minValue, maxValue) {
	var onSlide = this.catSlider.options.onSlide;
	var onChange = this.catSlider.options.onChange;
	this.catSlider.options.onSlide = function() {};
	this.catSlider.options.onChange = function() {};
    this.catSlider.setValue(minValue, 0);
    this.catSlider.setValue(maxValue, 1);
    this.catSlider.options.onSlide = onSlide;
    this.catSlider.options.onChange = onChange;
}
*/

//Copyright (c) 2005 Marty Haught, Thomas Fuchs 
//
// See http://script.aculo.us for more info
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

if(!Control) var Control = {};
Control.MtxSlider = Class.create();

// options:
//  axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
//  onChange(value)
//  onSlide(value)
Control.MtxSlider.prototype = {
  initialize: function(handle, track, options) {
    var slider = this;
    
    if(handle instanceof Array) {
      this.handles = handle.collect( function(e) { return $(e) });
    } else {
      this.handles = [$(handle)];
    }
    
    this.track   = $(track);
    this.options = options || {};

    this.axis      = this.options.axis || 'horizontal';
    this.increment = this.options.increment || 1;
    this.step      = parseInt(this.options.step || '1');
    this.range     = this.options.range || $R(0,1);
    this.value     = 0; // assure backwards compat
    this.values    = this.handles.map( function() { return 0 });
    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
    this.options.startSpan = $(this.options.startSpan || null);
    this.options.endSpan   = $(this.options.endSpan || null);

    this.restricted = this.options.restricted || false;

    this.maximum   = this.options.maximum || this.range.end;
    this.minimum   = this.options.minimum || this.range.start;
    // Will be used to align the handle onto the track, if necessary
    this.alignX = parseInt(this.options.alignX || '0');
    this.alignY = parseInt(this.options.alignY || '0');
    
    this.trackLength = this.maximumOffset() - this.minimumOffset();
    this.handleLength = this.isVertical() ? this.handles[0].offsetHeight : this.handles[0].offsetWidth;

    this.active   = false;
    this.dragging = false;
    this.disabled = false;

    if(this.options.disabled) this.setDisabled();

    // Allowed values array
    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
    if(this.allowedValues) {
      this.minimum = this.allowedValues.min();
      this.maximum = this.allowedValues.max();
    }

    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
    this.eventMouseMove = this.update.bindAsEventListener(this);

    // Initialize handles in reverse (make sure first handle is active)
    this.handles.each( function(h,i) {
      i = slider.handles.length-1-i;
      slider.setValue(parseFloat(
        (slider.options.sliderValue instanceof Array ? 
          slider.options.sliderValue[i] : slider.options.sliderValue) || 
         slider.range.start), i);
      Element.makePositioned(h); // fix IE
      Event.observe(h, "mousedown", slider.eventMouseDown);
    });
    
    Event.observe(this.track, "mousedown", this.eventMouseDown);
    Event.observe(document, "mouseup", this.eventMouseUp);
    Event.observe(document, "mousemove", this.eventMouseMove);
    
    this.initialized = true;
  },
  dispose: function() {
    var slider = this;    
    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
    Event.stopObserving(document, "mouseup", this.eventMouseUp);
    Event.stopObserving(document, "mousemove", this.eventMouseMove);
    this.handles.each( function(h) {
      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
    });
  },
  setDisabled: function(){
    this.disabled = true;
  },
  setEnabled: function(){
    this.disabled = false;
  },  
  getNearestValue: function(value){
    if(this.allowedValues){
      if(value >= this.allowedValues.max()) return(this.allowedValues.max());
      if(value <= this.allowedValues.min()) return(this.allowedValues.min());
      
      var offset = Math.abs(this.allowedValues[0] - value);
      var newValue = this.allowedValues[0];
      this.allowedValues.each( function(v) {
        var currentOffset = Math.abs(v - value);
        if(currentOffset <= offset){
          newValue = v;
          offset = currentOffset;
        } 
      });
      return newValue;
    }
    if(value > this.range.end) return this.range.end;
    if(value < this.range.start) return this.range.start;
    return value;
  },
  setValue: function(sliderValue, handleIdx){
    if(!this.active) {
      this.activeHandle    = this.handles[handleIdx];
      this.activeHandleIdx = handleIdx;
      this.updateStyles();
    }
    handleIdx = handleIdx || this.activeHandleIdx || 0;
    if(this.initialized && this.restricted) {
      if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
        sliderValue = this.values[handleIdx-1];
      if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
        sliderValue = this.values[handleIdx+1];
    }
    sliderValue = this.getNearestValue(sliderValue);
    this.values[handleIdx] = sliderValue;
    this.value = this.values[0]; // assure backwards compat
    
    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] = 
      this.translateToPx(sliderValue, handleIdx);
    
    this.drawSpans();
    if(!this.dragging || !this.event) this.updateFinished();
  },
  setValueBy: function(delta, handleIdx) {
    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta, 
      handleIdx || this.activeHandleIdx || 0);
  },
  translateToPx: function(value, handleIdx) {
    // Hack GR to allow two sliders show the same value without overlapping
    if (this.handles.length == 2 && handleIdx != null) {
      var offset = (value - this.range.start) / (this.range.end-this.range.start) * (this.trackLength - 2 * this.handleLength);
	  if (handleIdx > 0) {
	    return Math.round(offset + this.handleLength) + 'px';
	  }
	  else {
	    return Math.round(offset) + 'px';
	  }
	}
    else {
      return Math.round(
      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) * 
      (value - this.range.start)) + "px";
    }
  },
  translateToValue: function(offset) {
    // Hack GR to allow two sliders show the same value without overlapping
    var percent = 0;
    if (this.handles.length == 2) {
	  if (this.activeHandleIdx > 0) {
	  	percent = (offset - 1.5 * this.handleLength)/(this.trackLength - 2 * this.handleLength)
	  }
	  else {
    	percent = (offset + 0.5 * this.handleLength)/(this.trackLength - 2 * this.handleLength)
	  }
	  var value = percent * (this.range.end-this.range.start) + this.range.start; 
	  if (value < this.range.start) {
	    value = this.range.start;
	  }
	  else if (value > this.range.end) {
	    value = this.range.end;
	  }
	  return value;
	}
    else {
      return ((offset/(this.trackLength-this.handleLength) * 
        (this.range.end-this.range.start)) + this.range.start);
    }
  },
  getRange: function(range) {
    var v = this.values.sortBy(Prototype.K); 
    range = range || 0;
    return $R(v[range],v[range+1]);
  },
  minimumOffset: function(){
    return(this.isVertical() ? this.alignY : this.alignX);
  },
  maximumOffset: function(){
    return(this.isVertical() ?
      this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
  },  
  isVertical:  function(){
    return (this.axis == 'vertical');
  },
  drawSpans: function() {
    var slider = this;
    if(this.spans)
      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
    if(this.options.startSpan)
      this.setSpan(this.options.startSpan,
        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
    if(this.options.endSpan)
      this.setSpan(this.options.endSpan, 
        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
  },
  setSpan: function(span, range) {
    if(this.isVertical()) {
      span.style.top = this.translateToPx(range.start, null);
      span.style.height = this.translateToPx(range.end - range.start + this.range.start, null);
    } else {
      span.style.left = this.translateToPx(range.start, null);
      span.style.width = this.translateToPx(range.end - range.start + this.range.start, null);
    }
  },
  updateStyles: function() {
    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
    Element.addClassName(this.activeHandle, 'selected');
  },
  startDrag: function(event) {
    if(Event.isLeftClick(event)) {
      if(!this.disabled){
        this.active = true;
        
        var handle = Event.element(event);
        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
        if(handle==this.track) {
          var offsets  = Position.cumulativeOffset(this.track); 
          this.event = event;
          this.setValue(this.translateToValue( 
           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
          ));
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        } else {
          // find the handle (prevents issues with Safari)
          while((this.handles.indexOf(handle) == -1) && handle.parentNode) 
            handle = handle.parentNode;
          this.activeHandle    = handle;
          this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
          this.updateStyles();
          var offsets  = Position.cumulativeOffset(this.activeHandle);
          this.offsetX = (pointer[0] - offsets[0]);
          this.offsetY = (pointer[1] - offsets[1]);
        }
      }
      Event.stop(event);
    }
  },
  update: function(event) {
   if(this.active) {
      if(!this.dragging) this.dragging = true;
      this.draw(event);
      // fix AppleWebKit rendering
      if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
      Event.stop(event);
   }
  },
  draw: function(event) {
    var pointer = [Event.pointerX(event), Event.pointerY(event)];
    var offsets = Position.cumulativeOffset(this.track);
    pointer[0] -= this.offsetX + offsets[0];
    pointer[1] -= this.offsetY + offsets[1];
    this.event = event;
    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
    if(this.initialized && this.options.onSlide)
      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
  },
  endDrag: function(event) {
    if(this.active && this.dragging) {
      this.finishDrag(event, true);
      Event.stop(event);
    }
    this.active = false;
    this.dragging = false;
  },  
  finishDrag: function(event, success) {
    this.active = false;
    this.dragging = false;
    this.updateFinished();
  },
  updateFinished: function() {
    if(this.initialized && this.options.onChange) 
      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
    this.event = null;
  }
}
function toggleSubSortPanel() {
	if ($('searchPanel').style.display == "none") {
		$('searchPanel').style.display = "block";
		$('subsortBtn').value = "Hide hotel preferences";
		if (typeof(searchSliders)=="undefined") searchSliders = new SearchSliders();
	} else {
		$('searchPanel').style.display = "none";
		$('subsortBtn').value="Show hotel preferences";
	}
}
function doFilter() {
	window.matrix.doFilter();
	if (isFilterOn()) {
		$('showAll').style.visibility = "";
	} else {
		$('showAll').style.visibility = "hidden";
	}
}

function isFilterOn() {
	if ($("restaurant").checked || $("kitchenette").checked || $("pool").checked || $("fitnessCentre").checked || $("broadbandInternet").checked ||$("satelliteCableTV").checked) {
		return true;
	}
	if ($('minCategory').innerHTML!="1" || $('maxCategory').innerHTML!="5") {
		return true;
	}
	for (i=0; i<window.matrix.data.accomList.length; i++) {
    	if ($("mtx_fltr_accom_"+i).checked) return true;
    }
}

function resetFilter() {
	if (typeof(searchSliders) != "undefined") {
		searchSliders.setSearchEnabled(false);
		searchSliders.catSlider.setValue(5, 1);
	    searchSliders.catSlider.setValue(1, 0);
	    $('minCategory').update(1);
	    $('maxCategory').update(5);
	    adjustStars(1,5);
	    searchSliders.setSearchEnabled(true);
	}
	$("restaurant").checked = false;
    $("kitchenette").checked = false;
    $("pool").checked = false;
    $("fitnessCentre").checked = false;
    $("broadbandInternet").checked = false;
    $("satelliteCableTV").checked = false;
    for (i=0; i<window.matrix.data.accomList.length; i++) {
    	$("mtx_fltr_accom_"+i).checked = false;
    }
   	window.matrix.doFilter();
   	$('showAll').style.visibility = "hidden";
}

function replaceImage(image, imagePath) {
	if (!imagePath) {
		image.src = "http://rates.asiawebdirect.com/asahi/images/space.gif";
		} else {
		image.src = imagePath;
	}
} 

function MatrixFilter() {
	this.showFilterPanel = false;
	this.minCat = 1;
	this.maxCat = 5;
	this.restaurant = false;
    this.kitchenette = false;
    this.pool = false;
    this.fitnessCentre = false;
    this.broadbandInternet = false;
    this.satelliteCableTV = false;
    this.accommodation = new Array();
    
    // sort field
    this.sort_field = "Popular";
    this.sort_direction = 1;
}
    
MatrixFilter.prototype.KeepCurrent = function() {
	if (!$('div_mtx_filter')) return;
	this.minCat = $('minCategory').innerHTML;
	this.maxCat = $('maxCategory').innerHTML;
	this.restaurant = $("restaurant").checked
    this.kitchenette = $("kitchenette").checked;
    this.pool = $("pool").checked;
    this.fitnessCentre = $("fitnessCentre").checked;
    this.broadbandInternet = $("broadbandInternet").checked;
    this.satelliteCableTV = $("satelliteCableTV").checked;
    this.accommodation = new Array();
    for (i=0; i<window.matrix.accomList.length; i++) {
    	if ($("mtx_fltr_accom_"+i).checked) this.accommodation.push(window.matrix.accomList[i]);
    }
    this.showFilterPanel = true;
    if ($('searchPanel').style.display == "none") this.showFilterPanel = false
    this.sort_field = window.matrix.curSortBy;
    this.sort_direction = window.matrix.sortDirection[window.matrix.curSortBy];
}
    
MatrixFilter.prototype.Restore = function() {
	if (!$('div_mtx_filter')) return;
	$('searchPanel').style.display = "block";
	$('subsortBtn').value = "Hide hotel preferences";
	$("restaurant").checked = this.restaurant; 
	$("kitchenette").checked = this.kitchenette;
	$("pool").checked = this.pool;
	$("fitnessCentre").checked = this.fitnessCentre;
	$("broadbandInternet").checked = this.broadbandInternet;
	$("satelliteCableTV").checked = this.satelliteCableTV;
    $('minCategory').update(this.minCat);
    $('maxCategory').update(this.maxCat);
    adjustStars(this.minCat,this.maxCat);
    
    if (window.matrix.data) {
    	if (window.matrix.data.accomList) {
	    	for (var i=0; i<window.matrix.data.accomList.length; i++) {
	        	for (var j = 0; j < this.accommodation.length; j++) {
	        		if (window.matrix.data.accomList[i] == this.accommodation[j]) $("mtx_fltr_accom_"+i).checked = true;
	        	}
		    }
    	}
    }

	if (isFilterOn()) {
		$('showAll').style.visibility = "";
		//window.matrix.doFilter();
	} else {
		$('showAll').style.visibility = "hidden";
	}
	
    $('mtx_loading').style.display = "none";
    searchSliders = null;
	searchSliders = new SearchSliders();
	searchSliders.setSearchEnabled(false);
	searchSliders.setValue(this.minCat, this.maxCat);
    searchSliders.setSearchEnabled(true);
    if (this.showFilterPanel) {
    	$('subsortBtn').value = "Hide hotel preferences";
    } else {
    	$('searchPanel').style.display = "none";
    	$('subsortBtn').value = "Show hotel preferences";
    }
    
    // sorting
    if (this.sort_field!="Popular") {
    	//if (this.sort_field == window.matrix.curSortBy) this.sort_direction *= -1;
    	window.matrix.sortDirection[this.sort_field] = this.sort_direction;
    	//doSortBy(this.sort_field);
    	var arrowField = $('arrow' + this.sort_field);
    	if (matrix.sortDirection[this.sort_field] != 1) {
    		arrowField.update('&darr;');
    	} else {
    		arrowField.update('&uarr;');
    	}
    	arrowField.style.visibility = "visible";
    }
}

MatrixFilter.prototype.Reset = function() {
	if (typeof(searchSliders) != "undefined") {
		searchSliders.setSearchEnabled(false);
		searchSliders.setValue(1, 5);
	    $('minCategory').update(1);
	    $('maxCategory').update(5);
	    adjustStars(1,5);
	    searchSliders.setSearchEnabled(true);
	}
	$("restaurant").checked = false;
    $("kitchenette").checked = false;
    $("pool").checked = false;
    $("fitnessCentre").checked = false;
    $("broadbandInternet").checked = false;
    $("satelliteCableTV").checked = false;
    for (i=0; i<window.matrix.data.accomList.length; i++) {
    	$("mtx_fltr_accom_"+i).checked = false;
    }
   	$('showAll').style.visibility = "hidden";
    window.matrix.doFilter();
}

function hilightRow(oRow) {
	oRow.className = oRow.className + ' hover';
}

function unHilightRow(oRow) {
	oRow.className = oRow.className.replace(' hover', '');
	oRow.className = oRow.className.replace(' hover', '');
	oRow.className = oRow.className.replace(' hover', '');
}

function getCookie(name) {
	var dcookie = document.cookie;
	var cname = name + "=";
	var clen = dcookie.length;
	var cbegin = 0;
	while (cbegin < clen) {
		var vbegin = cbegin + cname.length;
		if (dcookie.substring(cbegin, vbegin) == cname) {
			var vend = dcookie.indexOf(";", vbegin);
			if (vend == -1)
				vend = clen;
			return unescape(dcookie.substring(vbegin, vend));
		}
		cbegin = dcookie.indexOf(" ", cbegin) + 1;
		if (cbegin == 0)
			break;
	}
	return null;
}

function setCookie(name, value, expires) {
	if (!expires)
		expires = new Date();
	document.cookie = name + "=" + escape(value) + "; expires="
			+ expires.toGMTString() + "; path=/";
}

function delCookie(name) {
	var expireNow = new Date();
	document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT"
			+ "; path=/";
}

function preLoadImages() {
	var preFix = 'http://rates.asiawebdirect.com/asahi/images/';
	var imagesArray = new Array('book.png', 'book-over.png', 'bg_selecteddatetip.gif', 'next-7-yellow.png', 'previous-7-yellow.png', 'request.png', 'request-over.png');
	for (var i=0; i<imagesArray.length; i++) {
		var img = new Image();
		img.src = preFix + imagesArray[i];
	}
}

function addComma(nStr) {
	  nStr += '';
	  x = nStr.split('.');
	  x1 = x[0];
	  x2 = x.length > 1 ? '.' + x[1] : '';
	  var rgx = /(\d+)(\d{3})/;
	  while (rgx.test(x1)) {
	    x1 = x1.replace(rgx, '$1' + ',' + '$2');
	  }
	  return x1 + x2;
}


function lookupMonth(number) {
	var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	return months[number];
}

;
//Simple JavaScript Templating
//John Resig - http://ejohn.org/ - MIT Licensed
(function(){	
  var cache = {};
 
  this.tmpl = function tmpl(str, data){
      str = document.getElementById(str).value;
      str = '/*st*/\'' + str + '\'/*en*/';	  
      var functionBody = "var p=[],print=function(){p.push.apply(p,arguments);};" +
      
      // Introduce the data as local variables using with(){}
      "with(obj){p.push(" +
        
      // Convert the template into pure JavaScript

      str
        .replace(/[\r\t\n]/g, " ")
        .split("<%").join("\t")
        .replace(/\t=(.*?)%>/g, "'/*en*/,$1,/*st*/'")
        .split("\t").join("'/*en*/);")
        .split("%>").join("p.push(/*st*/'")
        .replace(/\/\*st\*\/'(.*?)'\/\*en\*\//g, function (match, d) {
        	return "'" + d.replace(/'/g, '\\\'') + "'";
        })
        
      + ");}return p.join('');";
      
      var fn = new Function("obj",functionBody);

      return data ? fn( data ) : fn;
  };
})();

(function(){	
	  var cache = {};
	 
	  this.tmpl_str = function tmpl_str(str, data){
		  
	      str = '/*st*/\'' + str + '\'/*en*/';	  
	      var functionBody = "var p=[],print=function(){p.push.apply(p,arguments);};" +
	      
	      // Introduce the data as local variables using with(){}
	      "with(obj){p.push(" +
	        
	      // Convert the template into pure JavaScript

	      str
	        .replace(/[\r\t\n]/g, " ")
	        .split("<%").join("\t")
	        .replace(/\t=(.*?)%>/g, "'/*en*/,$1,/*st*/'")
	        .split("\t").join("'/*en*/);")
	        .split("%>").join("p.push(/*st*/'")
	        .replace(/\/\*st\*\/'(.*?)'\/\*en\*\//g, function (match, d) {
	        	return "'" + d.replace(/'/g, '\\\'') + "'";
	        })
	        
	      + ");}return p.join('');";
	      
	      var fn = new Function("obj",functionBody);

	      return data ? fn( data ) : fn;
	  };
	})();
;
function Currency(isoCode, symbol, name, shortName, relativeRate) {
    this.isoCode      = isoCode;
    this.symbol       = symbol;
    this.name         = name;
    this.shortName    = shortName;
    this.relativeRate = relativeRate;
};

function setCurrency(select) {
	if (!window.allCurrencies) {
		extractCurrencies(select);
    }
	var newCurrency = select.value.split('|')[0];
	currencyConverter.convert(window.allCurrencies, newCurrency);
	document.fire("matrix:currencyChanged", newCurrency);
}

function extractCurrencies(select) {
	window.allCurrencies = {};
	for (var i = 0; i < select.options.length; i++) {
		var values = select.options[i].value.split('|');
		var code = values[0];
		var rate = values[1];
		var symbol = values[2];
		var name = select.options[i].textContent;
		
		window.allCurrencies[code] = new Currency(code, symbol, name, name, rate);
	}	
}

/**
 * A 'class' representing generic currency conversion functionality for deal matrices.
 *
 * Follows a convention based approach for identifying cells that require conversion.
 * Rather than a single synchronous conversion, the approach taken is to split the work required up into
 * logical sections (known henceforth as 'deal blocks') and returning control to the browser momentarily between
 * deal block conversions.
 *
 * The convention is as follows:
 *
 *           _________________________________________________________
 *       ___|______________________________________________________   |
 *   ___|_______________________________________________________   |  |
 *  |                                                           |  |  |
 *  | div: id=currencyGroup                                     |  |  |
 *  |      name=currencyGroup                                   |  |  |
 *  |      isoCode=CODE                                         |  |  |
 *  |      index=indexNumber                                    |  |  |
 *  |                                                           |  |  |
 *  |    ____________________________                           |  |  |
 *  |   | div: name=dealBlock        |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   |____________________________|                          |  |  |
 *  |    ____________________________                           |  |  |
 *  |   | div: name=dealBlock        |                          |  |  |
 *  |   |                            |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   | span, name=drc             |                          |  |  |
 *  |   |                            |                          |  |__|
 *  |   |____________________________|                          |__|
 *  |___________________________________________________________|
 *
 *
 * Blocks that relate to the same currency are encapsulated in DIV elements with an ID of 'currencyGroup'
 * and a name of 'currencyGroup'. Cells within a 'currencyGroup' are further subdivided into logical units known
 * as 'deal blocks'. These are denoted by DIV elements with a name of 'dealBlock'. Each 'deal block' is processed
 * synchronously. After processing a deal block, the next block is scheduled for processing at a later stage
 * (denoted by the TIME_BETWEEN_DEAL_BLOCKS parameter defined below). This approach is taken so that control can be
 * returned to the browser between conversions, allowing continual response to the user for large pages.
 *
 * Each rate cell within a deal block is expected to contain semi-structured data:
 *
 *   ---> <span name="drc">[currencySymbol]RATE</span>
 *  (the currencySymbol is optional, but cannot contain digits).
 *
 * To provide further flexibility, users of this class can schedule callbacks for various phases in the lifecycle:
 *
 * before any conversions take place: addPreConversionCallback(function() { ... } );
 *
 * before each currency group: addPreCurrencyGroupCallback(function(...) { ... } );
 *
 * after each currency group: addPostCurrencyGroupCallback(function(...) { ... } );
 *
 * before performing cell conversions on each deal block: addPreDealBlockCallback(function(...) { ... } );
 *
 * after performing cell conversions on each deal block: addPostDealBlockCallback(function(...) { ... } );
 *
 * after entire conversion: addPostConversionCallback(function() { ... } );
 *
 * These callbacks can be useful in performing various random tasks (i.e. updating messages pertaining to currency
 * codes).
 *
 */
function CurrencyConverter() {
	this.preConversionCallbacks     = new Array();
    this.postConversionCallbacks    = new Array();
    this.preCurrencyGroupCallbacks  = new Array();
    this.postCurrencyGroupCallbacks = new Array();
    this.preDealBlockCallbacks      = new Array();
    this.postDealBlockCallbacks     = new Array();
    
    this.currentTimeout = null;
    this.convertIsoCode = false;
};

// DEFINE 'CLASS' level constants.
CurrencyConverter.prototype.DEAL_RATE_REGEX           = /name="?drc?"?>([\d,]+)<[^>]+>([\d\.]+)(?::)?(\w{3})?<\/\S+>\s*([^<]*)?<\/[Ss]/g;
CurrencyConverter.prototype.DEAL_RATE_SYMBOL_REGEX    = /name="?ccs"?>(\S*)<\/span/ig;
CurrencyConverter.prototype.TIME_BETWEEN_DEAL_BLOCKS  = 20;
CurrencyConverter.prototype.CONVERSION_TYPE_SINGLE    = 1;
CurrencyConverter.prototype.CONVERSION_TYPE_MULTI     = 2;
CurrencyConverter.prototype.CURRENCY_CODE_ATTR        = 'isoCode';
CurrencyConverter.prototype.CURRENCY_GROUP_NAME       = 'currencyGroup';
CurrencyConverter.prototype.DEAL_BLOCK_NAME           = 'dealBlock';
CurrencyConverter.prototype.CURRENCY_COOKIE           = 'ccCodePersist';
CurrencyConverter.prototype.COOKIE_EXPIRY             = 90;

/**
 * Specify that the given function should be called once before any conversions take place.
 *    ---> callBack(currencyConverter);
 *    
 * currencyConverter --> this CurrencyConverter instance
 * 
 */
CurrencyConverter.prototype.addPreConversionCallback = function(cb) {
	this.preConversionCallbacks.push(cb);
};

/**
 * Specify that the given function should be called when all conversions are complete.
 */
CurrencyConverter.prototype.addPostConversionCallback = function(cb) {
    this.postConversionCallbacks.push(cb);
};

/**
 * Specify that the given function should be called before applying conversions to a currencyGroup DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'currencyGroup' element
 * fromCurrency   --> a Currency object representing the default currency for the group
 * toCurrency     --> a Currency object representing the indicative currency for the group
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 */
CurrencyConverter.prototype.addPreCurrencyGroupCallback = function(cb) {
    this.preCurrencyGroupCallbacks.push(cb);
};

/**
 * Specify that the given function should be called after applying conversions to a currencyGroup DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'currencyGroup' element
 * fromCurrency   --> a Currency object representing the default currency for the group
 * toCurrency     --> a Currency object representing the indicative currency for the group
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 */
CurrencyConverter.prototype.addPostCurrencyGroupCallback = function(cb) {
    this.preCurrencyGroupCallbacks.push(cb);
};


/**
 * Specify that the given function should be called before applying conversions to a deal block DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'dealBlock' element
 * fromCurrency   --> a Currency object representing the default currency for the current currency group
 * toCurrency     --> a Currency object representing the indicative currency
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 */
CurrencyConverter.prototype.addPreDealBlockCallback = function(cb) {
    this.preDealBlockCallbacks.push(cb);
};

/**
 * Specify that the given function should be called after applying conversions to a deal block DIV.
 *    --> callBack(divElement, fromCurrency, toCurrency, conversionType);
 *
 * divElement     --> the 'dealBlock' element
 * fromCurrency   --> a Currency object representing the default currency for the current currency group
 * toCurrency     --> a Currency object representing the indicative currency
 * conversionType --> The convserion type
 *                ------> CONVERSION_TYPE_SINGLE (only displaying one currency, i.e. turning off display, or default == indicative)
 *                ------> CONVERSION_TYPE_MULTI  (displaying two currencies in the cell, implies default != indicative)
 */
CurrencyConverter.prototype.addPostDealBlockCallback = function(cb) {
    this.postDealBlockCallbacks.push(cb);
};

/**
 * Perform a currency conversion:
 *
 * currencies is a mapping of isoCode --> Currency objects.
 *
 * indicativeCurrency is the ISO code of the currency to convert to.
 */
CurrencyConverter.prototype.convert = function(currencies, indicativeCurrency) {
	this.setCookie(CurrencyConverter.prototype.CURRENCY_COOKIE, indicativeCurrency, CurrencyConverter.prototype.COOKIE_EXPIRY);
    if (this.currentTimeout != null) {
        window.clearTimeout(this.currentTimeout);
    }
    var scopedThis          = this;
    this.turningOff         = false;
    this.longRatePresent    = false;
    this.currencies         = new CloneCurrencyList(currencies);
    this.indicativeCurrency = this.currencies[new String(indicativeCurrency)];
    this.currencyGroups     = this.findCurrencyGroups();
    this.currencyGroupNum   = 0;
    this.fireCallbacks(this, this.preConversionCallbacks);
    this.currentTimeout = window.setTimeout(function () { scopedThis.convertCurrencyGroup(); }, 1);
};

/**
 * Turn off any conversions that are currently applied.
 */
CurrencyConverter.prototype.turnOff = function() {
    if (this.currentTimeout != null) {
        window.clearTimeout(this.currentTimeout);
    }
    var scopedThis          = this;
    this.longRatePresent    = false;
    this.turningOff         = true;
    this.indicativeCurrency = null;
    this.currencyGroups     = this.findCurrencyGroups();
    this.currencyGroupNum   = 0;
    if(this.currencies) {
        /* Dont need to turn off in this case as nothing was turned on */
        this.currentTimeout = window.setTimeout(function () { scopedThis.convertCurrencyGroup(); }, 1);
    }
};

/**
 * Internal function for converting a currency group.
 */
CurrencyConverter.prototype.convertCurrencyGroup = function() {
    if (this.currencyGroupNum < this.currencyGroups.length) {
        var scopedThis       = this;
        var currencyGroup    = this.currencyGroups.item(this.currencyGroupNum++);
        this.currencyGroup   = currencyGroup;
        this.defaultCurrency = this.currencies[currencyGroup.getAttribute(this.CURRENCY_CODE_ATTR)];
        this.conversionType  = this.determineConversionType();
        this.conversionFunc  = this.determineConversionFunc();
        this.dealBlocks      = this.findDealBlocks(currencyGroup);
        this.dealBlockNum    = 0;
        this.fireCallbacks(currencyGroup, this.preCurrencyGroupCallbacks);
        this.currentTimeout = window.setTimeout(function() { scopedThis.convertDealBlock(); }, 1);
    } else {
        for (var callbackIndex = 0; callbackIndex < this.postConversionCallbacks.length; callbackIndex++) {
            this.postConversionCallbacks[callbackIndex]();
        }
    }
};

/**
 * Internal function for converting a deal block.
 */
CurrencyConverter.prototype.convertDealBlock = function() {
    var scopedThis = this;
    if (this.dealBlockNum < this.dealBlocks.length) {
        // change this deal block and schedule the next one
        var dealBlock = this.dealBlocks[this.dealBlockNum++];
        this.fireCallbacks(dealBlock, this.preDealBlockCallbacks);
        dealBlock.innerHTML = dealBlock.innerHTML.replace(this.DEAL_RATE_REGEX, function (match, displayRate, origRate, origIsoCode, isoCode) {
            return scopedThis.conversionFunc(match, displayRate, origRate, origIsoCode, isoCode);
        });
        
        dealBlock.innerHTML = dealBlock.innerHTML.replace(this.DEAL_RATE_SYMBOL_REGEX, function (match, symbol) {
            return 'name="ccs">' + scopedThis.indicativeCurrency.symbol + '</span';
        });
        
        
        this.fireCallbacks(dealBlock, this.postDealBlockCallbacks);

        this.currentTimeout = window.setTimeout(function() { scopedThis.convertDealBlock(); }, this.TIME_BETWEEN_DEAL_BLOCKS);
    } else {
        // next currency block
        this.fireCallbacks(this.currencyGroup, this.postCurrencyGroupCallbacks);
        this.currentTimeout = window.setTimeout(function() { scopedThis.convertCurrencyGroup(); }, 1);
    }
};

/**
 * Rate cell conversion function for displaying a single rate.
 */
CurrencyConverter.prototype.showOneCurrency = function(match, displayRate, origRate, origIsoCode, isoCode) {
	if (origIsoCode==undefined || origIsoCode=='') {
		var amountInt = convertCurrency(origRate, this.defaultCurrency.relativeRate, this.indicativeCurrency.relativeRate);
	} else {
		var amountInt = convertCurrency(origRate, this.currencies[origIsoCode].relativeRate, this.indicativeCurrency.relativeRate);
	}
	
	var txt = 'name="drc">' + addCommas(amountInt) + '<span class="or">' + origRate;
	if (origIsoCode!=undefined && origIsoCode!='') {
		txt += ':' + origIsoCode;
	}

	if (isoCode == undefined || isoCode == '') {
		txt += '</span></s';
	} else {
		txt += '</span> ' + this.indicativeCurrency.isoCode + '</s';
	}
	return txt;
};

/**
 * Rate cell conversion function for displaying the indicative rate as well as the default rate
 */
CurrencyConverter.prototype.showBothCurrencies = function(match, symbol, rate) {
    var indicativeSymbol = symbol == '' ? '' : this.indicativeCurrency.shortName;
    var amountInt = convertCurrency(rate, this.defaultCurrency.relativeRate, this.indicativeCurrency.relativeRate);
    if (rate.length + symbol.length >= 5 || amountInt.length + indicativeSymbol.length >=5) {
        this.longRatePresent = true;
    }
    var klass = "cc";
    if (amountInt.length >= 7) {
      klass = "ccs1";
    }
    if (amountInt.length >= 10) {
      klass = "ccs2";
    }
    return 'name="drc">' + symbol + rate + '<br/><font class="' + klass + '">' + indicativeSymbol + amountInt + '</font></s';
};

/**
 * Internal function for determining what type of conversion is being performed currently:
 * - single, or
 * - multi
 */
CurrencyConverter.prototype.determineConversionType = function() {
    //if (this.turningOff || (this.indicativeCurrency.isoCode == this.defaultCurrency.isoCode)) {
	return this.CONVERSION_TYPE_SINGLE;
    //}
    //return this.CONVERSION_TYPE_MULTI;
};

/**
 * Internal function for determining which function to use for converting deal cells.
 */
CurrencyConverter.prototype.determineConversionFunc = function() {
    if (this.conversionType == this.CONVERSION_TYPE_SINGLE) {
        return this.showOneCurrency;
    } else {
    	return this.showBothCurrencies;
    }
};

/**
 * Internal function for finding all currency groups.
 */
CurrencyConverter.prototype.findCurrencyGroups = function() {
    return document.getElementsByName(this.CURRENCY_GROUP_NAME);
};

/**
 * Internal function for finding all deal blocks inside a currency group.
 */
CurrencyConverter.prototype.findDealBlocks = function(currencyGroup) {
    return document.getElementsByName("dealBlock");
    /*var divs = currencyGroup.getElementsByTagName("table");
    var dealBlocks = new Array();
    for (var divNum = 0; divNum < divs.length; divNum++) {
        var div = divs.item(divNum);
        if (div.getAttribute('name') == this.DEAL_BLOCK_NAME) {
            dealBlocks.push(div);
        }
    }
    return dealBlocks;*/
};

/**
 * Internal function for calling all callbacks for a div based on the given callback array.
 */
CurrencyConverter.prototype.fireCallbacks = function(divElement, callbackArray) {
    for (var callbackIndex = 0; callbackIndex < callbackArray.length; callbackIndex++) {
        callbackArray[callbackIndex](divElement, this.defaultCurrency, this.indicativeCurrency, this.conversionType);
    }
};

/**
 * Get the value for the cookie with the given name, or null if it doesn't exist
 */
CurrencyConverter.prototype.getCookie = function(name) {
	var dc     = document.cookie;
	var prefix = name + "=";
	var begin  = dc.indexOf("; " + prefix);
	if (begin == -1) {
        begin = dc.indexOf(prefix);
	    if (begin != 0) {
	    	return null;
	    }
	} else {
	    begin += 2;
	}
	var end = document.cookie.indexOf(";", begin);
	if (end == -1) {
        end = dc.length;
	}
	return unescape(dc.substring(begin + prefix.length, end));
}

/**
 * Set a session cookie with the given name to the given value
 */
CurrencyConverter.prototype.setCookie = function(cookieName, cookieValue, expireDays) {
	var expiry = new Date(new Date().getTime() + (expireDays * 1000 * 60 * 60 * 24)).toGMTString()
	document.cookie = cookieName + "=" + cookieValue + ";" + "expires=" + expiry + ";path=" + "/" ;
}


/**
 * Switch the css classes for all 'elementName' elements under 'rootElement' that have a
 * className of 'fromClass' to 'toClass'.
 */
function switchClasses(rootElement, elementName, fromClass, toClass) {
    var childNodes = rootElement.getElementsByTagName(elementName);
    for (var childIndex = 0; childIndex < childNodes.length; childIndex++) {
        var childNode = childNodes.item(childIndex);
        if (childNode.className == fromClass) {
            childNode.className = toClass;
        }
    }
}

function CloneCurrencyList(source) {
    for (var field in source) {
        var c = source[field];
        this[new String(field)] = new Currency(
                new String(c.isoCode), new String(c.symbol), new String(c.name), new String(c.shortName), parseFloat(c.relativeRate));
    }
}

function convertCurrency(amount, fromRate, toRate) {
    if (amount == "n/a") { amount; }
    return new String(Math.ceil((parseInt(amount.replace(',', '')) / toRate) * fromRate));
}

function addCommas(nStr) {
    nStr += '';
    var x = nStr.split('.');
    var x1 = x[0];
    var x2 = x.length > 1 ? '.' + x[1] : '';
    var rgx = /(\d+)(\d{3})/;
    while (rgx.test(x1)) {
        x1 = x1.replace(rgx, '$1' + ',' + '$2');
    }
    return x1 + x2;
}

function findCurrencySelect() {
	var dropDowns = document.getElementsByTagName("SELECT");
    for (var ddIdx = 0; ddIdx < dropDowns.length; ddIdx++) {
    	var dd = dropDowns.item(ddIdx);
    	if (dd.getAttribute('name') == 'ccSelect') return dd;
    }
    return null;
}

window.currencyConverter = new CurrencyConverter();

// update currency drop-downs
window.currencyConverter.addPostDealBlockCallback(function (dealBlock, from, to) {
	var targetCode = to.isoCode;
    var dropDowns = dealBlock.getElementsByTagName("SELECT");
    for (var ddIdx = 0; ddIdx < dropDowns.length; ddIdx++) {
    	var dd = dropDowns.item(ddIdx);
    	if (dd.getAttribute('name') != 'ccSelect') continue;
    	for (var i = 0; i < dd.options.length; i++) {
    		if (dd.options[i].value.split('|')[0] == targetCode) {
    			dd.selectedIndex = i;
    			break;
    		}
    	}
    }
});

window.currencyConverter.addPreConversionCallback(function (cc) {
	//matrix.hideRatePlanSelection();
});

window.currencyConverter.addPostConversionCallback(function () {
	//matrix.unhideRatePlanSelection();
	_f_set_mtx_s_ChangeMY();
	if ($('mtx_filterOnRequest')) $('mtx_filterOnRequest').checked = !matrix.showOnRequest;
	if (matrix.reservation) matrix.reservation.setDateSelected();
	
	if (mtxHelper) {
		mtxHelper.checkCheckboxes();
		mtxHelper.removeHoverMsg();
	}
	
	if (matrix.matrixType=='Destination' && matrix.portal != 1) {
		for (var i=0; i<shortlistedHotels.length; i++) {
			checkShortlist(shortlistedHotels[i]);
		}
	}
});

document.observe("matrix:loaded", function(e) {
	var m = e.memo;
	var isoCode = window.currencyConverter.getCookie(CurrencyConverter.prototype.CURRENCY_COOKIE);
	
	if (!window.allCurrencies) {
		var select = findCurrencySelect();
		if (!select) return; // no currency converter drop-down, we can't perform the conversion
		extractCurrencies(select);
	}
	if (isoCode && isoCode!=window.matrix.data.currency) {
		window.currencyConverter.convert(window.allCurrencies, isoCode);
	}
	window.currencyConverter.defaultCurrency = window.allCurrencies[window.matrix.data.currency];
});
;
