/*
	MLBScoreboard.js
	Defines an MLBScoreboard object that, when initialized, will populate an array of contest objects periodically via ajax
*/
function MLBScoreboard(scoreboardDatUrl, contestDivPrefix, contestBuilderFunction) {
	var self = this;
	self._scoreboardDatUrl = scoreboardDatUrl;
	self._contestDivPrefix = contestDivPrefix;
	self._contestBuilderFunction = contestBuilderFunction;
	self._updateInterval = 30; // seconds!
	self._rawContests = {};
	self.contests = [];
	self._contestScoreCache = {};
	
	self.DELIMITERS = ["\n", "|", ";", "#", "$"];
	// same for each team, no need to duplicate
	var TEAM_FIELD_INDICES = [
		"id",
		"vendorId",
		"name",
		"shortName",
		"nickname",
		"abbr",
		"urlName",
		"runs",
		"hits",
		"errors",
		"wins",
		"losses",
		"seriesWins",
		[
			"battingAvgLeaderId",
			"battingAvgLeaderLastName",
			"battingAvgLeaderLinkable",
			"battingAvgLeaderAvg"
		],
		[
			"homeRunLeaderId",
			"homeRunLeaderLastName",
			"homeRunLeaderLinkable",
			"homeRunLeaderHomeRuns"
		],
		[
			"RBILeaderId",
			"RBILeaderLastName",
			"RBILeaderLinkable",
			"RBILeaderRBIs"
		],
		[
			"runsLeaderId",
			"runsLeaderLastName",
			"runsLeaderLinkable",
			"runsLeaderRuns"
		],
		[
			"startingPitcherId",
			"startingPitcherLastName",
			"startingPitcherLinkable",
			"startingPitcherWins",
			"startingPitcherLosses",
			"startingPitcherERA"
		],
		[
			"dueUpPlayer1Id",
			"dueUpPlayer1LastName",
			"dueUpPlayer1Linkable"
		],
		[
			"dueUpPlayer2Id",
			"dueUpPlayer2LastName",
			"dueUpPlayer2Linkable"
		],
		[
			"dueUpPlayer3Id",
			"dueUpPlayer3LastName",
			"dueUpPlayer3Linkable"
		],
		{
			name: "homeRuns",
			isArray: true,
			fields: [
				"playerId",
				"playerLastName",
				"playerSeasonHRs",
				"summaryText"
			]
		},
		{
			name: "innings",
			isArray: true,
			fields: [ "score" ]
		}
	];
	self.CONTEST_FIELD_INDICES = [
		"id",
		"vendorId",
		"seasonYear",
		"stageId",
		"substageId",
		"substageName",
		"leagueAbbr",
		"status",
		"date",
		"time",
		// game state
		[
			"order",
			"inning",
			"inningHalf",
			"balls",
			"strikes",
			"outs",
			"leftOnBase"
		],
		// venue
		[
			"venueName",
			"venueCity",
			"venueState",
			"venueZipCode"
		],
		// weather
		[
			"weatherIconId",
			"weatherIconName",
			"weatherTemperature"
		],
		// links
		[
			"previewLink",
			"boxscoreLink",
			"recapLink",
			"playByPlayLink",
			"matchupLink",
			"gameBlogUrlInd"
		],
		// winning pitcher
		[
			"winningPitcherId",
			"winningPitcherLastName",
			"winningPitcherLinkable",
			"winningPitcherWins",
			"winningPitcherLosses"
		],
		// losing pitcher
		[
			"losingPitcherId",
			"losingPitcherLastName",
			"losingPitcherLinkable",
			"losingPitcherWins",
			"losingPitcherLosses"
		],
		// saving pitcher
		[
			"savingPitcherId",
			"savingPitcherLastName",
			"savingPitcherLinkable",
			"savingPitcherSaves"
		],
		// current pitcher
		[
			"currentPitcherId",
			"currentPitcherLastName",
			"currentPitcherLinkable"
		],
		// current batter
		[
			"currentBatterId",
			"currentBatterLastName",
			"currentBatterLinkable"
		],
		// home team
		{
			name: "homeTeam",
			fields: TEAM_FIELD_INDICES
		},
		// visitor team
		{
			name: "visitorTeam",
			fields: TEAM_FIELD_INDICES
		}
	];
	
	self.loadScoreboard();
	var pe = new PeriodicalExecuter(function(pe) { self.loadScoreboard(); }, self._updateInterval);
}

// static "constants"
MLBScoreboard.CDN_URL = "http://i.a.cnn.net/si";
MLBScoreboard.TRUE = "T";
MLBScoreboard.FALSE = "F";

MLBScoreboard.prototype.loadScoreboard = function() {
	var self = this;
	var url = this._scoreboardDatUrl.indexOf("?") >= 0 ? this._scoreboardDatUrl : this._scoreboardDatUrl;
	var r = new Ajax.Request(url, {
		method: 'get',
		evalJS: false,
		evalJSON: false,
		onSuccess: function(request) {
			self.parseDataFile(request.responseText);
			self.updateScoreboard();
			for (var i=0; i < self.contests.length; i++) {
				if (self.contests[i].id) {
					self._contestScoreCache[self.contests[i].id] = { home: self.contests[i].homeTeam.runs, visitor: self.contests[i].visitorTeam.runs };
				}
			}
		},
		onFailure: function(request) {
			// do nothing?
		},
		onException: function(request, ex) {
			// log to firebug
			if (console && console.error) {
				console.error("Request exception: " + ex.name + ": " + ex.message);
			}
		}
	});
};

MLBScoreboard.prototype.updateScoreboard = function () {
	if (!this.contests || !this.contests.length || this.contests.length <= 0) {
		return false;
	}
	// loop through contests and look for a div with the id corresponding to the contest id
	for (var i=0; i < this.contests.length; i++) {
		var contest = this.contests[i];
		if (contest._fresh && contest.id) {
			var contestDiv = $(this._contestDivPrefix + contest.id);
			if (contestDiv) {
				var newContestBoxscore = this._contestBuilderFunction.call(contest);
				while (contestDiv.childNodes.length > 0) {
					contestDiv.removeChild(contestDiv.childNodes[0]);
				}
				contestDiv.appendChild(newContestBoxscore);
			}
		}
	}
	return true;
};
	
// parse the data file and return an array of contest objects
MLBScoreboard.prototype.parseDataFile = function (dataFileContents) {
	var lines = dataFileContents.split(this.DELIMITERS[0]);
	// first line is timestamp + update interval
	var metadata = lines[0].split(this.DELIMITERS[1]);
	this._lastUpdatedTimestamp = metadata[0];
	this._updateInterval = metadata[1];
	
	// following lines are each a contest
	for (var i=1; i < lines.length; i++) {
		// manually get the contest id from this line and check a hash
		var parts = lines[i].split(this.DELIMITERS[1]);
		if (parts.length > 0) {
			var id = parts[0];
			if (!this._rawContests[id] || this._rawContests[id] != lines[i]) {
				this._rawContests[id] = lines[i];
				var contest = this.createContest(lines[i]);
				this.contests[i - 1] = contest;
			} else {
				this.contests[i - 1]._fresh = false;
			}
		}
	}
};

MLBScoreboard.prototype.createContest = function(contestData) {
	var contest = new Contest();
	try {
		this.populateObject(contest, this.CONTEST_FIELD_INDICES, 1, contestData);
		contest._scoreboard = this;
	} catch (ex) {
		if (ex instanceof Error) {
			if (console && console.error) {
				console.error(ex.name + ": " + ex.message);
			}
		}
		return null;
	}
	return contest;
};

MLBScoreboard.prototype.populateObject = function(o, fieldMap, delimiterIndex, data) {
	if (!o || !data) { return; }
	if (!fieldMap || !fieldMap.length || fieldMap.length == 0) {
		throw new Error("Invalid or empty fieldMap specified");
	}
	if (delimiterIndex < 0 || delimiterIndex > this.DELIMITERS.length - 1) {
		throw new Error("Invalid delimiterIndex (must be between zero and delimiter array length)");
	}
	
	var dataArray = data.split(this.DELIMITERS[delimiterIndex]);
	if (dataArray.length != fieldMap.length) {
		var msg = "Unparseable data: field map length = " + fieldMap.length + "; data length = " + dataArray.length;
		if (fieldMap.toJSON) {
			msg += "\nfieldMap = " + fieldMap.toJSON();
		}
		if (dataArray.toJSON) {
			msg += "\ndataArray = " + dataArray.toJSON();
		}
		throw new Error(msg);
	}
	for (var i=0; i < fieldMap.length; i++) {
		var field = fieldMap[i];
		switch (typeof field) {
			case 'string':
				// simply set the value
				o[field] = dataArray[i];
				break;
			case 'object':
				if (field instanceof Array) {
					// recursively populate the object with the sub-fields and the next delimiter
					this.populateObject(o, field, delimiterIndex + 1, dataArray[i]);
				} else {
					if (!field.name || !field.fields) {
						throw new Error("Unparseable data: field map object does not contain required members");
					}
					if (field.isArray) {
						o[field.name] = [];
						// split the current data based on the next delimiter, and recursively populate each object in the array
						if (dataArray[i].length > 0) {
							var fieldDataArray = dataArray[i].split(this.DELIMITERS[delimiterIndex + 1]);
							for (var j=0; j < fieldDataArray.length; j++) {
								this.populateObject(o[field.name][j] = {}, field.fields, delimiterIndex + 2, fieldDataArray[j]);
							}
						}
					} else {
						// just populate a single field of the current object with a new object
						this.populateObject(o[field.name] = {}, field.fields, delimiterIndex + 1, dataArray[i]);
					}
				}
				break;
			default:
				throw new Error("Unparseable data: unknown field type: " + typeof field);
		}
	}
};

MLBScoreboard.prototype.createCookie = function(name,value,days,path) {
	var expires = "";
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		expires = "; expires="+date.toGMTString();
	}
	document.cookie = name+"="+encodeURIComponent(value)+expires+"; path=" + (path ? path : "/");
};

MLBScoreboard.prototype.readCookie = function(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') {
			c = c.substring(1,c.length);
		}
		if (c.indexOf(nameEQ) == 0) {
			return decodeURIComponent(c.substring(nameEQ.length,c.length));
		}
	}
	return null;
};

/* a contest class */
function Contest() {
	this._fresh = true;
}

// static "constants"
Contest.STATUS_FINAL = "FINA";
Contest.STATUS_IN_PROGRESS = "PROG";
Contest.STATUS_DELAYED = "DELA";
Contest.STATUS_POSTPONED = "POST";
Contest.STATUS_CANCELED = "CANC";
Contest.STATUS_SUSPENDED = "SUSP";
Contest.NEW_SCORE_DELAY = 60000; //ms to show score as "new"

// sort function for homeruns
Contest.homeRunOrder = function(a, b) {
	// first by player id, then by seasonHRs
	return (a.playerId - b.playerId == 0) ? a.playerSeasonHRs - b.playerSeasonHRs : a.playerId - b.playerId;
};

// returns inning as an ordinal number
Contest.prototype.getOrdinalInning = function() {
	if (this.inning) {
		var intInning = parseInt(this.inning, 10);
		if (!isNaN(intInning)) {
			var ord;
			switch (intInning) {
				case 0:
					return null;
				case 1:
				case 21:
					ord = "st";
					break;
				case 2:
				case 22:
					ord = "nd";
					break;
				case 3:
				case 23:
					ord = "rd";
					break;
				default:
					ord = "th";
			}
			return intInning + ord;
		}
	}
	return null;
};

Contest.prototype.buildEntity = function(str) {
	str = (str) ? str : "";
	var e = document.createElement("div");
	e.innerHTML = str;
	return e.innerHTML;
};

// converts contest into a dom tree
Contest.prototype.toDOM = function() {
	var contestHTMLData;
	switch (this.status) {
		case Contest.STATUS_IN_PROGRESS:
		case Contest.STATUS_DELAYED:
			contestHTMLData = this.buildInProgressData();
			break;
		case Contest.STATUS_FINAL:
		case Contest.STATUS_POSTPONED:
		case Contest.STATUS_SUSPENDED:
			contestHTMLData = this.buildFinalData();
			break;
		default:
			contestHTMLData = this.buildPreGameData();
	}
	var boxDiv = $E({
			tag: "div", className: "cnnscorebox",
			children: contestHTMLData
	});
	return boxDiv;
};

Contest.prototype.toMiniDOM = function() {
	var boxTable = $E({
			tag: "table", align: "center", cellspacing: "0", children: {
				tag: "tbody", children: [
					{
						tag: "tr", children: [
							{ tag: "td", className: "cnnTeamName", children: this.visitorTeam.shortName.toUpperCase() },
							this.buildTeamScoreCell("visitor", this.visitorTeam)
						]
					},
					{
						tag: "tr", children: [
							{ tag: "td", className: "cnnTeamName", children: this.homeTeam.shortName.toUpperCase() },
							this.buildTeamScoreCell("home", this.homeTeam)
						]
					},
					{
						tag: "tr", children: {
							tag: "td", colSpan: "2", children: {
								// entire table for one piece of data
								tag: "table", className: "cnnBotSize", align: "center", children: {
									tag: "tbody", children: {
										tag: "tr", children: {
											tag: "td", className: "cnnSgleLine", children: this.buildMiniStatus()
										}
									}
								}
							}
						}
					}
				]
			}
	});
	return boxTable;
};

Contest.prototype.buildTeamScoreCell = function(side, team) {
	var cellId = "miniScore_" + this.id + "_" + side;
	if (this.status == Contest.STATUS_IN_PROGRESS || this.status == Contest.STATUS_FINAL) {
		var scoreNew = this._scoreboard._contestScoreCache &&
			this._scoreboard._contestScoreCache[this.id] && 
			this._scoreboard._contestScoreCache[this.id][side] &&
			this._scoreboard._contestScoreCache[this.id][side] != team.runs;
		if (scoreNew) {
			window.setTimeout(this.createScoreResetFunction(cellId, "cnnScore"), Contest.NEW_SCORE_DELAY);
		}
		return { tag: "td", id: cellId, className: (scoreNew ? "cnnScoreRed" : "cnnScore"), children: (team.runs ? team.runs : "\u00a0") };
	} else {
		return { tag: "td", id: cellId, className: "cnnScore", children: "\u00a0" };
	}
};

Contest.prototype.buildTeamLogo = function(team) {
    if (team.shortName.toUpperCase() == "NATIONAL" || team.shortName.toUpperCase() == "AMERICAN") {
        return { tag: "a", href: "", children: { tag: "img", width: "60", height: "40", border: "0", alt: team.shortName, src: this.createTeamLogoUrl(team.urlName) } };
    } else {
        return { tag: "a", href: this.createTeamUrl(team.urlName), children: { tag: "img", width: "60", height: "40", border: "0", alt: team.shortName, src: this.createTeamLogoUrl(team.urlName) } };
    }
};

Contest.prototype.buildSmallTeamLogo = function(team) {
    if (team.shortName.toUpperCase() == "NATIONAL" || team.shortName.toUpperCase() == "AMERICAN") {
        return { tag: "a", href: "", children: { tag: "img", src: this.createSmallTeamLogoUrl(team.urlName) } };
    } else {
        return { tag: "a", href: this.createTeamUrl(team.urlName), children: { tag: "img", src: this.createSmallTeamLogoUrl(team.urlName) } };
    }
};

Contest.prototype.buildTeamName = function(team) {
    if (team.shortName.toUpperCase() == "NATIONAL" || team.shortName.toUpperCase() == "AMERICAN") {
        return { tag: "a", href: "", children: team.shortName };
    } else {
        return { tag: "a", href: this.createTeamUrl(team.urlName), children: team.shortName };
    }
};

Contest.prototype.buildMiniStatus = function() {
	var status = "";
	switch (this.status) {
		case Contest.STATUS_IN_PROGRESS:
			status = this.inningHalf + " " + this.getOrdinalInning();
			break;
		case Contest.STATUS_FINAL:
			status = "FINAL" + (this.inning > 9 ? " - " + this.inning + " Innings" : "");
			break;
		case Contest.STATUS_DELAYED:
			status = "DELAYED";
			break;
		case Contest.STATUS_POSTPONED:
			status = "POSTPONED";
			break;
		case Contest.STATUS_CANCELED:
			status = "CANCELED";
			break;
		default:
			status = this.time.toUpperCase() + " ET";
	}
	return status;
};

Contest.prototype.createTeamUrl = function(teamUrlName) {
    if (teamUrlName == "national_league" || teamUrlName == "american_league") {
		return "";
	} else {
		return "/baseball/mlb/teams/" + teamUrlName;
	} 
};

Contest.prototype.createTeamLogoUrl = function(teamUrlName) {
	return MLBScoreboard.CDN_URL + "/.element/img/3.0/sect/baseball/mlb/logos/" + teamUrlName + ".jpg";
};

Contest.prototype.createSmallTeamLogoUrl = function(teamUrlName) {
	return MLBScoreboard.CDN_URL + "/images/baseball/mlb/logos/" + teamUrlName + "_30.gif";
};

Contest.prototype.createPlayerUrl = function(playerId) {
	return "/baseball/mlb/players/" + playerId + "/index.html";
};

Contest.prototype.createGameTicketsLink = function(teamUrlName) {
    if (teamUrlName == "national_league" || teamUrlName == "american_league") {
		return "http://www.stubhub.com/mlb-all-star-game-tickets/mlb-all-star-game-?gcid=C12289x400";
	} else {
		return "http://www.stubhub.com/" + this.homeTeam.name.toLowerCase().replace(/ /g, "-").replace(/\./g, "") + "-" + this.homeTeam.nickname.toLowerCase().replace(/ /g, "-").replace(/\./g, "") + "-tickets?gcid=C12289x400";
	} 
};

Contest.prototype.createGameFlashUrl = function(pageName) {
	return this.createGenericGameFlashUrl(this.id, pageName);
};

Contest.prototype.createGenericGameFlashUrl = function(prefix, pageName) {
	return "/baseball/mlb/gameflash/" + this.date + "/" + prefix + "_" + pageName + ".html";
};

Contest.prototype.createBoxscoreUrl = function() {
	return this.createGameFlashUrl("gameflash");
};

Contest.prototype.createMatchupUrl = function() {
	return this.createGameFlashUrl("matchup");
};

Contest.prototype.createPlayByPlayUrl = function() {
	return this.createGameFlashUrl("playbyplay");
};

Contest.prototype.createFanCommentsUrl = function() {
	return this.createGameFlashUrl("fancomment");
};

Contest.prototype.createPhotosUrl = function() {
	return this.createGenericGameFlashUrl(this.vendorId, "photos");
};

Contest.prototype.createRecapUrl = function() {
	return this.createGameFlashUrl("recap");
};

Contest.prototype.createPreviewUrl = function() {
	return this.createGameFlashUrl("preview");
};

Contest.prototype.createSeriesRecord = function() {
	var homeWins = parseInt(this.homeTeam.seriesWins, 10);
	var visitorWins = parseInt(this.visitorTeam.seriesWins, 10);
	if (isNaN(homeWins) || isNaN(visitorWins)) {
		return "";
	}
	if (homeWins > visitorWins) {
		return this.homeTeam.shortName + " lead series " + homeWins + "-" + visitorWins;
	} else if (homeWins < visitorWins) {
		return this.visitorTeam.shortName + " lead series " + visitorWins + "-" + homeWins;
	} else {
		return "Series tied " + homeWins + "-" + visitorWins;
	}
};

// return a $E data structure to represent the pre-game html
Contest.prototype.buildPreGameData = function() {
	return [
		{
			tag: "div", className: "cnnUnScBx", children: [
				{ // start time and weather
					tag: "table", height: "20px", cellpadding: "0", cellspacing: "0", align: "left", children: {
						tag: "tbody", children: {
							tag: "tr", children: this.buildTimeAndWeatherData()
						}
					}
				},
				{ // venue
					tag: "table", height: "20px", cellpadding: "0", cellspacing: "0", align: "right", children: {
						tag: "tbody", children: {
							tag: "tr", children: { tag: "td", align: "right", children: this.venueName }
						}
					}
				}
			]
		},
		{
			tag: "table", className: "cnnTblNada", cellpadding: "0", cellspacing: "0", children: {
				tag: "tbody", children: {
					tag: "tr", children: [
						{ // team logos, records, buy tickets link
							tag: "td", className: "cnnbbrr11", children: {
								tag: "div", className: "cnntexxtalli", children: [
									{ // team logo + record table
										tag: "table", children: {
											tag: "tbody", children: {
												tag: "tr", valign: "top", children: [
													{ // visitor team
														tag: "td", valign: "top", className: "cnntdcltelign", children: [
                                                            this.buildTeamLogo(this.visitorTeam),
															{ // record
																tag: "div", className: "cnndivhero", children: {
																	tag: "span", children: "(" + this.visitorTeam.wins + "-" + this.visitorTeam.losses + ")"
																}
															}
														]
													},
													{ // pointless spacer div
														tag: "td", className: "cnnDivMid", height: "100%", children: { tag: "div", children: "|" }
													},
													{ // home team
														tag: "td", valign: "top", className: "cnntdcltelign", children: [
                                                            this.buildTeamLogo(this.homeTeam),
															{ // record
																tag: "div", className: "cnndivhero", children: {
																	tag: "span", children: "(" + this.homeTeam.wins + "-" + this.homeTeam.losses + ")"
																}
															}
														]
													}
												]
											}
										}
									},
									{ // buy tickets table
										tag: "table", width: "128px", className: "cnntbbbmartp2", children: {
											tag: "tbody", children: {
												tag: "tr", children: {
													tag: "td", className: "cnntdtacb", children: {
														tag: "a", href: this.createGameTicketsLink(this.homeTeam.urlName), target: "_blank", children: {
															tag: "img", className: "cnnimgstyy", width: "99", height: "14", border: "0",
															alt: "Buy Tickets", src: "http://i.a.cnn.net/si/.element/img/3.0/sect/football/nfl/scoreboards/buytickets_99x14.gif"
														}
													}
												}
											}
										}
									}
								]
							}
						},
						{ // team leaders
							tag: "td", children: {
								tag: "div", className: "cnnNbaBxSc", children: [
									{
										tag: "table", children: {
											tag: "tbody", children: [
												{ // nickname header row
													tag: "tr", className: "cnnFstRw", valign: "top", children: [
														{ tag: "td", className: "cnnTeamNameNone", children: { tag: "div" } },
														{
															tag: "td", className: "cnnTeamName", children: {
																tag: "div", children: {
																	tag: "a", href: this.createTeamUrl(this.visitorTeam.urlName), children: this.visitorTeam.shortName
																}
															}
														},
														{
															tag: "td", className: "cnnTeamName", children: {
																tag: "div", children: {
																	tag: "a", href: this.createTeamUrl(this.homeTeam.urlName), children: this.homeTeam.shortName
																}
															}
														}
													]
												},
												{ // batting avg leaders
													tag: "tr", children: [
														{ tag: "td", valign: "bottom", className: "cnnNBACat", children: { tag: "div", children: "BA" } },
														{ // visitor
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.visitorTeam.battingAvgLeaderId, this.visitorTeam.battingAvgLeaderLastName, this.visitorTeam.battingAvgLeaderLinkable),
																	this.visitorTeam.battingAvgLeaderId == 0 ? "-" : (" " + this.visitorTeam.battingAvgLeaderAvg)
																] 
															}
														},
														{ // home
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.homeTeam.battingAvgLeaderId, this.homeTeam.battingAvgLeaderLastName, this.homeTeam.battingAvgLeaderLinkable),
																	this.homeTeam.battingAvgLeaderId == 0 ? "-" : " " + this.homeTeam.battingAvgLeaderAvg
																] 
															}
														}
													]
												},
												{ // homerun leaders
													tag: "tr", children: [
														{ tag: "td", className: "cnnNBACat", children: { tag: "div", children: "HR" } },
														{ // visitor
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.visitorTeam.homeRunLeaderId, this.visitorTeam.homeRunLeaderLastName, this.visitorTeam.homeRunLeaderLinkable),
																	this.visitorTeam.homeRunLeaderId == 0 ? "-" : " " + this.visitorTeam.homeRunLeaderHomeRuns
																] 
															}
														},
														{ // home
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.homeTeam.homeRunLeaderId, this.homeTeam.homeRunLeaderLastName, this.homeTeam.homeRunLeaderLinkable),
																	this.homeTeam.homeRunLeaderId == 0 ? "-" : " " + this.homeTeam.homeRunLeaderHomeRuns
																] 
															}
														}
													]
												},
												{ // rbi leaders
													tag: "tr", children: [
														{ tag: "td", className: "cnnNBACat", children: { tag: "div", children: "RBI" } },
														{ // visitor
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.visitorTeam.RBILeaderId, this.visitorTeam.RBILeaderLastName, this.visitorTeam.RBILeaderLinkable),
																	this.visitorTeam.RBILeaderId == 0 ? "-" : " " + this.visitorTeam.RBILeaderRBIs
																] 
															}
														},
														{ // home
															tag: "td", children: {
																tag: "div", children: [
																	this.buildLinkedPlayer(this.homeTeam.RBILeaderId, this.homeTeam.RBILeaderLastName, this.homeTeam.RBILeaderLinkable),
																	this.homeTeam.RBILeaderId == 0 ? "-" : " " + this.homeTeam.RBILeaderRBIs
																] 
															}
														}
													]
												}
											]
										}
									},
								    this.createSeriesRecord()
								]
							}
						}
					]
				}
			}
		},
		{
			tag: "div", className: "cnnbtoppad5", children: {
				tag: "table", border: "0", width: "100%", cellpadding: "0", cellspacing: "4", className: "cnngreylnks00", children: {
					tag: "tbody", children: {
						// starting pitchers
						tag: "tr", align: "center", children: this.buildStartingPitcherData()
					}
				}
			}
		},
		{ // game links
			tag: "div", children: {
				tag: "table", className: "cnngreylnkstblman", border: "0", cellpadding: "0", cellspacing: "0", children: {
					tag: "tbody", children: {
						tag: "tr", children: this.buildGameLinksData()
					}
				}
			}
		}
	];
};

// return a series of $E td elements representing the game start time and weather forecast
Contest.prototype.buildTimeAndWeatherData = function () {
	var tds = [
		{
			tag: "td", className: "cnnstyllpargt5", align: "left", children: [
				{ tag: "strong", children: this.time + " ET" },
				{ tag: "span", children: "|" }
			]
		}
	];
	if (this.weatherIconId && this.weatherIconName && this.weatherTemperature) {
		tds.push(
			{
				tag: "td", className: "cnnstyllpargt5", align: "left", children: {
					tag: "a",
					href: "http://weather.cnn.com/weather/forecast.jsp?zipCode=" + this.venueZipCode,
					target: "_blank",
					children: this.weatherIconName + ", " + this.weatherTemperature + this.buildEntity("&deg;") + " F"
				}
			},
			{
				tag: "td", align: "left", children: {
					tag: "a",
					href: "http://weather.cnn.com/weather/forecast.jsp?zipCode=" + this.venueZipCode,
					target: "_blank",
					children: {
						tag: "img",
						width: "21",
						height: "17",
						border: "0",
						src: "http://i.a.cnn.net/cnn/.element/img/2.0/weather/01/" + this.weatherIconId + ".gif"
					}
				}
			}
		);
	}
	var gameNumberData = this.buildGameNumberData();
	if (gameNumberData !== null) {
		tds.unshift(gameNumberData);
	}
	return tds;
};

Contest.prototype.buildLinkedPlayer = function(id, name, linkable) {
	if (id == 0) {
		return "-";
	}
	if (linkable == MLBScoreboard.FALSE) {
		return name;
	}
	return {
		tag: "a", href: this.createPlayerUrl(id),
		children: name
	};
};

Contest.prototype.buildGameNumberData = function() {
	var gameNum = parseInt(this.gameNumber, 10);
	if (!isNaN(gameNum) && gameNum > 0) {
		return {
			tag: "td", align: "left", className: "cnnstyllpargt5", children: [
				{ tag: "strong", children: "Game " + gameNum },
				{ tag: "span", children: "|" }
			]
		};
	} else {
		return null;
	}
};

Contest.prototype.buildStartingPitcherData = function() {
	var homeSPId  = parseInt(this.homeTeam.startingPitcherId, 10);
	var visitorSPId = parseInt(this.visitorTeam.startingPitcherId, 10);
	if (isNaN(homeSPId) || homeSPId == 0 || isNaN(visitorSPId) || visitorSPId == 0) {
		return { tag: "td", align: "center", children: "No probable pitchers available" };
	}
	return  [
		{ 
			tag: "td", align: "center", children: [
				// visitor starting pitcher
				this.buildLinkedPlayer(this.visitorTeam.startingPitcherId, this.visitorTeam.startingPitcherLastName, this.visitorTeam.startingPitcherLinkable),
				"(" + this.visitorTeam.startingPitcherWins + "-" + this.visitorTeam.startingPitcherLosses + ", " + this.visitorTeam.startingPitcherERA + ")",
				" vs. ",
				// home starting pitcher
				this.buildLinkedPlayer(this.homeTeam.startingPitcherId, this.homeTeam.startingPitcherLastName, this.homeTeam.startingPitcherLinkable),
				"(" + this.homeTeam.startingPitcherWins + "-" + this.homeTeam.startingPitcherLosses + ", " + this.homeTeam.startingPitcherERA + ")"
			]
		}
	];
};

// return a $E series of TDs (one for each link)
Contest.prototype.buildGameLinksData = function() {
	var gameLinksData = [];
	var tdClass = "cnntacp300";
	// prog = box, matchup, pxp, fan comments, photos
	if (this.status == Contest.STATUS_IN_PROGRESS || this.status == Contest.STATUS_DELAYED) {
		gameLinksData.push(this.buildGameLinkData(this.createBoxscoreUrl(), "BOX", "viewcast", tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createMatchupUrl(), "MATCHUP", null, tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createPlayByPlayUrl(), "PLAY-BY-PLAY", null, tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createFanCommentsUrl(), "FAN COMMENTS", null, tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createPhotosUrl(), "PHOTOS", null, tdClass));
	}
	// fina = box, recap?, pxp, fan comments, photos
	else if (this.status == Contest.STATUS_FINAL || this.status == Contest.STATUS_POSTPONED || this.status == Contest.STATUS_SUSPENDED) {
		gameLinksData.push(this.buildGameLinkData(this.createBoxscoreUrl(), "BOX", "viewcast", tdClass));
		if (this.recapLink == MLBScoreboard.TRUE) {
			gameLinksData.push(this.buildGameLinkData(this.createRecapUrl(), "RECAP", null, tdClass));
		}
		gameLinksData.push(this.buildGameLinkData(this.createPlayByPlayUrl(), "PLAY-BY-PLAY", null, tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createFanCommentsUrl(), "FAN COMMENTS", null, tdClass));
		gameLinksData.push(this.buildGameLinkData(this.createPhotosUrl(), "PHOTOS", null, tdClass));
	}
	// pre = preview?, matchup, visitor news, home news
	else {
		if (this.previewLink == MLBScoreboard.TRUE) {
			gameLinksData.push(this.buildGameLinkData(this.createPreviewUrl(), "PREVIEW"));
		}
		gameLinksData.push(this.buildGameLinkData(this.createMatchupUrl(), "MATCHUP"));

        if (this.visitorTeam.shortName.toUpperCase() != "NATIONAL" && this.visitorTeam.shortName.toUpperCase() != "AMERICAN") {
                gameLinksData.push(this.buildGameLinkData(this.createTeamUrl(this.visitorTeam.urlName), this.visitorTeam.shortName.toUpperCase() + " NEWS"));
                gameLinksData.push(this.buildGameLinkData(this.createTeamUrl(this.homeTeam.urlName), this.homeTeam.shortName.toUpperCase() + " NEWS"));
        }
	}
	return gameLinksData;
};

Contest.prototype.buildGameLinkData = function(url, linkText, linkClass, tdClass) {
	return {
		tag: "td", className: tdClass ? tdClass : "", children: {
			tag: "a", className: linkClass ? linkClass : "", href: url, children: [
				{ tag: "img", src: MLBScoreboard.CDN_URL + "/.element/img/3.0/sect/football/nfl/scoreboards/arrow_transp.gif", width: "6", height: "7", border: "0" },
				" " + linkText
			]
		}
	};
};

// return a $E data structure to represent the in-progress html
Contest.prototype.buildInProgressData = function() {
	return this.buildGenericBoxscoreData(
		this.buildInProgressGameStateData(),
		this.buildGenericLinescoreData(),
		this.buildInProgressBelowLinescoreData()
	);
};

// return a series of TDs
Contest.prototype.buildInProgressGameStateData = function() {
	var stateTDs = [];
	var gameNumberData = this.buildGameNumberData();
	if (gameNumberData !== null) {
		stateTDs.push(gameNumberData);
	}
	if (this.status == Contest.STATUS_DELAYED) {
		stateTDs.push({ tag: "td", align: "left", children: { tag: "strong", children: "DELAYED" } });
	} else if (this.status == Contest.STATUS_POSTPONED) {
		stateTDs.push({ tag: "td", align: "left", children: { tag: "strong", children: "POSTPONED" } });
	} else {
		var ordinalInning = this.getOrdinalInning();
		var half = this.inningHalf;
		var change = false;
		if (this.outs == 3 || half == "Middle" || half == "End") {
			if (half != "Middle" && half != "End") {
				half = (half == "Top" ? "Middle" : "End");
			}
			change = true;
		}
		if (change) {
			// no b/s/o for inning change
			stateTDs.push(
				{ tag: "td", className: "cnnstyllpargt5", align: "left", children: { tag: "strong", children: half + " " + ordinalInning } }
			);
		} else {
			stateTDs.push(
				{ 
					tag: "td", className: "cnnstyllpargt5", align: "left", children: [
						{ tag: "strong", children: half + " " + ordinalInning },
						{ tag: "span", children: "|" }
					]
				},
				{
					tag: "td", className: "cnnstyllpargt5", align: "left", children: [
						"Balls: ",
						{ tag: "strong", children: this.balls }
					]
				},
				{
					tag: "td", className: "cnnstyllpargt5", align: "left", children: [
						"Strikes: ",
						{ tag: "strong", children: this.strikes }
					]
				},
				{
					tag: "td", className: "cnnstyllpargt5", align: "left", children: [
						"Outs: ",
						{ tag: "strong", children: this.outs }
					]
				}
			);
		}
	}
	return stateTDs;
};

// return a series of Tables 
// (top/bot: current pitcher/batter + homeruns)
// (mid: left on base + due up)
Contest.prototype.buildInProgressBelowLinescoreData = function() {
	var tables = [];
	var tds = [];
	if (this.outs == 3 || this.inningHalf == "Middle" || this.inningHalf == "End") {
		// left on base
		if (parseInt(this.leftOnBase, 10) > 0) {
			tds.push(
				{ tag: "td", valign: "top", align: "left", children: "Left On Base:" },
				{ tag: "td", valign: "top", align: "left", className: "cnn5toleft", children: this.leftOnBase }
			);
			tables.push( { tag: "table", cellpadding: "0", cellspacing: "0", className: "cnngreylnks00", children: { tag: "tbody", children: { tag: "tr", children: tds } } } );
		}
		// due up
		var team = ((this.inningHalf == "Top" || this.inningHalf == "Middle") ? this.homeTeam : this.visitorTeam);
		tables.push({
				tag: "table", cellpadding: "0", cellspacing: "0", className: "cnnTblHRList", height: "28", children: {
					tag: "tbody", children: {
						tag: "tr", children: {
							tag: "td", children: [
								"Due Up for " + team.name + ": ",
								{ tag: "strong", children: this.buildLinkedPlayer(team.dueUpPlayer1Id, team.dueUpPlayer1LastName, team.dueUpPlayer1Linkable) },
								", ",
								{ tag: "strong", children: this.buildLinkedPlayer(team.dueUpPlayer2Id, team.dueUpPlayer2LastName, team.dueUpPlayer2Linkable) },
								", ",
								{ tag: "strong", children: this.buildLinkedPlayer(team.dueUpPlayer3Id, team.dueUpPlayer3LastName, team.dueUpPlayer3Linkable) }
							]
						}
					}
				}
		});
	} else {
		// current pitcher/batter
		var currentPitcherId = parseInt(this.currentPitcherId, 10);
		var useCP = !isNaN(currentPitcherId) && currentPitcherId > 0;
		var currentBatterId = parseInt(this.currentBatterId, 10);
		var useCB = !isNaN(currentBatterId) && currentBatterId > 0;
		if (useCP || useCB) {
			if (useCP) {
				tds.push(
					{ tag: "td", valign: "top", align: "left", children: "Pitcher:\u00a0" },
					{ tag: "td", valign: "top", align: "left", children: { tag: "strong", children: this.buildLinkedPlayer(currentPitcherId, this.currentPitcherLastName, this.currentPitcherLinkable) } }
				);
			}
			if (useCB) {
				tds.push(
					{ tag: "td", valign: "top", align: "left", children: (useCP ? "\u00a0" : "") + "Batter:\u00a0" },
					{ tag: "td", valign: "top", align: "left", children: { tag: "strong", children: this.buildLinkedPlayer(currentBatterId, this.currentBatterLastName, this.currentBatterLinkable) } }
				);
			}
			tables.push( { tag: "table", cellpadding: "0", cellspacing: "0", className: "cnngreylnks00", children: { tag: "tbody", children: { tag: "tr", children: tds } } } );
		}
		// home runs
		var homeRunsData = this.buildGenericHomeRunsData();
		if (homeRunsData !== null) {
			tables.push( { tag: "table", cellpadding: "0", cellspacing: "0", className: "cnnTblHRList", height: "28", children: { tag: "tbody", children: { tag: "tr", children: homeRunsData } } } );
		}
	}
	return tables;
};

// return a $E data structure to represent the final html
Contest.prototype.buildFinalData = function() {
	return this.buildGenericBoxscoreData(
		this.buildFinalGameStateData(),
		this.buildGenericLinescoreData(),
		this.buildFinalBelowLinescoreData()
	);
};

// return a single TD or two in case of double header
Contest.prototype.buildFinalGameStateData = function() {
    var stateTDs = [];
	var gameNumberData = this.buildGameNumberData();
    if (this.status == Contest.STATUS_POSTPONED) {
        stateTDs.push({ tag: "td", align: "left", children: { tag: "strong", children: "POSTPONED" } });
    }
    else if (this.status == Contest.STATUS_SUSPENDED) {
        stateTDs.push({ tag: "td", align: "left", children: { tag: "strong", children: "SUSPENDED" } });
    }
    else
    {
	    stateTDs.push({ tag: "td", align: "left", children: { tag: "strong", children: "FINAL" + (this.inning > 9 ? " - " + this.inning + " Innings" : "") } });
    }
    if (gameNumberData !== null) {
        return [
            gameNumberData,
            stateTDs
        ];
    } else {
        return stateTDs;
    }
};

// return a series of TDs (w/l/s pitcher + homeruns)
Contest.prototype.buildFinalBelowLinescoreData = function() {
	var tables = [];
	var tds = [];
	// w/l/s pitchers
	var winningPitcherId = parseInt(this.winningPitcherId, 10);
	if (!isNaN(winningPitcherId) && winningPitcherId > 0) {
		tds.push({
				tag: "td", valign: "top", align: "left", className: "cnnstyllpargt5", children: [
					"W: ",
					{ tag: "strong", children: this.buildLinkedPlayer(this.winningPitcherId, this.winningPitcherLastName, this.winningPitcherLinkable) },
					" (" + this.winningPitcherWins + "-" + this.winningPitcherLosses + ")"
				]
		});
	}
	var losingPitcherId = parseInt(this.losingPitcherId, 10);
	if (!isNaN(losingPitcherId) && losingPitcherId > 0) {
		tds.push({
				tag: "td", valign: "top", align: "left", className: "cnnstyllpargt5", children: [
					"L: ",
					{ tag: "strong", children: this.buildLinkedPlayer(this.losingPitcherId, this.losingPitcherLastName, this.losingPitcherLinkable) },
					" (" + this.losingPitcherWins + "-" + this.losingPitcherLosses + ")"
				]
		});
	}
	var savingPitcherId = parseInt(this.savingPitcherId, 10);
	if (!isNaN(savingPitcherId) && savingPitcherId > 0) {
		tds.push({
				tag: "td", valign: "top", align: "left", className: "cnnstyllpargt5", children: [
					"S: ",
					{ tag: "strong", children: this.buildLinkedPlayer(this.savingPitcherId, this.savingPitcherLastName, this.savingPitcherLinkable) },
					" (" + this.savingPitcherSaves + ")"
				]
		});
	}
	if (tds.length > 0) {
		tables.push({
			tag: "table",
			className: "cnngreylnks00",
			cellpadding: "0",
			cellspacing: "0",
			children: { tag: "tbody", children: { tag: "tr", children: tds } }
		});
	}
	// home runs
	var homeRunsData = this.buildGenericHomeRunsData();
	if (homeRunsData !== null && homeRunsData.length) {
		tables.push({ 
			tag: "table",
			cellpadding: "0",
			cellspacing: "0",
			className: "cnnTblHRList",
			height: "28",
			children: { tag: "tbody", children: { tag: "tr", children: homeRunsData } }
		});
	}
	return tables;
};

Contest.prototype.buildGenericBoxscoreData = function(gameStateData, lineScoreData, belowLinescoreData) {
	return [
		{
			tag: "div", className: "cnnUnScBx", children: [ 
				{ // game state
					tag: "table", height: "20", cellpadding: "0", cellspacing: "0", align: "left", children: {
						tag: "tbody", children: {
							tag: "tr", children: gameStateData
						}
					}
				},
				{ // venue name
					tag: "table", height: "20", cellpadding: "0", cellspacing: "0", align: "right", children: {
						tag: "tbody", children: {
							tag: "tr", align: "right", children: {
								tag: "td", align: "right", children: this.venueName
							}
						}
					}
				}
			]
		},
		{
			tag: "div", children: {
				tag: "table", className: "cnnBoxofRec", width: "390", children: {
					tag: "tbody", children: lineScoreData
				}
			}
		},
		{
			tag: "div", className: "cnnStatbxx", children: belowLinescoreData
		},
		{ // game links
			tag: "div", children: {
				tag: "table", className: "cnngreylnks", border: "0", cellpadding: "0", cellspacing: "0", children: {
					tag: "tbody", children: {
						tag: "tr", children: this.buildGameLinksData()
					}
				}
			}
		}
	];
};

// three TRs
Contest.prototype.buildGenericLinescoreData = function() {
	var startInning = 1, endInning = 9;
	var maxInning = this.homeTeam.innings.length > this.visitorTeam.innings.length ? this.homeTeam.innings.length : this.visitorTeam.innings.length;
	if (maxInning > 9) {
		startInning = maxInning - 8;
		endInning = maxInning;
	}
	var headerTDs = [
		{ tag: "td", className: "cnnNoBoLR" },
		{ tag: "td", className: "cnnNoBoL" }
	];
	var visitorTDs = [
		{ 
			tag: "td", className: "cnnlitlogoholder", children: {
				tag: "a", href: this.createTeamUrl(this.visitorTeam.urlName), children: {
					tag: "img", src: this.createSmallTeamLogoUrl(this.visitorTeam.urlName) 
				}
			}
		},
		{
			tag: "td", className: "cnnTeamoNamo", children: [
				{ tag: "a", href: this.createTeamUrl(this.visitorTeam.urlName), children: this.visitorTeam.shortName },
				{ tag: "div", children: "(" + this.visitorTeam.wins + "-" + this.visitorTeam.losses + ")" }
			]
		}
	];
	var homeTDs = [
		{ 
			tag: "td", className: "cnnlitlogoholder", children: {
				tag: "a", href: this.createTeamUrl(this.homeTeam.urlName), children: {
					tag: "img", src: this.createSmallTeamLogoUrl(this.homeTeam.urlName) 
				}
			}
		},
		{
			tag: "td", className: "cnnTeamoNamo", children: [
				{ tag: "a", href: this.createTeamUrl(this.homeTeam.urlName), children: this.homeTeam.shortName },
				{ tag: "div", children: "(" + this.homeTeam.wins + "-" + this.homeTeam.losses + ")" }
			]
		}
	];
	// append the inning boxes
	for (var inning = startInning; inning <= endInning; inning++) {
		headerTDs.push({ tag: "td", children: ""+inning });
		visitorTDs.push({ tag: "td", width: "21", children: this.visitorTeam.innings.length >= inning ? this.visitorTeam.innings[inning - 1].score : "" });
		homeTDs.push({ tag: "td", width: "21", children: this.homeTeam.innings.length >= inning ? this.homeTeam.innings[inning - 1].score : "" });
	}
	// append the R/H/E boxes
	headerTDs.push(
		{ tag: "td", children: "R" },
		{ tag: "td", children: "H" },
		{ tag: "td", className: "cnnatendrow", children: "E" }
	);
	// change score class to cnnBoldScrRR if it's new
	var visitorScoreNew = this._scoreboard._contestScoreCache &&
		this._scoreboard._contestScoreCache[this.id] && 
		this._scoreboard._contestScoreCache[this.id].visitor &&
		this._scoreboard._contestScoreCache[this.id].visitor != this.visitorTeam.runs;
	var visitorScoreId = "vs_" + this.id;
	visitorTDs.push(
		{ tag: "td", id: visitorScoreId, className: (visitorScoreNew ? "cnnBoldScrRR" : "cnnBoldScrRB"), width: "21", children: this.visitorTeam.runs },
		{ tag: "td", className: "cnnBoldScrH1", width: "21", children: this.visitorTeam.hits },
		{ tag: "td", className: "cnnBoldScrE1", width: "21", children: this.visitorTeam.errors }
	);
	if (visitorScoreNew) {
		window.setTimeout(this.createScoreResetFunction(visitorScoreId, "cnnBoldScrRB"), Contest.NEW_SCORE_DELAY);
	}
	var homeScoreNew = this._scoreboard._contestScoreCache &&
		this._scoreboard._contestScoreCache[this.id] && 
		this._scoreboard._contestScoreCache[this.id].home &&
		this._scoreboard._contestScoreCache[this.id].home != this.homeTeam.runs;
	var homeScoreId = "hs_" + this.id;
	homeTDs.push(
		{ tag: "td", className: (homeScoreNew ? "cnnBoldScrRR" : "cnnBoldScrRB"), children: this.homeTeam.runs },
		{ tag: "td", className: "cnnBoldScrH1", children: this.homeTeam.hits },
		{ tag: "td", className: "cnnBoldScrE1", children: this.homeTeam.errors }
	);
	if (homeScoreNew) {
		window.setTimeout(this.createScoreResetFunction(homeScoreId, "cnnBoldScrRB"), Contest.NEW_SCORE_DELAY);
	}
	// build the final content
	return [
		{ tag: "tr", className: "cnnBoxofRecRow1", children: headerTDs },
		{ tag: "tr", className: "cnnBoxofRecRow2", children: visitorTDs },
		{ tag: "tr", className: "cnnBoxofRecRow3", children: homeTDs }
	];
};

Contest.prototype.createScoreResetFunction = function(scoreElem, className) {
	return function() {
		scoreElem = $(scoreElem);
		if (scoreElem) {
			scoreElem.className = className;
		}
	};
};

Contest.prototype.buildGenericHomeRunsData = function() {
	var homeHRs = this.homeTeam.homeRuns && this.homeTeam.homeRuns.length && this.homeTeam.homeRuns.length > 0;
	var visitorHRs = this.visitorTeam.homeRuns && this.visitorTeam.homeRuns.length && this.visitorTeam.homeRuns.length > 0;
	if (homeHRs || visitorHRs) {
		var tds = [ { tag: "td", valign: "top", align: "left", children: "HR:\u00a0" } ];
		// each of these get used twice below, and js scope is weird.
		var teamHRData, hrs, lastId, totalPlayerHRs, playerSeasonHRs, i, hr, insideHomeHRs, insideVisitHRs, homeLastId, visitLastId, homeTotalPlayerHRs, visitTotalPlayerHRs, homePlayerSeasonHRs, visitPlayerSeasonHRs, nextPlayerId;
		if (homeHRs && visitorHRs) {
			teamHRData = [ this.visitorTeam.abbr + " - " ];
			insideVisitHRs = this.visitorTeam.homeRuns;
			insideVisitHRs.sort(Contest.homeRunOrder);
			visitLastId = insideVisitHRs[0].playerId;
			visitTotalPlayerHRs = 0; playerSeasonHRs = "";
            insideHomeHRs = this.homeTeam.homeRuns;
            insideHomeHRs.sort(Contest.homeRunOrder);
            homeLastId = insideHomeHRs[0].playerId;
            homeTotalPlayerHRs = 0; homePlayerSeasonHRs = "";
			for (i=0; i < insideVisitHRs.length; i++) {
				hr = insideVisitHRs[i];
                if ( i == insideVisitHRs.length - 1)
                {
                    nextPlayerId = "";
                }
                else
                {
                    nextPlayerId = insideVisitHRs[i+1].playerId;
                }
				visitTotalPlayerHRs++;
                if (hr.playerId != nextPlayerId) {
                    playerSeasonHRs += hr.playerSeasonHRs;
                }
				if (i == insideVisitHRs.length - 1 || insideVisitHRs[i+1].playerId != visitLastId) {
					teamHRData.push( 
						// TODO: linkable hacked
						{ tag: "strong", children: this.buildLinkedPlayer(hr.playerId, hr.playerLastName, MLBScoreboard.TRUE) },
						(visitTotalPlayerHRs > 1 ? " " + visitTotalPlayerHRs : "") + " (" + playerSeasonHRs + ") "
					);
					visitTotalPlayerHRs = 0;
					playerSeasonHRs = "";
				} else {
                    if (hr.playerId != nextPlayerId) {
                        playerSeasonHRs += ",";
                    }
				}
				visitLastId = hr.playerId;
			}
			teamHRData.push( ", " + this.homeTeam.abbr + " - " );
            for (i=0; i < insideHomeHRs.length; i++) {
                hr = insideHomeHRs[i];
                if ( i == insideHomeHRs.length - 1)
                {
                    nextPlayerId = "";
                }
                else
                {
                    nextPlayerId = insideHomeHRs[i+1].playerId;
                }
                homeTotalPlayerHRs++;
                if (hr.playerId != nextPlayerId) {
                    playerSeasonHRs += hr.playerSeasonHRs;
                }
                if (i == insideHomeHRs.length - 1 || insideHomeHRs[i+1].playerId != homeLastId) {
                    teamHRData.push(
                        // TODO: linkable hacked
                        { tag: "strong", children: this.buildLinkedPlayer(hr.playerId, hr.playerLastName, MLBScoreboard.TRUE) },
                        (homeTotalPlayerHRs > 1 ? " " + homeTotalPlayerHRs : "") + " (" + playerSeasonHRs + ") "
                    );
                    homeTotalPlayerHRs = 0;
                    playerSeasonHRs = "";
                } else {
                    if (hr.playerId != nextPlayerId) {
                        playerSeasonHRs += ",";
                    }
                }
                homeLastId = hr.playerId;
            }
			tds.push( { tag: "td", valign: "top", align: "left", children: teamHRData } );
		}
        else
        {
		  if (visitorHRs) {
			teamHRData = [ this.visitorTeam.abbr + " - " ];
			hrs = this.visitorTeam.homeRuns;
			hrs.sort(Contest.homeRunOrder);
			lastId = hrs[0].playerId;
			totalPlayerHRs = 0; playerSeasonHRs = "";
			for (i=0; i < hrs.length; i++) {
				hr = hrs[i];
                if ( i == hrs.length - 1)
                {
                    nextPlayerId = "";
                }
                else
                {
                    nextPlayerId = hrs[i+1].playerId;
                }
				totalPlayerHRs++;
                if (hr.playerId != nextPlayerId) {
				    playerSeasonHRs += hr.playerSeasonHRs;
                }
				if (i == hrs.length - 1 || hrs[i+1].playerId != lastId) {
					teamHRData.push( 
						// TODO: linkable hacked
						{ tag: "strong", children: this.buildLinkedPlayer(hr.playerId, hr.playerLastName, MLBScoreboard.TRUE) },
						(totalPlayerHRs > 1 ? " " + totalPlayerHRs : "") + " (" + playerSeasonHRs + ") "
					);
					totalPlayerHRs = 0;
					playerSeasonHRs = "";
				} else {
                    if (hr.playerId != nextPlayerId) {
					    playerSeasonHRs += ",";
                    }
				}
				lastId = hr.playerId;
			}
			tds.push( { tag: "td", valign: "top", align: "left", children: teamHRData } );
		  }
          if (homeHRs) {
            teamHRData = [ this.homeTeam.abbr + " - " ];
            hrs = this.homeTeam.homeRuns;
            hrs.sort(Contest.homeRunOrder);
            lastId = hrs[0].playerId;
            totalPlayerHRs = 0; playerSeasonHRs = "";
            for (i=0; i < hrs.length; i++) {
                hr = hrs[i];
                if ( i == hrs.length - 1)
                {
                    nextPlayerId = "";
                }
                else
                {
                    nextPlayerId = hrs[i+1].playerId;
                }
                totalPlayerHRs++;
                if (hr.playerId != nextPlayerId) {
                    playerSeasonHRs += hr.playerSeasonHRs;
                }
                if (i == hrs.length - 1 || hrs[i+1].playerId != lastId) {
                    teamHRData.push(
                        // TODO: linkable hacked
                        { tag: "strong", children: this.buildLinkedPlayer(hr.playerId, hr.playerLastName, MLBScoreboard.TRUE) },
                        (totalPlayerHRs > 1 ? " " + totalPlayerHRs : "") + " (" + playerSeasonHRs + ") "
                    );
                    totalPlayerHRs = 0;
                    playerSeasonHRs = "";
                } else {
                    if (hr.playerId != nextPlayerId) {
                        playerSeasonHRs += ",";
                    }
                }
                lastId = hr.playerId;
            }
            tds.push( { tag: "td", valign: "top", align: "left", children: teamHRData } );
          }
        }
		return tds;
	} else {
		return null;
	}
};

// a utility method for inheritance
Function.prototype.inherits = function(superclass) {
	var x = function() {};
	x.prototype = superclass.prototype;
	this.prototype = new x();
};

function MainMLBScoreboard(scoreboardDatUrl) {
	// call superclass constructor
	MLBScoreboard.call(this, scoreboardDatUrl, "boxscore_", Contest.prototype.toDOM);
	var self = this;
	// create a scriptaculous Sortable for each of the league divs
	var leagueIds = ["AL", "NL", "IL", "GPFT", "CAC"];
	for (var i=0; i < leagueIds.length; i++) {
		var leagueDiv = $(leagueIds[i]);
		if (leagueDiv) {
			Sortable.create(
				leagueDiv,
				{
					//tag: "div",
					//only: "cnnbox4bh",
					elements: leagueDiv.select(".cnnbox4bh"),
					scroll: window,
					onUpdate: self.createSortableOnUpdateHandler(leagueIds[i], leagueDiv)
				}
			);
			if (leagueDiv.select) {
				leagueDiv.select(".cnnbox4bh").each(function(boxDiv) {
						boxDiv.style.cursor = "move";
				});
			}
			var sequenceStr = self.readCookie("myGamesList_" + leagueIds[i]);
			if (sequenceStr) {
				var sequence = sequenceStr.split(",");
				Sortable.setSequence(leagueDiv, sequence);
			}
		}
	}
}

MainMLBScoreboard.inherits(MLBScoreboard);

MainMLBScoreboard.prototype.createSortableOnUpdateHandler = function (id, sortableDiv) {
	var self = this;
	return function(container) {
		//TODO: make cookie life a constant
		self.createCookie("myGamesList_" + id, Sortable.sequence(sortableDiv).join(","), 1, window.location.pathname);
	};
};

function MiniMLBScoreboard(scoreboardDatUrl) {
	MLBScoreboard.call(this, scoreboardDatUrl, "minibox_", Contest.prototype.toMiniDOM);
}

MiniMLBScoreboard.inherits(MLBScoreboard);

function MLBGameFlashPage(contestId, gameDateUrl, type) {
	this._contestId = contestId;
	this._gameDateUrl = gameDateUrl;
	this._type = type;
	this._latestScore = 0;
	this._updateInterval = 30;
	this.updaters = {};
	this._urlPrefix = "/baseball/mlb/gameflash/" + this._gameDateUrl + "/" + this._contestId;
	
	var lastInning = $("lastInning");
	if (lastInning) {
		var lastInningScore = parseInt(lastInning.innerHTML, 10);
		if (!isNaN(lastInningScore)) {
			this._latestScore = lastInningScore;
		}
	}
	
	var self = this;
	
	if (type != "preview" && type != "recap") {
		this.updaters.linescore = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "linescore_" + self._contestId },
					self._urlPrefix + "_linescore.dat",
					{
						method: "get",
						evalJS: false,
						evalJSON: false,
						onComplete: function(xhr) {
							// get contest score from latest inning
							var lastInning = $("lastInning");
							if (lastInning) {
								var newScore = parseInt(lastInning.innerHTML, 10);
								// is the new score different?
								if (!isNaN(newScore) && newScore != self._latestScore && newScore != 0) {
									lastInning.className = "cnnTeamBlkBoxsScore";
									window.setTimeout(function () { lastInning.className = "cnnTeamBlkBoxs"; }, Contest.NEW_SCORE_DELAY);
								}
								self._latestScore = newScore;
							}
						}
					}
				);
			},
			self._updateInterval
		);
	}
	
	switch (type) {
	case "boxscore":
		self.updaters.pxp_inning = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "pxp_inning_" + self._contestId },
					self._urlPrefix + "_pxp_feed.dat",
					MLBGameFlashPage.UPDATER_OPTIONS
				);
			},
			self._updateInterval
		);
		self.updaters.fieldview = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "fieldview_" + self._contestId },
					self._urlPrefix + "_fieldview.dat",
					MLBGameFlashPage.UPDATER_OPTIONS
				);
			},
			self._updateInterval
		);
		self.updaters.boxscore = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "boxscore_" + self._contestId },
					self._urlPrefix + "_boxscore.dat",
					MLBGameFlashPage.UPDATER_OPTIONS
				);
			},
			self._updateInterval
		);
		break;
	case "scoring":
		self.updaters.scoringsummary = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "scoringsummary_" + self._contestId },
					self._urlPrefix + "_scoring.dat",
					MLBGameFlashPage.UPDATER_OPTIONS
				);
			},
			self._updateInterval
		);
		break;
	case "playbyplay":
		self.updaters.pxp = new PeriodicalExecuter(
			function (pe) {
				var u = new Ajax.Updater( 
					{ success: "pxp_" + self._contestId },
					self._urlPrefix + "_playbyplay.dat",
					MLBGameFlashPage.UPDATER_OPTIONS
				);
			},
			self._updateInterval
		);
		break;
	}
}

MLBGameFlashPage.UPDATER_OPTIONS = { 
	method: "get",
	evalJS: false,
	evalJSON: false
};

/**
 * document.createElement convenience wrapper
 *
 * The data parameter is an object that must have the "tag" key, containing
 * a string with the tagname of the element to create.  It can optionally have
 * a "children" key which can be: a string, "data" object, or an array of "data"
 * objects to append to this element as children.  Any other key is taken as an
 * attribute to be applied to this tag.
 *
 * Release homepage:
 * http://www.arantius.com/article/dollar+e
 *
 * Available under an MIT license:
 * http://www.opensource.org/licenses/mit-license.php
 *
 * @param {Object} data The data representing the element to create
 * @return {Element} The element created.
 */
function $E(data) {
	var el;
	if ('string'==typeof data) {
		el=document.createTextNode(data);
	} else {
		//create the element
		el=document.createElement(data.tag);
		delete(data.tag);
		if (data.className) {
			el.className = data.className;
			delete(data.className);
		}

		//append the children
		if ('undefined'!=typeof data.children) {
			if ('string'==typeof data.children ||
				'undefined'==typeof data.children.length) {
				//strings and single elements
				el.appendChild($E(data.children));
			} else {
				//arrays of elements
				for (var i=0, child=null; 'undefined'!=typeof (child=data.children[i]); i++) {
					el.appendChild($E(child));
				}
			}
			delete(data.children);
		}

		//any other data is attributes
		for (var attr in data) if (data.hasOwnProperty(attr)) {
			el.setAttribute(attr, data[attr]);
		}
	}

	return el;
}


