	function calHandler(parentObj, baseUrl, totalMonths, park, tableSupport) {
	// set parent class
		parentObj.className = 'cal';
	// gather scope
		var root         = this;
	// for mapping months/numbers
		root.monthNames   = ['JANUARY', 'FEBRUARY', 'MARCH', 'APRIL', 'MAY', 'JUNE', 'JULY', 'AUGUST', 'SEPTEMBER', 'OCTOBER', 'NOVEMBER', 'DECEMBER'];
		root.dailyNames   = ['S', 'M', 'T', 'W', 'T', 'F', 'S'];
	// initial setup variables
		root.parent      = parentObj;                                // html element that will host the calendar.
		root.totalMonths = totalMonths-1;                            // total number of months this calendar will support.
		root.thisPark    = park;                                     // the name of the park we are working with .. necessary?
		root.dateTable   = (tableSupport) ? tableSupport : false;    // dynTable for onclick events, or false if no dynTable support.
	// placeholder varaibles
		root.firstMonth  = false;                                    // this will be dictated by the server on the first response.
		root.firstYear   = false;                                    // this will be dictated by the server on the first response.
		root.killNode    = false;                                    // store replacement node for stepMonth (DOM ref cannot be carried as argument asynchronously).
		root.pallette    = false;                                    // this will be a placeholder for the mouseover date pallette, dom ref if exists, else boolean false.
	// this makes a request for a month worth of data,
	// given the current state of the menu
		root.getMonth = function(month, year) {
			if (typeof(month) != 'boolean') { month++; }
			var request = (typeof(month) == 'boolean') ? baseUrl + '?parkname=' + escape(root.thisPark) : baseUrl + '?month=' + month + '&year=' + year + '&parkname=' + escape(root.thisPark);
			ajax.get(request, root.addMonth)
		}
	// utility, simply creates a month object from
	// the data array and returns it for placement
		root.makeMonth = function(data) {
		// get year info
			var yearName   = data[0];
		// get month info
			var monthName  = root.monthNames[parseInt(data[1])];
			var monthIndex = data[1];
		// get starting day
			var startDay   = data[2];
		//  get days data
			var dailyData  = data[3];
		// get next/last
			var firstMo    = (data[4]) ? 'true' : 'false';
			var lastMo     = (data[5]) ? 'true' : 'false';
		// define holding div
			var monthParent = document.createElement('div');
				monthParent.className = 'month';
				monthParent.setAttribute('lastmo',   lastMo);
				monthParent.setAttribute('firstmo',  firstMo);
				monthParent.setAttribute('monthNum', data[1]);
				monthParent.setAttribute('yearName', yearName);
		// define header element
			var monthHdr    = document.createElement('h4');
				monthHdr    = monthParent.appendChild(monthHdr);
				monthHdr.innerHTML = monthName + ' ' + yearName;
		//  define table and header rows
			var monthObj   = document.createElement('table');
			var monthBod   = document.createElement('tbody');
				monthBod   = monthObj.appendChild(monthBod);
		// add daily labels row
			var dailyHdr   = document.createElement('tr');
				dailyHdr   = monthBod.appendChild(dailyHdr);
				dailyHdr.className = 'weekdays';
			for (var loop in root.dailyNames) {
				var dailyCel   = document.createElement('td');
					dailyCel   = dailyHdr.appendChild(dailyCel);
					dailyCel.innerHTML = root.dailyNames[loop];
			}
		// make all calendar rows
			var cellCount  = 0;
			var dailyCount = 0;
			for (var row = 0; row <= 6; row++) {
				var thisRow = document.createElement('tr');
					thisRow = monthBod.appendChild(thisRow);
				for (var day in root.dailyNames) {
					cellCount++;
					var thisCell = document.createElement('td');
						thisCell = thisRow.appendChild(thisCell)
					if (cellCount > startDay && dailyCount < dailyData.length) {
					// set innerHTML and events for these days
						dailyCount++;
						thisCell.innerHTML = dailyCount;
					// add className to date cell, as approrprate
						if ((dailyCount == 1 && day == 6) || (dailyCount == dailyData.length + 1 && day == 0)) { thisCell.className = 'uCap' }
						else if (day == 6 || dailyCount == dailyData.length) { thisCell.className = 'rCap'; }
						else if (day == 0 || dailyCount == 1) { thisCell.className = 'lCap'; }
						else { thisCell.className = 'mCap'; }
					// parse data into string, evalable to array
						var todayData = dailyData[dailyCount-1]; var dataStr = '[';
						var parkOpen  = false;
						for (var loop in todayData) {
						// make dataStr
							dataStr += '["' + todayData[loop][0] + '","'  +todayData[loop][1]+ '"]';
							if (loop < todayData.length-1) { dataStr += ','} else { dataStr += ']' }
							parkOpen = eval(todayData[loop][2]);
							// check if park open, while we're at it
							//	if (todayData[loop][1] && todayData[loop][1].toLowerCase().indexOf('closed') == -1) { parkOpen = true; }
						}
						if (!parkOpen) { thisCell.className = thisCell.className  +' closed'; }
					// add custom properties
						thisCell.setAttribute('day',       dailyCount);
						thisCell.setAttribute('dateIndex', day);
						thisCell.setAttribute('weekIndex', row);
						thisCell.setAttribute('month',     (monthIndex+1));
						thisCell.setAttribute('year',      yearName);
						thisCell.setAttribute('data',      dataStr);
						thisCell.setAttribute('parkopen',  parkOpen);
					// add events for calendars that support them
						if(root.dateTable && parkOpen) {
							thisCell.onclick = function() {
							// try and register the date Table object, if it has been created.
								var dateTable = function () {  return allTables[root.dateTable] };
								if (dateTable() && typeof(dateTable()) != 'string') {
									dateTable = dateTable()
								// assemble new date string
									var newRequestDate      = this.getAttribute('month') +'/'+ this.getAttribute('day') +'/'+ this.getAttribute('year');
								// get current querystring
									var currentRequest      = new queryHandler(dateTable.baseReq)
									  	currentRequest.date = newRequestDate;
								// change date value in querystring, and reinsert
									dateTable.baseReq = currentRequest.tostring()
								// update table remotely
									dateTable.updateContent();
								// update warning with date
									document.getElementById('noEventsDate').innerHTML =  root.monthNames[monthIndex] + ' ' + this.getAttribute('day') + ' EVENTS';
								// highlight current cell
									var allCells = root.parent.getElementsByTagName('td');
									for (var loop = 0; loop < allCells.length; loop++) {
										var curCell = allCells[loop];
										if (curCell.getAttribute('day')) {
											curCell.style.backgroundImage = '';
											curCell.style.color = '';
										}
									}
									this.style.backgroundImage = 'url(/global/assets/images/bg/2C4973.gif)';
									this.style.color      = '#FFFFFF';
								}
							}
						}
					// show park hours on mouseover
						thisCell.onmouseover = function() {
						// try and register the date Table object, if it has been created.
							if (dateTable && typeof(dateTable) == 'string') { try { var dateTable = allTables[root.dateTable]; } catch (e) { } }
						// create or update pallette
							if (root.pallette === false) {
								var newPallate = root.newPallet(this);
								root.pallette = document.body.appendChild(newPallate);
							} else {
								root.refreshPallette(this)
							}
						// get some dom
							var holder  = this.parentNode.parentNode.parentNode.parentNode;
							var contDiv = document.getElementsByTagName('div')[0];
							var refDiv  = root.parent.parentNode.parentNode;
						// set pallate margin offsets
							root.pallette.style.marginTop = (0 - root.pallette.offsetHeight) + 'px';
							root.pallette.style.marginLeft = (0 - (root.pallette.offsetWidth/2)) + 'px';
						// gecko freak flags fly
							if (client.engine == 'gecko') { root.pallette.style.marginLeft = (client.os == 'mac') ? (parseInt(root.pallette.style.marginLeft) - 10) + 'px' : (parseInt(root.pallette.style.marginLeft) - 9) + 'px' ; }
							var cellX = 100; // defaults for testing & dev work
							var cellY = 100; // defaults for testing & dev work
						// nearly as fun as doing your taxes:
							switch(root.parent.id) {
								case 'middle':
									// get parent offset
										cellX  = getObjOffset(contDiv)[0] + 151;
										cellY  = getObjOffset(contDiv)[1] + 115;
										cellY += getObjOffset(document.getElementById('middle'), document.getElementById('listing'))[1];
										cellY += parseInt(document.getElementById('listing').getElementsByTagName('p')[0].offsetHeight);
										cellX += 30;
									// adjust position by calendar width
										for (var loop = 0; loop < root.months.childNodes.length; loop++) {
											if (root.months.childNodes[loop] == holder) { break; }
											cellX += holder.offsetWidth;
										}
									// adjust position by date
										cellX += getObjOffset(this, this.parentNode)[0]; // sixflags safari
										cellY += getObjOffset(this, this.parentNode)[1];
									// make final adjustments
										if (client.engine == 'msie') {
										// for standards-challenged browsers
											cellX += 10;
											cellY -= 45;
										} else {
										// for standards-capable browsers
											cellX += 15;
											cellY += 25;
										}

								break;
								case 'left':
									// get parent offset
										cellX = getObjOffset(contDiv)[0];
										cellY = getObjOffset(contDiv)[1];
									// move down to calendar object
										cellY +=  (client.engine == 'msie') ? getObjOffset(root.parent)[1] : getObjOffset(root.parent, root.parent.parentNode)[1];
									// adjust for top buttons
										cellY += 25;
									// adjust offset by calendar height
										for (var loop = 0; loop < root.months.childNodes.length; loop++) {
											if (root.months.childNodes[loop] == holder) { break; }
											cellY += holder.offsetHeight;
										// gecko naughtiness
											if (client.engine != 'gecko')   { cellY += 14; }
										}
									// adjust position by date
										cellX += getObjOffset(this, this.parentNode)[0] + 22;
										cellY += getObjOffset(this, this.parentNode)[1] - 4;
								break;
							}
						// make final viewport-based adjustment
								var realOffset = cellX - Math.abs(parseInt(root.pallette.style.marginLeft));
								if (realOffset <= 3) {
									var karetOffset = Math.abs(cellX) - Math.abs(3 + Math.abs(parseInt(root.pallette.style.marginLeft)));
									cellX = 3 + Math.abs(parseInt(root.pallette.style.marginLeft))
									root.pallette.lastChild.firstChild.style.marginLeft = (75 + karetOffset) + 'px';
								} else {
									root.pallette.lastChild.firstChild.style.marginLeft = '75px';
								}
							root.pallette.style.left = cellX + 'px';
							root.pallette.style.top  = cellY + 'px';
							root.pallette.style.visibility = 'visible';
						}
						thisCell.onmouseout = function() {
							if (root.pallette !== false) {
								root.pallette.style.visibility = 'hidden'; //document.body.removeChild(root.pallette);
								//root.pallette = false;
							}
						}
					}
				}
			}
			monthObj = monthParent.appendChild(monthObj)
			return monthParent;
		}
	// this adds a month to the parent object, given
	// the months data and the state of the calendar
		root.addMonth = function(data) {
		// eval data, turn into array
			if (data && typeof(data) == 'string') { data = eval(data); }
		// make offset javascript friendly
				data[1] = parseInt(data[1]) - 1;
		// set globals if this is the first request
			if (typeof(root.firstMonth) == 'boolean') {
				root.firstYear  = parseInt(data[0]);
				root.firstMonth = parseInt(data[1]);
			}
		// create the raw table object from data (xmlhttp data)
			var thisMonth = root.makeMonth(data);
		// insert the new table and remove the old one
			if (root.killNode && root.killNode != 'undefined') {
				if (root.killNode == root.months.lastChild) {
					root.months.insertBefore(thisMonth, root.months.firstChild);
				} else {
					root.months.appendChild(thisMonth);
				}
				root.months.removeChild(root.killNode);
				root.killNode = false;
			} else {
				thisMonth = root.months.appendChild(thisMonth);
			// repeat until we have a full calendar
				root.update();
			}
		}
	// updates the calendar, given the state of things
		root.update = function() {
			if (root.months.childNodes.length <= root.totalMonths) {
				if (typeof(root.firstMonth) == 'boolean') {
				// initially
					root.getMonth(root.firstMonth, root.firstYear);
				} else {
						var count = root.months.childNodes.length;
						var thisMonth = (root.firstMonth + count) % 12;
						var lastMonth = parseInt(root.months.lastChild.getAttribute('monthNum'));
						var lastYear  = parseInt(root.months.lastChild.getAttribute('yearName'));
						if (lastMonth == 11) { thisYear = lastYear+1; } else { thisYear = lastYear; }
						root.getMonth(thisMonth, thisYear);
				}
			} else {
			// get top/botom next/last linkage
				var topRotLnks = root.parent.firstChild.childNodes;
				var botRotLnks = root.parent.lastChild.childNodes;
			// set visibility by availability
				topRotLnks[0].style.visibility = botRotLnks[0].style.visibility = (root.months.lastChild.getAttribute('firstmo') == 'false') ? 'hidden' : 'visible';
				topRotLnks[1].style.visibility = botRotLnks[1].style.visibility = (root.months.firstChild.getAttribute('lastmo') == 'false') ? 'hidden' : 'visible';
			}
		}
	// moves one month forward or backwards in time.
		root.stepMonth = function(dir) {
		// don't do anything if previous request is unfinished
			var count = root.months.childNodes.length;
			var firstMonth = root.firstMonth;
			var lastMonth  = (root.firstMonth + (count-1)) % 12;
			if (root.months.childNodes.length == root.totalMonths + 1) {
				if (dir == 'next') {
					// set dates for next month
						var count = root.months.childNodes.length;
						var thisMonth = (root.firstMonth + count) % 12;
						root.firstMonth = (firstMonth + 1) % 12;
						var lastMonth = parseInt(root.months.lastChild.getAttribute('monthNum'));
						var lastYear  = parseInt(root.months.lastChild.getAttribute('yearName'));
						if (lastMonth == 11) { thisYear = lastYear+1; } else { thisYear = lastYear; }
						if (lastMonth == count) { root.firstYear++ }
					// count replacement node
						root.killNode = root.months.firstChild;
				} else {
					// set dates for last month
						var thisMonth = root.firstMonth = (root.firstMonth - 1 + 12) % 12;
						var firstMonth = parseInt(root.months.firstChild.getAttribute('monthNum'));
						var firstYear  = parseInt(root.months.firstChild.getAttribute('yearName'));
						if (firstMonth == 0) { thisYear = firstYear - 1; } else { thisYear = firstYear; }
						root.firstYear = thisYear;
					// count replacement node
						root.killNode = root.months.lastChild;
				}
			// request new month
			root.getMonth(thisMonth, thisYear);
			}
		}
	// creates floating hours pallette
		root.newPallet = function(cellObj) {
			var date = cellObj.getAttribute('month') +'/'+ cellObj.getAttribute('day');
				var data = eval(cellObj.getAttribute('data'));
		// build basic structure
			var hrsPallette = document.createElement('div');
				hrsPallette.id = 'parkHoursPallette';
			var hrsContDiv  = document.createElement('div');
				hrsContDiv  = hrsPallette.appendChild(hrsContDiv);
				hrsContDiv.className = 'content';
			var hrsKratDiv  = document.createElement('div');
				hrsKratDiv  = hrsPallette.appendChild(hrsKratDiv);
				hrsKratDiv.className = 'karet';
		// add content
			var hrsHeader   = document.createElement('h4');
				hrsHeader   = hrsContDiv.appendChild(hrsHeader);
				if (date) { hrsHeader.innerHTML = date + ' PARK HOURS:'; }
			var hrsList     = document.createElement('ul');
				hrsList     = hrsContDiv.appendChild(hrsList);
		// for each park
			if (data) {
				for (var loop in data) {
					var item = document.createElement('li');
					var desc = document.createTextNode(data[loop][0])
						desc = item.appendChild(desc);
					var opHr = document.createElement('b')
						opHr.innerHTML = data[loop][1];
						opHr = item.appendChild(opHr);
						item = hrsList.appendChild(item);
				}
			}
		// add karet
			var krsKratImg  = document.createElement('img');
				krsKratImg  = hrsKratDiv.appendChild(krsKratImg);
				krsKratImg.src = '/global/assets/images/bg/cal_ovr_karet.gif';
				krsKratImg.onmouseover = function() {
					var weekIndex = parseInt(cellObj.getAttribute('weekIndex'));
					var dateIndex = parseInt(cellObj.getAttribute('dateIndex'));
					var date      = parseInt(cellObj.getAttribute('day'));
					if (weekIndex > 0 && date > 6) {
						cellObj.parentNode.previousSibling.childNodes[dateIndex].onmouseover();
					}
				}
		// make invisibile, and return.
			hrsPallette.style.visibility = 'hidden';
			return hrsPallette;
		}
	// updates a pre-existing pallete with new information
		root.refreshPallette = function(cellObj) {
		// extract relevant data
			var date = cellObj.getAttribute('month') +'/'+ cellObj.getAttribute('day');
			var data = eval(cellObj.getAttribute('data'));
		// get some dom refs
			var hrsHeader   = root.pallette.firstChild.childNodes[0];
			var hrsListOld  = root.pallette.firstChild.childNodes[1];
			var krsKratImg  = root.pallette.lastChild.childNodes[0];
			var hrsList     = document.createElement('ul')
		// update the structure
			if (date) { hrsHeader.innerHTML = date + ' PARK HOURS:'; }
		// for each park
			if (data) {
				for (var loop in data) {
					var item = document.createElement('li');
					var desc = document.createTextNode(data[loop][0])
						desc = item.appendChild(desc);
					var opHr = document.createElement('b')
						opHr.innerHTML = data[loop][1];
						opHr = item.appendChild(opHr);
						item = hrsList.appendChild(item);
				}
			}
			krsKratImg.onmouseover = function() {
					var weekIndex = parseInt(cellObj.getAttribute('weekIndex'));
					var dateIndex = parseInt(cellObj.getAttribute('dateIndex'));
					var date      = parseInt(cellObj.getAttribute('day'));
					if (weekIndex > 0 && date > 6) {
						try { cellObj.parentNode.previousSibling.childNodes[dateIndex].onmouseover(); } catch (e) {}
					}
			}
			hrsListOld = hrsListOld.parentNode.replaceChild(hrsList, hrsListOld);
		}
	// initializes new calendar
		root.initialize = function() {
		// create parent for next/last buttons
			root.topBtns = document.createElement('div');
			root.topBtns = root.parent.appendChild(root.topBtns)
			root.topBtns.className = 'topButtons';
		// add next/last buttons to button parent
			var leftBtn  = document.createElement('a');
				leftBtn  = root.topBtns.appendChild(leftBtn);
				leftBtn.innerHTML = 'Back';
				leftBtn.className = 'back';
				leftBtn.onclick = function()  { root.stepMonth('last'); }
			var rightBtn = document.createElement('a');
				rightBtn = root.topBtns.appendChild(rightBtn);
				rightBtn.innerHTML = 'Next';
				rightBtn.className = 'next';
				rightBtn.onclick = function() { root.stepMonth('next'); }
		// create parent for calendar months
			root.months = document.createElement('div');
			root.months = root.parent.appendChild(root.months)
			root.months.className = 'holder';
		// add secondary row of buttons ... annoying
			root.botBtns = root.parent.firstChild.cloneNode(true);
			root.botBtns = root.parent.appendChild(root.botBtns);
			root.botBtns.className = 'botButtons';
		// cloneNode doesn't copy events ... retarded.
			root.botBtns.getElementsByTagName('a')[0].onclick = function() { root.stepMonth('last'); }
			root.botBtns.getElementsByTagName('a')[1].onclick = function() { root.stepMonth('next'); }
		// begin populating calendar months
			root.update();
		}
	// start it up
		root.initialize()
	}


	// RETURN ACTUAL ELEMENT OFFSETS
	function getObjOffset(thisObj, stopAt) {
		stopAt = (stopAt) ? stopAt : document.getElementsByTagName('body')[0];
		var parentNode  = thisObj;
		var offsets = [0,0];
		while (parentNode != stopAt) {
				offsets[0] += parentNode.offsetLeft;
				offsets[1] += parentNode.offsetTop;
				parentNode  = parentNode.parentNode;
		}
		return offsets;
	}


	function calTableProxy(data) {
	// I know this is stupid, but necessary to support MSIE... so lame.
		var tableState = data[0];
		if (tableState == 'false') { tableState == false; }
	// show or hide warning div
		document.getElementById('noEventsWarning').style.display = (tableState) ? 'none' : 'block' ;
	}
