	// Immutable power constructor pattern
	var Schedule = function(config, domEls){
		// Private members
		var columnIndices = config.columnIndices,
			shownAndHiddenRows,
			setText = function(dom, text) {
				if(Object.isUndefined(dom.textContent)) {
					dom.innerText = text;
				} else {
					dom.textContent = text;
				}
			},
			getText = function(dom) { 
				return (Object.isUndefined(dom.textContent))? dom.innerText: dom.textContent;
			},
			copyConfig = function(replacementConfig) {
				var newConfig ={};
				for(var prop in config) { if(config.hasOwnProperty(prop)) {
					newConfig[prop] = replacementConfig[prop] || config[prop];
				}};
				return newConfig;
			},
		// Private methods
		    getShownRows = config.getShownRows,
			getHiddenRows = config.getHiddenRows;

	    // Initialization
		(function() {
			config.year = parseInt(config.year, 10);
			config.month = parseInt(config.month, 10);

			var isFilterMatch = function(s1, s2) {
				var teamIdx,
				    titan       = "drag-titan-evt",
				    dragWorld   = "drag-world-evt",
				    jotech      = "drag-jotech-evt",
				    scottkelley = "drag-scottkelley-evt",
				    nfs		    = "drift-nfs-evt",
				    rsr         = "drift-rsr-evt",
				    crawford    = "road-crawford-evt",
				    dgspec      = "road-dgspec-evt",
				    sparco      = "road-sparco-evt",
				    greddy      = "road-greddy-evt",
				    roadWorld   = "road-world-evt",
				    mpme        = "road-mpme-evt",
					teamHash = {},
					genreHash = {};
				genreHash["Drag"]               = $A([titan, dragWorld, jotech, scottkelley]);
				genreHash["Drift"]              = $A([nfs, rsr]);
				genreHash["Road & Rally"] 		= $A([crawford, dgspec, sparco, greddy, roadWorld, mpme]);
				teamHash["Titan Motorsports"]   = titan;
				teamHash["World Racing"]        = dragWorld;
				teamHash["Jotech Motorsports"]  = jotech;
				teamHash["Scott Kelley Racing"] = scottkelley;
				teamHash["Team Need For Speed"] = nfs;
				teamHash["Team RS*R"]           = rsr;
				teamHash["Crawford Performance"]	= crawford;
				teamHash["DG-Spec"]        		= dgspec;
				teamHash["Sparco Rally xD"]     = sparco;
				teamHash["GReddy Racing"]       = greddy;
				teamHash["World Racing(Road)"]  = roadWorld;
				teamHash["MPME"]                = mpme;

				if(s1 === Schedule.FILTERS.NONE || s1.substr(-("Month".length)) === "Month") {
					return true;
				}

				// for each team in s2
				for(teamIdx in s2) { if( s2.hasOwnProperty(teamIdx)) {
					team = s2[teamIdx];
					if (team) { team = team.replace(/^team /i,""); }
					if(s1 === team || teamHash[s1] === team || genreHash[s1] && genreHash[s1].include(team)) {
						return true;
					} else {
						continue;
					}
				}}
			    return false;
			},
		        
			isDateMatch = function(filterDate, compareDate) {
				if(filterDate.year === parseInt(compareDate.substr(6,4),10)) {
					return (!filterDate.month) || (filterDate.month === parseInt(compareDate.substr(0,2),10));
				} else {
					return false;
				}
			},
			
			getColumnIndices = function() {
				if(columnIndices) return columnIndices;
				var scheduleTableHeadRow = domEls.scheduleEl.select('thead tr').first().childElements();
				columnIndices = {
					filterColumnIdx: scheduleTableHeadRow.indexOf(scheduleTableHeadRow.grep(config.filterColumnSelector).first()),
		       		yearColumnIdx: scheduleTableHeadRow.indexOf(scheduleTableHeadRow.grep(config.dateColumnSelector).first())
				};
				// Save to config
				config.columnIndices = columnIndices;
				return columnIndices;
			},
			
			isRowShown = function(row) {
				var compareYear, compareFilter, isMatch = false, compareTd = Element.extend(row.childElements()[getColumnIndices().filterColumnIdx]);
				if(row && row.childElements() && row.childElements().length > 0) { 
					compareYear = row.childElements()[getColumnIndices().yearColumnIdx];
					if(compareTd.select("span").length > 0) {
						if (compareTd.select("span").pluck("className").first() == "series") {
							// mobile events page
							compareFilter = [getText(compareTd.select("span").first())];
						} else {
						compareFilter = compareTd.select("span").pluck("className").flatten();
						}
					} else {
						compareFilter = [getText(compareTd)];
					}
					isMatch = isDateMatch({year: config.year, month: config.month}, getText(compareYear));
					isMatch = isMatch && isFilterMatch(config.filter, compareFilter);
					return isMatch;
				}
				return false;
			},
			
			getShownAndHiddenRows = function() {
				shownAndHiddenRows = shownAndHiddenRows || domEls.scheduleEl.select('tbody tr').partition(isRowShown, config);
				return shownAndHiddenRows;
			};
			
			// Create body of public methods in closure scope
			getShownRows = function() {
				// Save to config
				config.getShownRows = getShownAndHiddenRows()[0];
				return getShownAndHiddenRows()[0];
			},
			
			getHiddenRows = function() {
				// Save to config
				config.getHiddenRows = getShownAndHiddenRows()[1];
				return getShownAndHiddenRows()[1];
			};
		})();
			
		// Public methods
		return {
			// Update the page to reflect schedule definition (filter, year)
			display: function() {
			    var pastCurrentFutureRows,
					pastRows,
					currentAndFutureRows,
					isRowPast,
					toDate,
					rowEndDateFromString,
					compareDates,
					compareDateRows,
					midnightDate;
					
			    midnightDate = function(MM, DD, YYYY) {
					if(!(MM && DD && YYYY)) {
						var MM   = MM   | (new Date()).getMonth() + 1,
							DD   = DD   | (new Date()).getDate(),
							YYYY = YYYY | (new Date()).getFullYear();
					}
					// javascript months start at 0 but days start at 1
					return new Date(YYYY, MM - 1, DD, 0, 0, 0, 0);
				};

				toDate = function(dateString) {
					return midnightDate( parseInt(dateString.substr(0,2),10),
										 parseInt(dateString.substr(3,2),10),
										 parseInt(dateString.substr(6,4),10)
					);
      			};

				compareDates = function(d1, d2) {
				    var a = toDate(d1), b = toDate(d2);
				    return (a === b)? 0: (a > b)? 1: -1; 
				};	

				// Update the display -- works with CSS to display and hide rows
				// 1. Hide the hidden rows
				getHiddenRows()
						.invoke('removeClassName', 'active')
						.invoke('addClassName', 'inactive');
						
				// 2. Add "No results found message" if needed otherwise hide it
				if(getShownRows().length === 0) {
					domEls.scheduleEl.addClassName('inactive').removeClassName('active');
					domEls.messageEl.addClassName('active').removeClassName('inactive');
					if(config.filter !== Schedule.FILTERS.NONE) {
						setText(domEls.messageEl, "No events found for filter: " + config.filter);
					} else {
						setText(domEls.messageEl, "No events are currently scheduled");
					}
				} else {
					domEls.scheduleEl.addClassName('active').removeClassName('inactive');
					domEls.messageEl.addClassName('inactive').removeClassName('active');
				}

				// 3. Show the shown rows, hide the unshown
				getShownRows()
						.invoke('addClassName', 'active')
						.invoke('removeClassName', 'inactive');
						
				// 4. Gray out past event text
				rowEndDateFromString = function(compareDate) {
					var endDate = (/([01][0-9]\/[0-3][0-9]\/[12][0-9]{3})$/).exec(compareDate)[1];
					if(!!(!endDate && console)) {
					  console.log("Row Highligting Error. Expecting dates in MM/DD/YYYY-MM/DD/YYYY format. Found: " + compareDate);
					  compareDate = (compareDate && compareDate.substr(11));
					}
				    return toDate(endDate);
				};
				
				/* There is no way to check the timezone of the user, nor does the data specify times and timezones,
				     so we only archive the row into the past if the browser's time is at least a day beyond the data's time at 00:00
				 */  
				isRowPast = function(row) {
					var dateString  = getText(row.childElements()[columnIndices.yearColumnIdx])
					  , endDate = rowEndDateFromString(dateString)
					  ;
					return ((midnightDate()) > new Date(endDate.getTime()));
				};

				pastCurrentFutureRows = getShownRows().partition(isRowPast);
				pastRows              = pastCurrentFutureRows[0];
				currentAndFutureRows  = pastCurrentFutureRows[1];

				pastRows.invoke("addClassName","past");
				currentAndFutureRows.invoke("removeClassName","past");
				
				// 5. Append new rows to end
				compareDateRows = function(row1, row2) {
					var row1Date = getText(row1.childElements()[columnIndices.yearColumnIdx])
					  , row2Date = getText(row2.childElements()[columnIndices.yearColumnIdx])
					  ;
					return compareDates(row1Date, row2Date);
				};
				
				if(getShownRows().last()) {
					currentAndFutureRows.sort(compareDateRows).each(function(element) {
						this.parent.insert(element.remove());
					}.bind({parent: getShownRows().last().ancestors().first()}));
				}
				
				// 6. Append older rows to the end in reverse order
				if(getShownRows().last()) {
					pastRows.sort(compareDateRows).reverse().each(function(element) {
						this.parent.insert(element.remove());
					}.bind({parent: getShownRows().last().ancestors().first()}));
				}
				
				shownAndHiddenRows = null; // remove caching now order has changed
				
				// 7. Reset dropdown and toggle UI to reflect current state
				if(config.filter === Schedule.FILTERS.NONE) {
					domEls.filterSelectEl.childElements()[0].selected = true;
				}
				setText(domEls.dateLabelEl, config.year);
			},
			// Return new Schedule for the year not shown (with the same filter)
			toggleYear: function() {
				var newConfig = copyConfig({month:null});
				if(newConfig.filter.substr(-("Month".length)) === "Month") {
					newConfig.filter = Schedule.FILTERS.NONE;
					newConfig.month = null;
					newConfig.year = null;
				}
				newConfig.year = (newConfig.year === Schedule.YEAR.y2010)? Schedule.YEAR.y2011 : Schedule.YEAR.y2010;
				return Schedule(newConfig, domEls); 
			},
			// Return new Schedule for the selected filter (with the same year (unless the filter changes the year))
			selectFilter: function(filter) {
				var currentMonth = parseInt((new Date()).getMonth(), 10),
				currentYear = parseInt((new Date()).getFullYear(), 10),
				newConfig = copyConfig({filter:filter});
				if(newConfig.filter === "Current Month") {
					newConfig.month = (currentMonth + 1) % 12;
					newConfig.year = currentYear; 
				} else if(newConfig.filter === "Past Month") {
					newConfig.month = ((currentMonth - 1) < 0)? 12: currentMonth;
					newConfig.year = (currentMonth === 0)? currentYear - 1: currentYear;
				} else {
					newConfig.month = null;
				}
				
				if(newConfig.filter === Schedule.FILTERS.SEPARATOR || newConfig.filter === Schedule.FILTERS.NONE) {
					newConfig.filter = Schedule.FILTERS.NONE;
				} else{
				    // Remove leading hyphens and trim any invisibles
				    newConfig.filter = newConfig.filter.substr(newConfig.filter.indexOf("-") + 1).replace(/^\s\s*/, '').replace(/\s\s*$/, '');
				}
				
				/* WHOA!    > 10!!! Where did I get that number??*/ /* Todo: fix bad magic number hack to distinguish one world racing from the other*/
				if(newConfig.filter === "World Racing" && domEls.filterSelectEl.selectedIndex > 10) {
					newConfig.filter = newConfig.filter + "(Road)";
				}

				return Schedule(newConfig, domEls);
			},
			getYear: function() {
				return config.year;
			},
			getFilter: function() {
				return config.filter;
			}
		}
	};
	// Static properties
	Schedule.YEAR = {y2009: 2009, y2010: 2010, y2011: 2011};
	Schedule.FILTERS = {NONE:'-Select-', SEPARATOR: '----------------------------'}; 


