var ModelClass = function() {

	// ================
	// = DATA OBJECTS =
	// ================
	 
	this.App = {
		config: {
			brand: CSL.url_vars["brand"] || "NBA", // Default to NBA branding if none is passed in
			start_screen: CSL.url_vars["screen"] || "stats",
			sim: CSL.url_vars["sim"] || false,
			live: CSL.url_vars["live"] || false,
			starting_feed_timestamp: parseInt(CSL.url_vars["start"], 10) || 0,
			timestamp_diff: 0,
			num_plays_to_animate: 1,
			num_plays_to_show: 10,
			main_auto_rotate_freq: appConfig['main_auto_rotate_freq'] || 10000
		},
		state: {
			screen: "init",
			auto_timer_id: 0, // setInterval timer id for Main Nav auto-rotate
			feed_timestamp: 0, // For explicitly setting the timestamp for Simulation files
			stat_animations_on: true, // Show animations on Stats & Boxscore screen when value is updated
			htm_stats_auto: false,
			vtm_stats_auto: false,
			htm_statset: "basic",
			vtm_statset: "basic",
			sc_prd_shown: 1,
			sc_timeline_opening: false, // Shotchart timeline in process of opening
			sc_shots_to_show: 10,
			shotchart_filters: {
				team: false,
				player: false,
				shot: false
			},
			pbp_filters: {
				team: false,
				player: false
			},
			pbp_prev_qtr_clicked: false,
			pbp_prd_shown: 0,
			postgame_retries: 0,
			sb_ots_shown: 0 //keeps track of how many OTs are shown in the jumbotron quarter-by-quarter scoreboard
		}
	};
	
	
	this.Game = {
		config: {
			game_code: CSL.url_vars["gamecode"],
			game_date: CSL.url_vars["gamecode"].split("/")[0] || "",
			vtm: CSL.url_vars["gamecode"].slice(9, 12),
			htm: CSL.url_vars["gamecode"].slice(12, 15)
		},
		state: "", //"pregame", "live", "halftime", "postgame"
		overtime: false,
		data: {
			officials: []
		}
	};

	
	this.Screens = {
		core: 		{feed_names: ["boxscore", "simplescoreboard", "nbaticker"]},
		shotchart: 	{feed_names: ["shotchart"], has_tab: true},
		playbyplay: {feed_names: ["playbyplay"], has_tab: true},
		stats: 		{feed_names: [], has_tab: true},
		boxscore: 	{feed_names: [], has_tab: true},
		pregame: 	{feed_names: []},
		halftime: 	{feed_names: []},
		postgame: 	{feed_names: []},
		interact: 	{feed_names: []}
	};

	
	this.FeedsClass = function() {
		this.boxscore = {
			url: CSL.getUrlForFeed("boxscore"),
			callback: CSL.boxscoreCallback,
			freq: appConfig['boxscore_update_freq'] || 10000
		};
		this.playbyplay = {
			url: CSL.getUrlForFeed("playbyplay"),
			callback: CSL.playbyplayCallback,
			freq: appConfig['playbyplay_update_freq'] || 10000,
			first_request: true
		};
		this.shotchart = {
			url: CSL.getUrlForFeed("shotchart"),
			callback: CSL.shotchartCallback,
			freq: appConfig['shotchart_update_freq'] || 10000,
			first_request: true
		};
		this.simplescoreboard = {
			url: CSL.getUrlForFeed("simplescoreboard"),
			callback: CSL.simplescoreboardCallback,
			freq: appConfig['simplescoreboard_update_freq'] || 19000
		};
		this.nbaticker = {
			url: CSL.getUrlForFeed("nbaticker"),
			callback: CSL.nbatickerCallback,
			freq: appConfig['nbaticker_update_freq'] || 60000
		};
		this.pregame = {
			url: CSL.getUrlForFeed("pregame"),
			callback: CSL.pregameCallback
			// No 'freq' property since it's only called once
		};
		this.broadcaster = {
			url: CSL.getUrlForFeed("broadcaster"),
			callback: CSL.broadcasterCallback
			// No 'freq' property since it's only called once
		};
	};	
	
	
	this.TeamsClass = function() {
		this.vtm = {
			config: teamConfigs[Model.Game.config.vtm],
			data: {
				score: {}
			},
			active: []
		};
		
		this.htm = {
			config: teamConfigs[Model.Game.config.htm],
			data: {
				score: {}
			},
			active: []
		};
	};
	
	this.Leaders = {
		vtm: { 
			assists: [], 
			points: [], 
			rebounds: [], 
			blocks: []
		},
		htm: {
			assists: [], 
			points: [], 
			rebounds: [], 
			blocks: []
		}
	};
	
	this.Players = {
		vtm: {},
		htm: {},
		vtm_codes: [],
		htm_codes: [],
		vtm_count: 0,
		htm_count: 0
	};

	// Used for lookups since Shotchart Feed sometimes only has player numbers
	this.PlayerNumberLookup = {
		vtm: [],
		htm: []
	};
	
	// Used for lookups since Play By Play Feed only has player codes
	this.PlayerCodeLookup = {
		vtm: {},
		htm: {}
	};
	
	this.Plays = {
		events: {
			"prd1": [],
			"prd2": [],
			"prd3": [],
			"prd4": [],
			"prd5": [],
			"prd6": [],
			"prd7": [],
			"prd8": []
		}, // Master list of shot events
		num_events: {
			"prd1": 0,
			"prd2": 0,
			"prd3": 0,
			"prd4": 0,
			"prd5": 0,
			"prd6": 0,
			"prd7": 0,
			"prd8": 0
		},
		queue: [], // Queue of "new" plays to animate prepending
		last_index_shown: -1
	};

	this.Shots = {
		events: {
			"prd1": [],
			"prd2": [],
			"prd3": [],
			"prd4": [],
			"prd5": [],
			"prd6": [],
			"prd7": [],
			"prd8": []
		}, // Master list of shot events
		num_events: {
			"prd1": 0,
			"prd2": 0,
			"prd3": 0,
			"prd4": 0,
			"prd5": 0,
			"prd6": 0,
			"prd7": 0,
			"prd8": 0
		},
		markers: [], // Markers shown on court
		tooltip_queue: [], // Queue of "new" shots to show tooltips for
		timeline_queue: []		
	};

	this.LeagueScoreboard = {
		ticker_text: "",
		date: "",
		games: [],
		display: {
			position: 0,
			total_width: 0,
			viewable_width: 0,
			extra_width: 0
		}
	};

	this.StatSets = {
		"basic": ["points", "rebounds", "assists", "fouls"],
		"shooting": ["fgm", "threepm", "ftm", "blocks_against"],
		"rebounds": ["off_reb", "def_reb", "rebounds"],
		"x-factor": ["steals", "blocks", "turnovers", "plusminus"],
		"leaders": ["points", "rebounds", "assists", "blocks"]
	};




	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// =========================
	// = AJAX REQUEST HANDLING =
	// =========================

	// Iterates through necessary feeds and makes AJAX requests for each one
	// NOTE: "screen" parameter is optional; If absent, uses App's current screen
	this.getDataForScreen = function(screen, is_init) {
		is_init = is_init || false;
		screen = screen || this.App.state.screen;

		var feed_names_for_screen = this.Screens[screen].feed_names;
		
		if (!feed_names_for_screen) {
			return;
		}
		else if (feed_names_for_screen.length > 1) {
			for (var i=0, num_feeds = feed_names_for_screen.length; i < num_feeds; i++) {
				Model.getDataForFeed(feed_names_for_screen[i], is_init);
			}
		}
		else if (feed_names_for_screen.length == 1) {
			Model.getDataForFeed(feed_names_for_screen, is_init);
		}
	};


	// Makes AJAX request for feed and calls appropriate feed-specific callback
	this.getDataForFeed = function(feed_name, is_init, period) {
		is_init = is_init || false;
		period = period || false; // When set, used for getting previous periods
		var callback_ref = this.Feeds[feed_name].callback;
		
		this.Feeds[feed_name].url = CSL.getUrlForFeed(feed_name, period);
		
		$j.ajax({
	        url: this.Feeds[feed_name].url,
			dataType: "xml",
			//cache: false,
			success: function(xml) {
				if (is_init && feed_name == "simplescoreboard") {
					CSL.initSimplescoreboardCallback(xml);
				}
				else if (is_init && feed_name == "boxscore") {
					CSL.initBoxscoreCallback(xml);
				}
				else if (period && feed_name == "shotchart") {
					CSL.prevShotchartCallback(xml, period);
				}
				else if (period && feed_name == "playbyplay") {
					CSL.prevPlaybyplayCallback(xml, period);
				}
				else if (feed_name == "broadcaster") {
					CSL.broadcasterCallback(xml);
				}
				else {
					callback_ref(xml);
				}
			},
			error: function(error) {
				if (typeof console !== undefined && DEBUG) { 
					console.warn("*** ERROR retrieving feed: "+ Model.Feeds[feed_name].url + " ( "+ error.status + " " + error.statusText + " )");
				}
				
				callback_ref(false);
			}
		});
	};
	





	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// ===================
	// = FEED PROCESSING =
	// ===================
	
	// ============
	// = BROADCASTER =
	// ============

  this.processBroadcaster = function(xml) {    
		var game_data = this.Game.data;
    var $broadcast_xml = $j(xml);
    var $game_node = $broadcast_xml.find("broadcasters");
  
		this.processBroadcasterForTeams("vtm", $game_node);
		this.processBroadcasterForTeams("htm", $game_node);
			View.populateBroadcasters();
  };

  this.processBroadcasterForTeams = function(team, $game_node) {
    var team_data = this.Teams[team].data;
    var broadcaster = $game_node.attr(team).split("|");
    team_data.local_tv_broadcaster = broadcaster[0];
    team_data.local_radio_broadcaster = broadcaster[1];
  };
	
	// ============
	// = BOXSCORE =
	// ============
	
	// Parse & populate data objects with Boxscore XML
	this.processBoxscoreFeed = function(xml, is_init) {
		is_init = is_init || false;
		var $boxscore_xml = $j(xml);
		
		// GAME
		//
		var game_arena_attr, $officials_node, officials_nm_attr, temp_officials, temp_official;
		var game_data = this.Game.data;
		var $game_node = $boxscore_xml.find("game");
		
		game_data.status = $game_node.attr("gstat");
		game_data.status_text = $game_node.attr("gstattxt");
		game_data.period = parseInt($game_node.attr("prd"), 10);
		game_data.clock = $game_node.attr("clk");
		game_data.vid_link = $game_node.attr("vid");

		if (is_init) { // Only parse once
			game_arena_attr = $game_node.attr("arn").split("|");
			game_data.arena_name = game_arena_attr[1];
			game_data.arena_city_state = game_arena_attr[2];
			game_data.arena_attendance = game_arena_attr[3];
			game_data.arena_capacity = game_arena_attr[3];
			
			game_data.national_tv_broadcaster = $game_node.attr("nbrd");
			game_data.start_time_eastern = $game_node.attr("timet");
			game_data.tickets_link = $game_node.attr("tkt");

			// OFFICIALS
			//
			$officials_node = $game_node.children("officials");
			officials_nm_attr = $officials_node.attr("nm");
			temp_officials = officials_nm_attr.split("^");
			for (var i = 0, num_officials = temp_officials.length; i < num_officials; i++) {
				temp_official = temp_officials[i];
				game_data.officials[i] = { name: temp_official.split("|")[0], number: temp_official.split("|")[1] };
			}
		}
		
		// TEAMS
		//
		this.processBoxscoreFeedForTeam("vtm", $game_node, is_init);
		this.processBoxscoreFeedForTeam("htm", $game_node, is_init);
	};


	this.processBoxscoreFeedForTeam = function(team, $game_node, is_init) {
		var $team_node, team_data, team_brd_attr, team_scr_attr, team_stat_attr, team_gstat_attr, team_timout_attr, team_tfouls_attr;
		is_init = is_init || false;
		
		this.Teams[team].active = []; // Resetting the array of active players every time 
		
		$team_node = $game_node.children(team);
		team_data = this.Teams[team].data;

		if (is_init) {
			team_data.wins = $team_node.attr("rcd").split("/")[0];
			team_data.losses = $team_node.attr("rcd").split("/")[1];
			team_data.conf_standings = $team_node.attr("std").split("|")[0]; 
			team_data.div_standings = $team_node.attr("std").split("|")[1]; 

			team_brd_attr = $team_node.attr("brd").split("|");
			team_data.local_tv_broadcaster = team_brd_attr[0];
			team_data.local_radio_broadcaster = team_brd_attr[1];
		}

		team_scr_attr = $team_node.attr("scr").split("|");
		team_data.score.qtr1 = team_scr_attr[0];
		team_data.score.qtr2 = team_scr_attr[1];
		team_data.score.qtr3 = team_scr_attr[2];
		team_data.score.qtr4 = team_scr_attr[3];
		team_data.score.ot1 = team_scr_attr[4];
		team_data.score.ot2 = team_scr_attr[5];
		team_data.score.ot3 = team_scr_attr[6];
		team_data.score.ot4 = team_scr_attr[7];
		team_data.score.total = team_scr_attr[8];
		
		team_stat_attr = $team_node.attr("stat").split("|");
		team_data.min_played = team_stat_attr[0];
		team_data.fg_ma = team_stat_attr[1]; // Delimited by "-"
		team_data.threep_ma = team_stat_attr[2]; // Delimited by "-"
		team_data.ft_ma = team_stat_attr[3]; // Delimited by "-"
		team_data.off_reb = team_stat_attr[4];
		team_data.def_reb = team_stat_attr[5];
		team_data.total_reb = team_stat_attr[6];
		team_data.assists = team_stat_attr[7];
		team_data.pfouls = team_stat_attr[8];
		team_data.steals = team_stat_attr[9];
		team_data.turnovers = team_stat_attr[10];
		team_data.blocks = team_stat_attr[11];
		team_data.total_points = team_stat_attr[14];
		team_data.blocks_against = team_stat_attr[16];
		
		team_gstat_attr = $team_node.attr("gstat").split("|");
		team_data.team_reb = team_gstat_attr[0];
		team_data.fg_pct = Math.round(team_gstat_attr[1] * 100);
		team_data.threep_pct = Math.round(team_gstat_attr[2] * 100);
		team_data.ft_pct = Math.round(team_gstat_attr[3] * 100);
		team_data.turnovers = team_gstat_attr[4];
		
		team_timout_attr = $team_node.attr("timout").split("|");
		team_data.total_to = team_timout_attr[0];
		team_data.full_to = team_timout_attr[1];
		team_data.twentysec_to = team_timout_attr[2];
		
		team_tfouls_attr = $team_node.attr("tfoul").split("^");
		team_data.tfouls = team_tfouls_attr.length-1; // Only need to store number of technical fouls
		
		// Leaders
		//
		this.processLeadersAttr(team, "assists", $team_node.attr("ald"));
		this.processLeadersAttr(team, "points", $team_node.attr("pld"));
		this.processLeadersAttr(team, "rebounds", $team_node.attr("rld"));
		this.processLeadersAttr(team, "blocks", $team_node.attr("bld"));
		
		
		// Players - Parsing & Storing data
		//
		var player_nodes = $team_node.children("pl");
		var $pl_node, player_srt, player_hash, player, player_name_attr, player_stat_attr;
		var num_players = player_nodes.length;

		for (var i = 0; i < num_players; i++) {
			$pl_node = $j(player_nodes[i]);
			// player_srt = $pl_node.attr("srt"); // Not using as player hash anymore since srt values may change throughout the game
			player_id = $pl_node.attr("name").split("|")[0]; // Finding this explicitly since we're using it as the player hash
			player_hash = player_id;
			
			this.Players[team][player_hash] = this.Players[team][player_hash] || {};
			player = this.Players[team][player_hash];
			
			if (is_init) { // Only process static player data once
				this.Players[team+"_count"] = num_players;
				player_name_attr = $pl_node.attr("name").split("|");
				player.id = player_name_attr[0];
				player.code = player_name_attr[1];
				player.name = player_name_attr[2];
				player.lastname = player.name.split(", ")[0];
				player.firstname = player.name.split(", ")[1];
				player.firstlastname = (player.firstname) ? player.firstname +" "+ player.lastname : player.lastname;
				player.status = player_name_attr[3];
				player.position = player_name_attr[4].charAt(0); // Only need "G" from "G1"
				player.number = player_name_attr[5];

				this.Players[team+"_codes"].push(player.code); // Store copy of all player codes for alphabetical sorting later
			}
			
			player_stat_attr = $pl_node.attr("stat").split("|");
			player.min_played = player_stat_attr[0];
			player.fgma = player_stat_attr[1];
			player.fgm = player_stat_attr[1].split("-")[0];
			player.fga = player_stat_attr[1].split("-")[1];
			player.threepma = player_stat_attr[2];
			player.threepm = player_stat_attr[2].split("-")[0];
			player.threepa = player_stat_attr[2].split("-")[1];
			player.ftma = player_stat_attr[3];
			player.ftm = player_stat_attr[3].split("-")[0];
			player.fta = player_stat_attr[3].split("-")[1];
			player.off_reb = player_stat_attr[4];
			player.def_reb = player_stat_attr[5];
			player.rebounds = player_stat_attr[6];
			player.assists = player_stat_attr[7];
			player.fouls = player_stat_attr[8];
			player.steals = player_stat_attr[9];
			player.turnovers = player_stat_attr[10];
			player.blocks = player_stat_attr[11];
			player.points = player_stat_attr[14];
			player.plusminus = player_stat_attr[15];
			player.blocks_against = player_stat_attr[16];

			player.srt = player_srt;
			player.dnp = $pl_node.attr("dnp");
			player.oncrt = $pl_node.attr("oncrt");
			if (player.oncrt == "1") {
				this.Teams[team].active.push(player_hash);
			}

			if (player.number) {
				this.PlayerNumberLookup[team][player.number] = player_hash;
			}
			if (player.code) {
				this.PlayerCodeLookup[team][player.code] = player_hash;
			}
		}
	};


	this.processLeadersAttr = function(team, stat, leader_attr_val) {
		if (!leader_attr_val) return;
		
		var leaders, leader, leader_obj;
		
		this.Leaders[team][stat] = []; // Reset Leaders list for this team & stat

		leaders = leader_attr_val.split("^");
		
		for (var i = 0, num_leaders = leaders.length; i < num_leaders; i++) {
			leader = leaders[i].split("|");
			leader_obj = {};
			leader_obj.id = leader[0];
			leader_obj.code = leader[1];
			leader_obj.name = leader[2];
			leader_obj.stat_val = leader[3];
			leader_obj.lastname = leader_obj.name.split(", ")[0];
			leader_obj.firstname = leader_obj.name.split(", ")[1];
			leader_obj.firstlastname = (leader_obj.firstname) ? leader_obj.firstname +" "+ leader_obj.lastname : leader_obj.lastname;
			leader_obj.number = leader[4];

			this.Leaders[team][stat].push(leader_obj);
		}
	};




	// =============
	// = SHOTCHART =
	// =============

	// Parse & populate data objects with Shotchart XML
	this.processShotchartFeed = function(xml, period) {
		var $shotchart_xml, event_nodes, $evt_node, evt_id, evt, shot_type, event_pid_attr, old_evt, fixed_xy, seconds_pixel_ratio;
		period = period || Model.Game.data.period;
		var app_state = Model.App.state;
		
		$shotchart_xml = $j(xml);
		this.Shots.clock = $shotchart_xml.find("game").attr("clk") || 0;
		event_nodes = $shotchart_xml.find("event");
		
		for (var i = 0, num_events = event_nodes.length; i < num_events; i++) {
			$evt_node = $j(event_nodes[i]);
			evt_id = parseInt($evt_node.attr("id"), 10); // NOTE: "id" attr matches with Play by Play "eventid" attr's
			
			// Checks if this event is newer than last seen since new event id's are always greater
			if ((i+1) > this.Shots.num_events["prd"+period]) {
				evt = {};

				evt.id = evt_id;
				evt.action = $evt_node.attr("act");
				shot_type = this.ShotTypeLookup[evt.action]
				if (shot_type) {
					evt.action_text = shot_type.action_text;
					evt.shot_group = shot_type.group; // Custom grouping
				}
				else {
					evt.action_text = "Shot";
				}
				
				// =====================================================
				// = SWAPPING X & Y SINCE DATA FEED HAS THEM BACKWARDS =
				// =====================================================

				evt.x = parseInt($evt_node.attr("y"), 10);
				evt.y = parseInt($evt_node.attr("x"), 10);
				evt.team = $evt_node.attr("tm") + "tm"; // To match convention of "vtm" and "htm" through code & feeds
				evt.period = $evt_node.attr("prd");
				
				fixed_xy = [];
				fixed_xy = Utils.convertCoordinates(evt.x, evt.y, evt.team, evt.period);
				evt.fixed_x = fixed_xy[0];
				evt.fixed_y = fixed_xy[1];
				
				evt.time = parseInt($evt_node.attr("time"), 10); // Tenths of seconds left in period (7200 == full qtr)
				evt.time_text = Utils.getDisplayTimeForDeciseconds(evt.time);
				var timeline_offset = (evt.type == 1) ? 7 : 4; // offset for middle of timeline marker
				seconds_pixel_ratio = (evt.period > 4) ? 3.8961 : 9.3506; // Account for overtimes only being 5min in length (3.8961 == 3000 / 770)
				//seconds_pixel_ratio = (evt.period > 4) ? 3.8961 : 15.5844; // RKESPH Game - Account for overtimes only being 5min in length (3.8961 == 3000 / 770)
				evt.timeline_position = 770 - (Math.round(evt.time / seconds_pixel_ratio) + timeline_offset); // 9.3506 == Ratio of quarter seconds to timeline pixels (7200 / 770)
				evt.type = $evt_node.attr("typ"); // 1 = made, 2 = missed
				// Don't store events that come through in feed that aren't "made" or "missed" shots e.g. shot clock violations (type == 5)
				if (evt.type != 1 && evt.type != 2) continue; 
				evt.result_text = (evt.type == 1) ? "Made" : "Missed";
			
				event_pid_attr = $evt_node.attr("pid").split("|");
				evt.player_code = event_pid_attr[1];
				
				/* 2011-12-17 NO */
				evt.player_name = event_pid_attr[2];
			
				evt.player_number = event_pid_attr[5];
				evt.player_firstname = evt.player_name.split(", ")[1];
				evt.player_lastname = evt.player_name.split(", ")[0];

				// Add to master Shot events list for period
				this.Shots.events["prd"+evt.period].push(evt);

				if (evt.period == Model.Game.data.period) {
					// Timeline still shows all shots, regardless of filters 
					this.Shots.timeline_queue.push(evt);
					
					if (!Model.Feeds.shotchart.first_request)
						this.Shots.markers.push(evt);

					if (app_state.screen == "shotchart") {
						this.Shots.tooltip_queue.push(evt);
					}
				}
			}
		}

		this.Shots.num_events["prd"+period] = num_events;
	};





	// ================
	// = PLAY BY PLAY =
	// ================

	// Parse & populate data objects with Play-by-Play XML
	this.processPlaybyplayFeed = function(xml, period) {
		var $playbyplay_xml, event_nodes, $evt_node, evt_id, evt;
		period = period || Model.Game.data.period;
		var plays = this.Plays;

		$playbyplay_xml = $j(xml);
		event_nodes = $playbyplay_xml.find("event");

		for (var i = 0, num_events = event_nodes.length; i < num_events; i++) {
			$evt_node = $j(event_nodes[i]);
			evt_id = parseInt($evt_node.attr("eventid"), 10); // NOTE: First eventid will typically have a value of "2"
			
			// Checks if this event is newer than last seen since new event id's are always greater
			if ((i+1) > plays.num_events["prd"+period]) {
				evt = {};

				evt.id = evt_id;
				evt.period = $evt_node.attr("prd");
				evt.time_text = $evt_node.attr("game_clock");
				evt.time = Utils.getDecisecondsForDisplayTime(evt.time_text);
				evt.htm_score = $evt_node.attr("htms");
				evt.vtm_score = $evt_node.attr("vtms");
				evt.msg_type = parseInt($evt_node.attr("msg_type"), 10);
				evt.action_type = parseInt($evt_node.attr("action_type"), 10);
				evt.player_code = $evt_node.attr("player_code");
				evt.team_code = $evt_node.attr("tm"); // Lowercase team code (slug)
				evt.team = (evt.team_code == this.Teams.htm.config.code) ? "htm" : "vtm"; // Workaround to associate event with global htm/vtm convention
				evt.raw_text = $evt_node.text();
				evt.stripped_text = evt.raw_text.split("] ")[1];
				if (!evt.stripped_text) evt.stripped_text = evt.raw_text.split(")")[1]; // Account for plays with just (TI:ME) string, but no [TEAM] string in it
				evt.stripped_text = evt.stripped_text.replace("]]>", "");				

				// Assign a custom play type to determine how to display 
				if (evt.player_code) {
					evt.type = "player";
				}
				else if (!evt.team_code) {
					evt.type = "neutral";
				}
				else {
					evt.type = "team";
				}
				
				// Add to master Play events list for period
				plays.events["prd"+evt.period].push(evt);

				// Add to queue of new Plays to show
				if (evt.period == Model.Game.data.period && i > plays.last_index_shown) {
					evt.index = i; // Add custom property so that View can update 'last_index_shown' property
					plays.queue.push(evt);
				}
			}
		}
		
		plays.num_events["prd"+period] = num_events;
	};





	// ====================
	// = SIMPLESCOREBOARD =
	// ====================

	// Parse & populate data objects with Simple Scoreboard XML
	this.processSimpleScoreboardFeed = function(xml, is_init) {
		is_init = is_init || false;
		var lsb_game, $game_node, $game_htm_node, $game_vtm_node;
		var $simplescoreboard_xml = $j(xml);

		if (is_init) {
			var games_date = $simplescoreboard_xml.find("message").attr("gdt"); // Format: YYYYMMDD
			var year = parseInt(games_date.substring(0,4), 10);
			var month = parseInt(games_date.substring(4,6), 10);
			var day = parseInt(games_date.substring(6,8), 10);
			this.LeagueScoreboard.date = new Date(year, month-1, day); // Accounting for Date Object's months starting at 0
		}
		
		var game_nodes = $simplescoreboard_xml.find("game");

		for (var i = 0, num_games = game_nodes.length; i < num_games; i++) {
			this.LeagueScoreboard.games[i] = this.LeagueScoreboard.games[i] || {};
			lsb_game = this.LeagueScoreboard.games[i];

			$game_node = $j(game_nodes[i]);
			lsb_game.game_code = $game_node.attr("gcd");
			lsb_game.status = $game_node.attr("gstat");
			lsb_game.status_text = $game_node.attr("gstattxt");
			lsb_game.period = $game_node.attr("prd");
			lsb_game.clock = $game_node.attr("clk");
			lsb_game.start_time_eastern = $game_node.attr("timet");

			lsb_game.htm = lsb_game.htm || {};
			$game_htm_node = $game_node.children("htm");
			lsb_game.htm.abbrev = $game_htm_node.attr("tm").split("|")[3];
			lsb_game.htm.score = parseInt($game_htm_node.attr("scr").split("|")[8], 10);
			lsb_game.htm.tcd = $game_htm_node.attr("tcd");

			lsb_game.vtm = lsb_game.vtm || {};
			$game_vtm_node = $game_node.children("vtm");
			lsb_game.vtm.abbrev = $game_vtm_node.attr("tm").split("|")[3];
			lsb_game.vtm.score = parseInt($game_vtm_node.attr("scr").split("|")[8], 10);
			lsb_game.vtm.tcd = $game_vtm_node.attr("tcd");
			
			// Set Game data for Pregame since Boxscore feed might not be available on init (and division/conf seed data only available in SS feed)
			if (is_init && lsb_game.game_code == this.Game.config.game_code) {
				var game_data = this.Game.data;
				game_data.status = lsb_game.status;
				game_data.start_time_eastern = $game_node.attr("timet");
				game_data.national_tv_broadcaster = $game_node.attr("nbrd");
				
				var vtm_data = this.Teams.vtm.data;
				var htm_data = this.Teams.htm.data;
				
				vtm_data.wins = $game_vtm_node.attr("rcd").split("/")[0];
				vtm_data.losses = $game_vtm_node.attr("rcd").split("/")[1];
				vtm_data.conf_standings = $game_vtm_node.attr("std").split("|")[0]; 
				vtm_data.div_standings = $game_vtm_node.attr("std").split("|")[1]; 
				vtm_data.div = $game_vtm_node.attr("div");
				vtm_data.div_seed = $game_vtm_node.attr("divSeed");
				vtm_data.conf = $game_vtm_node.attr("conf");
				vtm_data.conf_seed = $game_vtm_node.attr("seed");

				htm_data.wins = $game_htm_node.attr("rcd").split("/")[0];
				htm_data.losses = $game_htm_node.attr("rcd").split("/")[1];
				htm_data.conf_standings = $game_htm_node.attr("std").split("|")[0]; 
				htm_data.div_standings = $game_htm_node.attr("std").split("|")[1]; 
				htm_data.div = $game_htm_node.attr("div");
				htm_data.div_seed = $game_htm_node.attr("divSeed");
				htm_data.conf = $game_htm_node.attr("conf");
				htm_data.conf_seed = $game_htm_node.attr("seed");
			}
		}
	};


	// Parse & populate data object with NBA Ticker XML
	this.processNbaTickerFeed = function(xml) {
		this.LeagueScoreboard.ticker_text = $j(xml).find("item").eq(0).children("description").eq(0).text();
	};
	
	
	// Parse & populate data objects with pertitent data points from Pregame XML
	// TODO: Consider moving all pregame-related data processing to here instead of processSimpleScoreboardFeed() and processBoxscoreFeed()
	this.processPregameFeed = function(xml) {
		var game_arena_attr, $officials_node, officials_nm_attr, temp_officials, temp_official;
		var $pregame_xml = $j(xml);
		var game_data = this.Game.data;

		var $game_node = $pregame_xml.find("game[gcd="+Model.Game.config.game_code+"]");
		if (!$game_node.length) return false; // Exit out if game isn't found in pregame.xml for some reason

		game_arena_attr = $game_node.attr("arn").split("|");
		game_data.arena_name = game_arena_attr[1];
		game_data.arena_city_state = game_arena_attr[2];
		game_data.arena_attendance = game_arena_attr[3];
		game_data.arena_capacity = game_arena_attr[3];

		// OFFICIALS
		//
		$officials_node = $game_node.children("officials");
		officials_nm_attr = $officials_node.attr("nm");
		temp_officials = officials_nm_attr.split("^");
		for (var i = 0, num_officials = temp_officials.length; i < num_officials; i++) {
			temp_official = temp_officials[i];
			game_data.officials[i] = { name: temp_official.split("|")[0], number: temp_official.split("|")[1] };
		}
	};

	
	
	
	// ===========
	// = HELPERS =
	// ===========
	
	this.getPlayerByNumber = function(team, number) {
		var player_hash = this.PlayerNumberLookup[team][number];
		return this.Players[team][player_hash];
	};

	this.getPlayerByCode = function(team, code) {
		var player_hash = this.PlayerCodeLookup[team][code];
		return this.Players[team][player_hash];
	};
	
	this.ShotGroups = ["Lay-up", "Dunk", "Jump Shot", "Tip-in", "Alley Oop"];
	
	this.ShotTypeLookup = [];
	this.ShotTypeLookup[0]  = {action_text: "No Shot", group: ""};
	this.ShotTypeLookup[1]  = {action_text: "Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[2]  = {action_text: "Running Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[3]  = {action_text: "Hook Shot", group: "Jump Shot"};
	this.ShotTypeLookup[4]  = {action_text: "Tip Shot", group: "Tip-in"};
	this.ShotTypeLookup[5]  = {action_text: "Layup Shot", group: "Lay-up"};
	this.ShotTypeLookup[6]  = {action_text: "Driving Layup Shot", group: "Lay-up"};
	this.ShotTypeLookup[7]  = {action_text: "Dunk Shot", group: "Dunk"};
	this.ShotTypeLookup[8]  = {action_text: "Slam Dunk Shot", group: "Dunk"};
	this.ShotTypeLookup[9]  = {action_text: "Driving Dunk Shot", group: "Dunk"};
	this.ShotTypeLookup[40] = {action_text: "Layup", group: "Lay-up"};
	this.ShotTypeLookup[41] = {action_text: "Running Layup", group: "Lay-up"};
	this.ShotTypeLookup[42] = {action_text: "Driving Layup", group: "Lay-up"};
	this.ShotTypeLookup[43] = {action_text: "Alley Oop Layup", group: "Lay-up"};
	this.ShotTypeLookup[44] = {action_text: "Reverse Layup", group: "Lay-up"};
	this.ShotTypeLookup[45] = {action_text: "Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[46] = {action_text: "Running Jump", group: "Jump Shot"};
	this.ShotTypeLookup[47] = {action_text: "Turnaround Jump", group: "Jump Shot"};
	this.ShotTypeLookup[48] = {action_text: "Dunk", group: "Dunk"};
	this.ShotTypeLookup[49] = {action_text: "Driving Dunk", group: "Dunk"};
	this.ShotTypeLookup[50] = {action_text: "Running Dunk", group: "Dunk"};
	this.ShotTypeLookup[51] = {action_text: "Reverse Dunk", group: "Dunk"};
	this.ShotTypeLookup[52] = {action_text: "Alley Oop Dunk", group: "Dunk"};
	this.ShotTypeLookup[53] = {action_text: "Tip-In", group: "Tip-in"};
	this.ShotTypeLookup[54] = {action_text: "Running Tip-In", group: "Tip-in"};
	this.ShotTypeLookup[55] = {action_text: "Hook Shot", group: "Jump Shot"};
	this.ShotTypeLookup[56] = {action_text: "Running Hook Shot", group: "Jump Shot"};
	this.ShotTypeLookup[57] = {action_text: "Driving Hook Shot", group: "Jump Shot"};
	this.ShotTypeLookup[58] = {action_text: "Turnaround Hook Shot", group: "Jump Shot"};
	this.ShotTypeLookup[59] = {action_text: "Finger Roll", group: "Lay-up"};
	this.ShotTypeLookup[60] = {action_text: "Running Finger Roll", group: "Lay-up"};
	this.ShotTypeLookup[61] = {action_text: "Driving Finger Roll", group: "Lay-up"};
	this.ShotTypeLookup[62] = {action_text: "Turnaround Finger Roll", group: "Lay-up"};
	this.ShotTypeLookup[63] = {action_text: "Fade Away", group: "Jump Shot"};
	this.ShotTypeLookup[64] = {action_text: "Follow Up Dunk", group: "Dunk"};
	this.ShotTypeLookup[65] = {action_text: "Jump Hook", group: "Jump Shot"};
	this.ShotTypeLookup[66] = {action_text: "Jump Bank", group: "Jump Shot"};
	this.ShotTypeLookup[67] = {action_text: "Hook Bank", group: "Jump Shot"};
	this.ShotTypeLookup[71] = {action_text: "Finger Roll Layup", group: "Lay-up"};
	this.ShotTypeLookup[72] = {action_text: "Putback Layup", group: "Lay-up"};
	this.ShotTypeLookup[73] = {action_text: "Driving Reverse Layup", group: "Lay-up"};
	this.ShotTypeLookup[74] = {action_text: "Running Reverse Layup", group: "Lay-up"};
	this.ShotTypeLookup[75] = {action_text: "Driving Finger Roll Layup", group: "Lay-up"};
	this.ShotTypeLookup[76] = {action_text: "Running Finger Roll Layup", group: "Lay-up"};
	this.ShotTypeLookup[77] = {action_text: "Driving Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[78] = {action_text: "Floating Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[79] = {action_text: "Pullup Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[80] = {action_text: "Step Back Jump Shot", group: "Jump Shot"};
	this.ShotTypeLookup[81] = {action_text: "Pullup Bank Shot", group: "Jump Shot"};
	this.ShotTypeLookup[82] = {action_text: "Driving Bank Shot", group: "Jump Shot"};
	this.ShotTypeLookup[83] = {action_text: "Fade Away Bank Shot", group: "Jump Shot"};
	this.ShotTypeLookup[84] = {action_text: "Running Bank Shot", group: "Jump Shot"};
	this.ShotTypeLookup[85] = {action_text: "Turnaround Bank Shot", group: "Jump Shot"};
	this.ShotTypeLookup[86] = {action_text: "Turnaround Fade Away Shot", group: "Jump Shot"};
	this.ShotTypeLookup[87] = {action_text: "Putback Dunk", group: "Dunk"};
	this.ShotTypeLookup[88] = {action_text: "Driving Slam Dunk", group: "Dunk"};
	this.ShotTypeLookup[89] = {action_text: "Reverse Slam Dunk", group: "Dunk"};
	this.ShotTypeLookup[90] = {action_text: "Running Slam Dunk", group: "Dunk"};
	this.ShotTypeLookup[91] = {action_text: "Putback Reverse Dunk", group: "Dunk"};
	this.ShotTypeLookup[92] = {action_text: "Putback Slam Dunk", group: "Dunk"};
	this.ShotTypeLookup[93] = {action_text: "Driving Bank Hook", group: "Jump Shot"};
	this.ShotTypeLookup[94] = {action_text: "Jump Bank Hook", group: "Jump Shot"};
	this.ShotTypeLookup[95] = {action_text: "Running Bank Hook", group: "Jump Shot"};
	this.ShotTypeLookup[96] = {action_text: "Turnaround Bank Hook", group: "Jump Shot"};
};
