/*
Copyright (C) 2006, Apple Computer, Inc.  All rights reserved.
NOTE:  Use of this source code is subject to the terms of the Software
License Agreement for Mac OS X, which accompanies the code.  Your use
of this source code signifies your agreement to such license terms and
conditions.  Except as expressly granted in the Software License Agreement
for Mac OS X, no other copyright, patent, or other intellectual property
license or right is granted, either expressly or by implication, by Apple.
*/

//
// ESPN Parser Version 1.2
//

//
// The main website URL for clicking on the backside logo:
//

var kESPNLogoClickedURL = "http://www.espn.com";

//
// The Leagues Array contains data on all sports-"leagues" and refers to the Kinds Array.
// Carefully note that the index string ("mlb", etc) is the ESPN-coverage-category-league-name and is a
// naming convention that is passed around as a token and is thus more than simply an arbitrary index.
// The "staticEnable" fields can be used to turn off entire leagues from this widget version:
//

var gLeagueArray = new Array;

gLeagueArray ['RPM'] =				{
									staticEnable:	true, // Racing feeds still point to old Apple XML, keeping disabled
									title:			"Auto Racing",
									kind:			'racing',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/racing/f1/scoreboard',
									url:			'http://sports.espn.go.com/rpm/index',
									background:		'racing.png',
									scoreParser:	scoreParser_racing
									};

gLeagueArray ['mlb'] =				{
									staticEnable:	true,
									title:			"Baseball",
									kind:			'baseball',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/baseball/mlb/scoreboard',
									url:			'http://sports.espn.go.com/mlb/index',
									background:		'baseball.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['nba'] =				{
									staticEnable:	true,
									title:			"Basketball",
									kind:			'basketball',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/basketball/nba/scoreboard',
									url:			'http://sports.espn.go.com/nba/index',
									background:		'basketball.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['WNBA'] =				{
									staticEnable:	true,
									title:			"Basketball - Women's Pro",
									kind:			'basketball',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/basketball/wnba/scoreboard',
									url:			'http://sports.espn.go.com/wnba/index',
									background:		'basketball.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['ncb'] =				{
									staticEnable:	true,
									title:			"Basketball - College",
									kind:			'basketball',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball/scoreboard',
									url:			'http://sports.espn.go.com/ncb/index',
									background:		'basketball.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['ncw'] =				{
									staticEnable:	true,
									title:			"Basketball - Women's",
									kind:			'basketball',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball/scoreboard',
									url:			'http://sports.espn.go.com/ncw/index',
									background:		'basketball.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['nfl'] =				{
									staticEnable:	true,
									title:			"Football",
									kind:			'football',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/football/nfl/scoreboard',
									url:			'http://sports.espn.go.com/nfl/index',
									background:		'football.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['ncf'] =				{
									staticEnable:	true,
									title:			"Football - College",
									kind:			'football',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/football/college-football/scoreboard',
									url:			'http://sports.espn.go.com/ncf/index',
									background:		'football.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['GOLF'] =				{
									staticEnable:	true, // Golf feeds still point to old Apple XML, keeping disabled
									title:			"Golf",
									kind:			'golf',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/golf/pga/scoreboard',
									url:			'http://sports.espn.go.com/golf/index',
									background:		'golf.png',
									scoreParser:	scoreParser_golf
									};

gLeagueArray ['nhl'] =				{
									staticEnable:	true,
									title:			"Hockey",
									kind:			'hockey',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/hockey/nhl/scoreboard',
									url:			'http://sports.espn.go.com/nhl/index',
									background:		'hockey.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['INTLSOC'] =			{
									staticEnable:	true, // Soccer feeds still point to old Apple XML, keeping disabled
									title:			"Soccer - International",
									kind:			'soccer',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/soccer/fifa.world/scoreboard',
									url:			'http://soccernet.espn.go.com',
									background:		'soccer.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['EUROSOC'] =			{
									staticEnable:	true, // Soccer feeds still point to old Apple XML, keeping disabled
									title:			"Soccer - Europe",
									kind:			'soccer',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/soccer/UEFA.EUROPA/scoreboard',
									url:			'http://soccernet.espn.go.com/section?id=europe',
									background:		'soccer.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['UKSOC'] =			{
									staticEnable:	true, // Soccer feeds still point to old Apple XML, keeping disabled
									title:			"Soccer - UK",
									kind:			'soccer',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/soccer/eng.1/scoreboard',
									url:			'http://soccernet.espn.go.com/section?id=england',
									background:		'soccer.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['USASOC'] =			{
									staticEnable:	true, // Soccer feeds still point to old Apple XML, keeping disabled
									title:			"Soccer - US",
									kind:			'soccer',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/soccer/usa.1/scoreboard', // Corrected example for US Soccer
									url:			'http://soccernet.espn.go.com/index?ver=us',
									background:		'soccer.png',
									scoreParser:	scoreParser_standard
									};

gLeagueArray ['TENNIS'] =			{
									staticEnable:	true, // Tennis feeds still point to old Apple XML, keeping disabled
									title:			"Tennis",
									kind:			'tennissingles',
									feed:			'http://site.api.espn.com/apis/site/v2/sports/tennis/atp/scoreboard',
									url:			'http://espn.go.com/tennis/index.html',
									background:		'tennis.png',
									scoreParser:	scoreParser_tennis
									};



//
// The Kinds Array defines sport-specific info and layout styles.  Note that the multiple sports leagues given in the main
// Sports Leagues Array above may share the same layout (for example US Soccer, International Soccer, etc).  Carefully
// note that the index string ("baseball", etc) is a naming convention that is passed around, is a suffix in HTML items,
// etc; it is thus more than simply an index.  Even though this is layout-specific, the list of leagues can be feed-specific
// so define it here in the parser:
//

var gKindsArray = new Array;

gKindsArray ['baseball'] =			{
									teamComposition:		"team",
									layoutRowType:			"2row"
									};

gKindsArray ['football'] =			{
									teamComposition:		"team",
									layoutRowType:			"2row"
									};

gKindsArray ['basketball'] =		{
									teamComposition:		"team",
									layoutRowType:			"2row"
									};

gKindsArray ['soccer'] =			{
									teamComposition:		"team",
									layoutRowType:			"1row"
									};

gKindsArray ['hockey'] =			{
									teamComposition:		"team",
									layoutRowType:			"2row"
									};

gKindsArray ['golf'] =				{
									teamComposition:		"individual",
									layoutRowType:			"1row"
									};

gKindsArray ['racing'] =			{
									teamComposition:		"individual",
									layoutRowType:			"1row"
									};

gKindsArray ['tennissingles'] =		{
									teamComposition:		"individual",
									layoutRowType:			"2row"
									};

gKindsArray ['tennisdoubles'] =		{
									teamComposition:		"team",
									layoutRowType:			"2row"
									};



function fetchNews (inLeague, inDataConsumerCallback)
{
	var url;
	if (inLeague=="nba") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/basketball/nba';
	}
	else if (inLeague=="nfl") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/football/nfl';
	}
	else if (inLeague=="ncf") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/football/college-football';
	}
	else if (inLeague=="WNBA") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/basketball/wnba';
	}
	else if (inLeague=="ncb") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/basketball/mens-college-basketball';
	}
	else if (inLeague=="ncw") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/basketball/womens-college-basketball';
	}
	else if (inLeague=="mlb") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/baseball/mlb';
	}
	else if (inLeague=="nhl") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/hockey/nhl';
	}
	else if (inLeague=="GOLF") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/golf/pga';
	}
	else if (inLeague=="TENNIS") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/tennis/atp';
	}
	else if (inLeague=="RPM") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/racing/f1';
	}
	else if (inLeague=="INTLSOC") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/soccer/fifa.world';
	}
	else if (inLeague=="EUROSOC") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/soccer/UEFA.EUROPA';
	}
	else if (inLeague=="UKSOC") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/soccer/eng.1';
	}
	else if (inLeague=="USASOC") {
		url = 'http://site.api.espn.com/apis/site/v2/sports/soccer/usa.1';
	}
	else {
        var outObj = {error:true, errorString:"fetchNews: Unknown league: " + inLeague, refresh:0, league:inLeague, stamp:(new Date).getTime(), stories:new Array};
        inDataConsumerCallback(outObj);
        return null;
    }

	var xml_request = new XMLHttpRequest();
	xml_request.onload = function(e) {newsParser(e, xml_request, inDataConsumerCallback, inLeague, (new Date).getTime());}
	xml_request.onerror = function(e) {
        var outObj = {error:true, errorString:"fetchNews: Network error occurred", refresh:0, league:inLeague, stamp:(new Date).getTime(), stories:new Array};
        inDataConsumerCallback(outObj);
    };
	xml_request.overrideMimeType("application/json");
	xml_request.open("GET", url+'/news');
	xml_request.setRequestHeader("Cache-Control", "no-cache");
	xml_request.send(null);

	return xml_request;
}



function newsParser (inDOMEvent, inRequest, inDataConsumerCallback, inLeague, inTimeStamp)
{
	var outObj = {error:false, errorString:null, refresh:0, league:inLeague, stamp:inTimeStamp, stories:new Array};

	if (inRequest.responseText)
	{
		try
		{
			if (inRequest.readyState != 4)   { outObj.error = true;   outObj.errorString = "newsParser: readyState not 4";   inDataConsumerCallback (outObj);   return; }
			if (inRequest.status != 200)     { outObj.error = true;   outObj.errorString = "newsParser: status not 200, status: " + inRequest.status;     inDataConsumerCallback (outObj);   return; }

			var jsonData = JSON.parse(inRequest.responseText);

			outObj.refresh = 5 * 60 * 1000; // Default refresh for news: 5 minutes

			if (jsonData.articles && Array.isArray(jsonData.articles))
			{
				for (var i = 0; i < jsonData.articles.length; i++)
				{
					var article = jsonData.articles[i];
					var headline = article.headline;
					var url = article.links && article.links.web && article.links.web.href ? article.links.web.href : null;

					if (headline != null)
					{
						outObj.stories[outObj.stories.length] = {headline: headline, url: url};
					}
				}
			} else {
				outObj.error = true;
				outObj.errorString = "newsParser: JSON response contains no 'articles' array or is not an array.";
				inDataConsumerCallback (outObj);
				return;
			}

			try
			{
				inDataConsumerCallback (outObj);
			}
			catch (ex)
			{
				outObj.error = true;
				outObj.errorString = "newsParser: throw during data consumer callback (re-calling): " + ex;
				inDataConsumerCallback (outObj);
			}
		}
		catch (ex)
		{
			outObj.error = true;
			outObj.errorString = "newsParser: throw during parse or data processing: " + ex;
			inDataConsumerCallback (outObj);
		}
	}
	else
	{
		outObj.error = true;
		outObj.errorString = "newsParser: no responseText";
		inDataConsumerCallback (outObj);
	}
}



function fetchScores (inLeague, inDataConsumerCallback)
{
	var obj = gLeagueArray [inLeague];

    if (!obj || !obj.feed) {
        var outObj = {error:true, errorString:"fetchScores: Invalid league object or missing feed for league: " + inLeague, refresh:0, league:inLeague, stamp:(new Date).getTime(), games:new Array};
        inDataConsumerCallback(outObj);
        return null;
    }

	var xml_request = new XMLHttpRequest();
	xml_request.onload = function(e) {
        obj.scoreParser(e, xml_request, inDataConsumerCallback, inLeague, (new Date).getTime());
    }
	xml_request.onerror = function(e) {
        var outObj = {error:true, errorString:"fetchScores: Network error occurred", refresh:0, league:inLeague, stamp:(new Date).getTime(), games:new Array};
        inDataConsumerCallback(outObj);
    };
	xml_request.overrideMimeType("application/json");
	xml_request.open("GET", obj.feed);
	xml_request.setRequestHeader("Cache-Control", "no-cache");
	xml_request.send(null);

	return xml_request;
}



function scoreParser_standard (inDOMEvent, inRequest, inDataConsumerCallback, inLeague, inTimeStamp)
{
    var outObj = { error: false, errorString: null, refresh: 0, league: inLeague, stamp: inTimeStamp, games: new Array };

    if (inRequest.responseText) {
        try {
            if (inRequest.readyState !== 4 || inRequest.status !== 200) {
                outObj.error = true;
                outObj.errorString = "scoreParser_standard: network error or invalid response";
                inDataConsumerCallback(outObj);
                return;
            }

            var jsonData = JSON.parse(inRequest.responseText);
            outObj.refresh = 30 * 1000;

            if (jsonData.events && Array.isArray(jsonData.events)) {
                for (var i = 0; i < jsonData.events.length; i++) {
                    var event = jsonData.events[i];
                    if (!event || !event.competitions || !Array.isArray(event.competitions) || event.competitions.length === 0) {
                        continue;
                    }
                    var competition = event.competitions[0];

                    var game = {
                        id: event.id || null,
                        url: (event.links && event.links.web && event.links.web.href) ? event.links.web.href : null,
                        away: null,
                        home: null,
                        statusId: 0,
                        statusStr: null,
                        statusLineDetailStrs: []
                    };

                    if (competition.competitors && Array.isArray(competition.competitors) && competition.competitors.length === 2) {
                        var team1 = competition.competitors[0];
                        var team2 = competition.competitors[1];
                        
                        var team1Name = (team1.team && team1.team.shortDisplayName) || (team1.team && team1.team.displayName) || "Unknown";
                        var team2Name = (team2.team && team2.team.shortDisplayName) || (team2.team && team2.team.displayName) || "Unknown";
                        var team1Score = String(team1.score || "");
                        var team2Score = String(team2.score || "");
                        
                        // Check the 'homeAway' property to correctly assign teams
                        if (team1.homeAway === "away") {
                            game.away = { team: team1Name, score: team1Score };
                            game.home = { team: team2Name, score: team2Score };
                        } else {
                            game.away = { team: team2Name, score: team2Score };
                            game.home = { team: team1Name, score: team1Score };
                        }
                    } else {
                        game.statusLineDetailStrs.push("[Invalid competitors array]");
                        continue;
                    }

                    if (competition.status && competition.status.type) {
                        game.statusId = parseInt(competition.status.type.id, 10) || 0;
                        game.statusStr = competition.status.detail || "";

                        if (game.statusId === 2 && competition.status.period) {
                            game.statusLineDetailStrs.push("Q" + competition.status.period);
                            if (competition.status.displayClock) {
                                game.statusLineDetailStrs.push(competition.status.displayClock);
                            }
                        } else if (game.statusId === 1 && competition.date) {
                            try {
                                const gameDate = new Date(competition.date);
                                const timeStr = gameDate.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
                                const dateStr = gameDate.toLocaleDateString([], { month: '2-digit', day: '2-digit' });
                                game.statusLineDetailStrs.push(timeStr + ' ' + dateStr);
                            } catch (e) {
                                game.statusLineDetailStrs.push(competition.date || "Date Unknown");
                            }
                        } else if (game.statusId === 3) {
                            game.statusLineDetailStrs.push("Final");
                        }

                        if (competition.broadcasts && Array.isArray(competition.broadcasts) && competition.broadcasts.length > 0) {
                            competition.broadcasts.forEach(function(broadcast) {
                                if (broadcast.names && Array.isArray(broadcast.names) && broadcast.names.length > 0) {
                                    game.statusLineDetailStrs.push(broadcast.names[0]);
                                }
                            });
                        }
                    } else {
                        game.statusLineDetailStrs.push("[No game status]");
                    }

                    outObj.games.push(game);
                }
            } else {
                outObj.error = true;
                outObj.errorString = "scoreParser_standard: JSON response contains no 'events' array or 'events' is not an array.";
                inDataConsumerCallback(outObj);
                return;
            }

            inDataConsumerCallback(outObj);
        } catch (ex) {
            outObj.error = true;
            outObj.errorString = "scoreParser_standard: throw during JSON parse or data processing: " + ex;
            inDataConsumerCallback(outObj);
        }
    } else {
        outObj.error = true;
        outObj.errorString = "scoreParser_standard: no responseText from XMLHttpRequest.";
        inDataConsumerCallback(outObj);
    }
}


function trimWhiteSpace (inString)
{
	return inString.replace(/^\s*/, "").replace(/\s*$/, "");
}


//
// Updated Parser Functions for Racing, Golf, and Tennis based on provided JSON
//

function scoreParser_racing (inDOMEvent, inRequest, inDataConsumerCallback, inLeague, inTimeStamp)
{
    var outObj = {error: false, errorString: null, refresh: 0, league: inLeague, stamp: inTimeStamp, games: new Array};

    if (inRequest.responseText) {
        try {
            if (inRequest.readyState !== 4 || inRequest.status !== 200) {
                outObj.error = true;
                outObj.errorString = "scoreParser_racing: network error or invalid response";
                inDataConsumerCallback(outObj);
                return;
            }

            var jsonData = JSON.parse(inRequest.responseText);
            outObj.refresh = 30 * 1000;

            if (jsonData.events && Array.isArray(jsonData.events)) {
                for (var i = 0; i < jsonData.events.length; i++) {
                    var event = jsonData.events[i];
                    if (!event || !event.competitions || !Array.isArray(event.competitions) || event.competitions.length === 0) {
                        continue;
                    }
                    var competition = event.competitions[0];

                    var game = {
                        id: event.id || null,
                        url: (event.links && event.links.web && event.links.web.href) ? event.links.web.href : null,
                        racer1: null,
                        racer2: null,
                        racer3: null,
                        statusId: 0,
                        statusStr: null,
                        statusLineDetailStrs: []
                    };

                    // Correctly parse racing data from the nested structure
                    if (competition.competitors && Array.isArray(competition.competitors) && competition.competitors.length >= 3) {
                        if (competition.competitors[0].athlete) game.racer1 = competition.competitors[0].athlete.displayName || "Unknown";
                        if (competition.competitors[1].athlete) game.racer2 = competition.competitors[1].athlete.displayName || "Unknown";
                        if (competition.competitors[2].athlete) game.racer3 = competition.competitors[2].athlete.displayName || "Unknown";
                    }

                    if (competition.status && competition.status.type) {
                        game.statusId = parseInt(competition.status.type.id, 10) || 0;
                        game.statusStr = competition.status.detail || "";
                        game.statusLineDetailStrs.push(competition.status.detail);
                        if (competition.status.displayClock) {
                            game.statusLineDetailStrs.push(competition.status.displayClock);
                        }
                    }

                    outObj.games.push(game);
                }
            } else {
                outObj.error = false;
                outObj.errorString = "scoreParser_racing: No events found.";
            }
            inDataConsumerCallback(outObj);
        } catch (ex) {
            outObj.error = true;
            outObj.errorString = "scoreParser_racing: throw during JSON parse or data processing: " + ex;
            inDataConsumerCallback(outObj);
        }
    } else {
        outObj.error = true;
        outObj.errorString = "scoreParser_racing: no responseText from XMLHttpRequest.";
        inDataConsumerCallback(outObj);
    }
}


function scoreParser_golf (inDOMEvent, inRequest, inDataConsumerCallback, inLeague, inTimeStamp)
{
    var outObj = { error: false, errorString: null, refresh: 0, league: inLeague, stamp: inTimeStamp, games: new Array(), debugMessage: "" };

    if (inRequest.responseText) {
        try {
            if (inRequest.readyState !== 4 || inRequest.status !== 200) {
                outObj.error = true;
                outObj.errorString = "scoreParser_golf: network error or invalid response";
                outObj.debugMessage = "Network Error: Status " + inRequest.status;
                inDataConsumerCallback(outObj);
                return;
            }

            var jsonData = JSON.parse(inRequest.responseText);
            outObj.refresh = 30 * 1000;
            outObj.debugMessage = "JSON data parsed successfully.";

            // Defensive navigation of the JSON structure to find the leaderboard entries
            if (jsonData && jsonData.leagues && Array.isArray(jsonData.leagues) && jsonData.leagues.length > 0) {
                var league = jsonData.leagues[0];
                if (league.events && Array.isArray(league.events) && league.events.length > 0) {
                    var event = league.events[0];
                    if (event.competitions && Array.isArray(event.competitions) && event.competitions.length > 0) {
                        var competition = event.competitions[0];
                        if (competition.leaderboard && competition.leaderboard.entries && Array.isArray(competition.leaderboard.entries)) {
                            var leaderboard = competition.leaderboard;

                            // Set the status string for the entire event
                            outObj.statusStr = (competition.status && competition.status.detail) ? competition.status.detail : "";

                            outObj.debugMessage += " Found " + leaderboard.entries.length + " leaderboard entries.";

                            // Loop through each entry on the leaderboard
                            for (var j = 0; j < leaderboard.entries.length; j++) {
                                var entry = leaderboard.entries[j];

                                // Create a game object with properties that match the ESPN.js rendering code
                                var game = {
                                    id: entry.id || null,
                                    url: (entry.links && entry.links.web && entry.links.web.href) ? entry.links.web.href : null,
                                    position: entry.position || "",
                                    player1name: (entry.athlete && entry.athlete.displayName) ? entry.athlete.displayName : "Unknown",
                                    player1score: (entry.score != null) ? String(entry.score) : "",
                                    // The rendering code expects 'thru' directly on the game object
                                    thru: (entry.status && entry.status.thru != null) ? String(entry.status.thru) : "0",
                                    statusId: (competition.status && competition.status.type) ? parseInt(competition.status.type.id, 10) || 0 : 0,
                                    statusStr: competition.status && competition.status.detail ? competition.status.detail : null,
                                    statusLineDetailStrs: []
                                };
                                outObj.games.push(game);
                            }
                        } else {
                            outObj.error = true;
                            outObj.errorString = "scoreParser_golf: No leaderboard entries found in the JSON structure.";
                            outObj.debugMessage = "Error: " + outObj.errorString;
                        }
                    } else {
                        outObj.error = true;
                        outObj.errorString = "scoreParser_golf: No competition found in the JSON structure.";
                        outObj.debugMessage = "Error: " + outObj.errorString;
                    }
                } else {
                    outObj.error = true;
                    outObj.errorString = "scoreParser_golf: No events found in the JSON structure.";
                    outObj.debugMessage = "Error: " + outObj.errorString;
                }
            } else {
                outObj.error = true;
                outObj.errorString = "scoreParser_golf: Invalid JSON structure or no leagues found.";
                outObj.debugMessage = "Error: " + outObj.errorString;
            }

            inDataConsumerCallback(outObj);
        } catch (ex) {
            outObj.error = true;
            outObj.errorString = "scoreParser_golf: throw during JSON parse or data processing: " + ex;
            outObj.debugMessage = "Exception: " + ex;
            inDataConsumerCallback(outObj);
        }
    } else {
        outObj.error = true;
        outObj.errorString = "scoreParser_golf: no responseText from XMLHttpRequest.";
        outObj.debugMessage = "Error: No response from API.";
        inDataConsumerCallback(outObj);
    }
}


function scoreParser_tennis (inDOMEvent, inRequest, inDataConsumerCallback, inLeague, inTimeStamp)
{
    var outObj = {error: false, errorString: null, refresh: 0, league: inLeague, stamp: inTimeStamp, games: new Array};

    if (inRequest.responseText) {
        try {
            if (inRequest.readyState !== 4 || inRequest.status !== 200) {
                outObj.error = true;
                outObj.errorString = "scoreParser_tennis: network error or invalid response";
                inDataConsumerCallback(outObj);
                return;
            }

            var jsonData = JSON.parse(inRequest.responseText);
            outObj.refresh = 30 * 1000;

            if (jsonData.events && Array.isArray(jsonData.events)) {
                for (var i = 0; i < jsonData.events.length; i++) {
                    var event = jsonData.events[i];
                    if (!event || !event.competitions || !Array.isArray(event.competitions) || event.competitions.length === 0) {
                        continue;
                    }
                    var competition = event.competitions[0];

                    var game = {
                        id: event.id || null,
                        url: (event.links && event.links.web && event.links.web.href) ? event.links.web.href : null,
                        player1: null,
                        player2: null,
                        player1score: null,
                        player2score: null,
                        statusId: 0,
                        statusStr: null,
                        statusLineDetailStrs: []
                    };

                    // Correctly parse tennis data from the nested structure
                    if (competition.competitors && Array.isArray(competition.competitors) && competition.competitors.length === 2) {
                        var p1 = competition.competitors[0];
                        var p2 = competition.competitors[1];

                        if (p1.athlete) game.player1 = p1.athlete.displayName || "Unknown";
                        if (p2.athlete) game.player2 = p2.athlete.displayName || "Unknown";

                        if (p1.score) {
                            game.player1score = String(p1.score);
                        }
                        if (p2.score) {
                            game.player2score = String(p2.score);
                        }
                    }

                    if (competition.status && competition.status.type) {
                        game.statusId = parseInt(competition.status.type.id, 10) || 0;
                        game.statusStr = competition.status.detail || "";
                        game.statusLineDetailStrs.push(competition.status.detail);
                        if (competition.status.displayClock) {
                            game.statusLineDetailStrs.push(competition.status.displayClock);
                        }
                    }

                    outObj.games.push(game);
                }
            } else {
                outObj.error = false;
                outObj.errorString = "scoreParser_tennis: No events found.";
            }
            inDataConsumerCallback(outObj);
        } catch (ex) {
            outObj.error = true;
            outObj.errorString = "scoreParser_tennis: throw during JSON parse or data processing: " + ex;
            inDataConsumerCallback(outObj);
        }
    } else {
        outObj.error = true;
        outObj.errorString = "scoreParser_tennis: no responseText from XMLHttpRequest.";
        inDataConsumerCallback(outObj);
    }
}