public static LeagueTeam GetOpposingTeam(this Player player, Fixture fixture)
 {
     if (player != null)
     {
         if (player.Team?.ID == fixture.HomeTeam?.Team?.ID)
             return fixture.AwayTeam;
         if (player.Team?.ID == fixture.AwayTeam?.Team?.ID)
             return fixture.HomeTeam;
     }
     return null;
 }
        public void UpdateStatisticsBasedOnFixture(Fixture fixture)
        {
            // Statistics are updated only when the game has ended
            var fixtureWinner = fixture.GetFixtureOutcome();
            if (fixtureWinner == FixtureOutcome.Undetermined)
                return;

            var isHome = Team.ID == fixture.HomeTeam.Team.ID;
            var opposingTeam = isHome ? fixture.AwayTeam : fixture.HomeTeam;

            // todo: validate that statistics hasn't already been applied?

            Statistics.PlayedGames++;

            Statistics.GoalsFor += isHome
                ? fixture.Statistics.Score.GoalsForHomeTeam
                : fixture.Statistics.Score.GoalsForAwayTeam;
            Statistics.GoalsConceded += isHome
                ? fixture.Statistics.Score.GoalsForAwayTeam
                : fixture.Statistics.Score.GoalsForHomeTeam;

            switch (fixtureWinner)
            {
                case FixtureOutcome.HomeTeam:
                    if (isHome)
                        Statistics.WonGames++;
                    else
                        Statistics.LostGames++;
                    break;

                case FixtureOutcome.AwayTeam:
                    if (isHome)
                        Statistics.LostGames++;
                    else
                        Statistics.WonGames++;
                    break;

                case FixtureOutcome.Draw:
                    Statistics.DrawGames++;
                    break;

                case FixtureOutcome.None:
                case FixtureOutcome.Undetermined:
                    throw new NotSupportedException("Cannot update statistics, when game has not ended");

                default:
                    throw new InvalidOperationException();
            }
        }
        public static int GetPlayedMinutesBeforeFixtureForPlayer(this Player player, Fixture fixture)
        {
            var playedMinutes = 0;
            var league = fixture.Gameweek.League;
            var team = player.GetLeagueTeam(fixture);
            for (var i = 0; i < league.Gameweeks.Length; i++)
            {
                var gw = league.Gameweeks[i];
                if (gw.Number > fixture.Gameweek.Number)
                    break;

                var fixtures = team.GetFixturesByTeam(gw.Fixtures).OrderBy(x => x.Time);
                foreach (var fix in fixtures)
                {
                    if (fix == fixture)
                        break;
                    var stats = fix.GetPlayerStats(player);
                    var min = stats != null && stats.PlayedMinutes > 0 ? stats.PlayedMinutes : 0;
                    playedMinutes += min;
                }
            }
            return playedMinutes;
        }
        public ValueMap GeneratePlayerValueMap(Player player, Fixture fixture, PlayerAnalyserBase analyser)
        {
            var playerTeam = player.GetLeagueTeam(fixture);
            double teamPlayedMinutes = playerTeam.GetPlayedMinutesBeforeFixtureForTeam(fixture);
            double playerMinutes = player.GetPlayedMinutesBeforeFixtureForPlayer(fixture);
            var percentage = teamPlayedMinutes > 0
                ? playerMinutes / teamPlayedMinutes
                : 0;
            var recentLeagueFixtues = playerTeam.GetFixturesFromLeagueTeam()
                .Where(x => x.Statistics.GameStarted && x.Statistics.GameFinished)
                .OrderByDescending(x => x.Time)
                .Take(5)
                .ToList();
            var playedLeagueFixtues = playerTeam.GetFixturesFromLeagueTeam()
                .Where(x => x.Statistics.GameStarted && x.Statistics.GameFinished)
                .OrderBy(x => x.Time)
                .ToList();

            double points = playedLeagueFixtues
                .Sum(fixt => playerTeam.Team.HasHomeTeamAdvantage(fixt)
                    ? fixt.Statistics.Score.PointsForHomeTeam
                    : fixt.Statistics.Score.PointsForAwayTeam);
            double totalPoints = playedLeagueFixtues
                .Where(x => x.Statistics.GameStarted && x.Statistics.GameFinished)
                .Sum(fixt => playerTeam.Team.HasHomeTeamAdvantage(fixt)
                    ? fixt.Statistics.Score.PointsForHomeTeam
                    : fixt.Statistics.Score.PointsForAwayTeam);

            var fixtGoalsAgainst = playedLeagueFixtues.Select(fixt =>
            {
                var isHome = playerTeam.Team.HasHomeTeamAdvantage(fixt);
                var ga = isHome
                    ? fixt.Statistics.Score.GoalsForAwayTeam
                    : fixt.Statistics.Score.GoalsForHomeTeam;
                return ga;
            }).ToList();

            double saves = player.Statistics.Saves;
            double goalsAgainst = fixtGoalsAgainst.Sum(x => x);
            double cleansheets = fixtGoalsAgainst.Count(x => x == 0);
            double cleansheetsPer = cleansheets / playedLeagueFixtues.Count;

            var valueMap = new ValueMap();
            valueMap[ValueMapKeys.GameweekStarted] = fixture.Gameweek.Started;
            valueMap[ValueMapKeys.GameweekEnded] = fixture.Gameweek.Ended;
            valueMap[ValueMapKeys.FixtureStarted] = fixture.Statistics.GameStarted;
            valueMap[ValueMapKeys.FixtureEnded] = fixture.Statistics.GameFinished;

            valueMap[ValueMapKeys.TotalGamesWon] = playedLeagueFixtues.Count(x => playerTeam.GetTeamFixtureOutcome(fixture) == TeamFixtureOutcome.Win);
            valueMap[ValueMapKeys.TotalGamesDraw] = playedLeagueFixtues.Count(x => playerTeam.GetTeamFixtureOutcome(fixture) == TeamFixtureOutcome.Draw);
            valueMap[ValueMapKeys.TotalGamesLoss] = playedLeagueFixtues.Count(x => playerTeam.GetTeamFixtureOutcome(fixture) == TeamFixtureOutcome.Loss);
            valueMap[ValueMapKeys.PlayerTotalGamesPlayed] = playedLeagueFixtues.Count;
            valueMap[ValueMapKeys.TotalGamesPlayed] = playedLeagueFixtues.Count;
            valueMap[ValueMapKeys.RecentGamesTeamPlayed] = recentLeagueFixtues.Count;
            valueMap[ValueMapKeys.RecentGamesTeamPoints] = points;
            valueMap[ValueMapKeys.RecentPointsPerGame] = points / recentLeagueFixtues.Count;
            valueMap[ValueMapKeys.TotalPoints] = totalPoints;
            valueMap[ValueMapKeys.TotalPointsPerGame] = totalPoints / playedLeagueFixtues.Count;

            valueMap[ValueMapKeys.PlayerTotalMinutesPlayed] = playerMinutes;
            valueMap[ValueMapKeys.PlayerTotalMinutesPercentageOfTeam] = percentage;
            valueMap[ValueMapKeys.FantasyPlayerCurrentPrice] = player.Fantasy.CurrentPrice;
            valueMap[ValueMapKeys.PlayerPosition] = player.Fantasy.Position.ToString();
            valueMap[ValueMapKeys.PlayerTotalCleansheets] = cleansheets;
            valueMap[ValueMapKeys.PlayerTotalCleansheetsPercentage] = cleansheetsPer;
            valueMap[ValueMapKeys.PlayerTotalSaves] = saves;
            valueMap[ValueMapKeys.PlayerTotalSavesPerGame] = saves / playedLeagueFixtues.Count;
            valueMap[ValueMapKeys.PlayerTotalSavesPercentage] = saves / (saves + goalsAgainst);       // todo: filter out other keepers in the team
            //valueMap["playedgames"] = ;
            //valueMap["substitutes-in"] = ;
            //valueMap["substitutes-out"] = ;
            // todo: add more data points (subs, recent playtime (5 last team games), recent subs)

            return valueMap;
        }
        private League GenerateLeague(JObject leagueDataJson, JObject fixturesJson, ref List<Team> teams)
        {
            var league = new League
            {
                ID = "PL",
                Name = "Premier League 16/17",
                Year = 2016,
            };
            var leagueTeams = new List<LeagueTeam>();
            var gameweeks = new List<Gameweek>();
            var positions = new Dictionary<int, PlayerPosition>();


            // Get playing positions
            var typeInfo = leagueDataJson?.Property("element_types")?.Value?.ToObject<JArray>();
            if (typeInfo != null)
            {
                foreach (var type in typeInfo.Select(x => x.ToObject<JObject>()).Where(x => x != null))
                {
                    PlayerPosition pos;
                    var posID = type.GetPropertyValue<int>("id");
                    var posShortName = type.GetPropertyValue<string>("singular_name_short");
                    switch (posShortName)
                    {
                        case "GKP":
                            pos = PlayerPosition.Goalkeeper;
                            break;

                        case "DEF":
                            pos = PlayerPosition.Defender;
                            break;

                        case "MID":
                            pos = PlayerPosition.Midfielder;
                            break;

                        case "FWD":
                            pos = PlayerPosition.Forward;
                            break;

                        default:
                            continue;
                    }
                    positions[posID] = pos;
                }
            }
            


            // Get teams
            var eiwTeams = leagueDataJson?.Property("teams")?.Value?.ToObject<JArray>();
            if (eiwTeams != null)
            {
                foreach (var eiwTeam in eiwTeams)
                {
                    var t = eiwTeam?.ToObjectOrDefault<JObject>();
                    if (t == null)
                        continue;
                    var teamID = t.GetPropertyValue<string>("id");
                    if (string.IsNullOrWhiteSpace(teamID))
                        throw new FormatException("Invalid TeamID");

                    var team = teams.FirstOrDefault(x => x.ID == teamID);
                    if (team == null)
                    {
                        team = new Team
                        {
                            ID = teamID,
                            Name = t.GetPropertyValue<string>("name"),
                            ShortName = t.GetPropertyValue<string>("short_name"),

                            // todo: implement
                            //Statistics = 
                            //Rating = new Rating
                            //{
                            //    // todo: normalize to a 1-10 rating
                            //    //Value = (t.GetPropertyValue<int>("strength_overall_home") +
                            //    //        t.GetPropertyValue<int>("strength_overall_away")) / 2
                            //}
                        };
                        teams.Add(team);

                        var club = _ukClubs?.FirstOrDefault(x => x.MatchName(team.Name));
                        if (club != null)
                        {
                            team.Aliases = club.Aliases;
                        }
                    }
                
                    var leagueTeam = new LeagueTeam(league, team);
                    leagueTeams.Add(leagueTeam);
                    team.Leagues = team.Leagues.Append(league);
                }
            }




            // Get players
            var elements = leagueDataJson?.Property("elements")?.Value?.ToObject<JArray>();
            if (elements != null)
            {
                foreach (var playerData in elements)
                {
                    var p = playerData?.ToObjectOrDefault<JObject>();
                    if (p == null)
                        continue;

                    var posID = p.GetPropertyValue<int>("element_type");
                    var pos = positions[posID];
                    
                    // todo: load and update player (statitics), if player plays in multiple leagues

                    var player                          = new Player();
                    player.ID                           = p.GetPropertyValue<string>("id");
                    player.FirstName                    = p.GetPropertyValue<string>("first_name");
                    player.LastName                     = p.GetPropertyValue<string>("second_name");
                    player.DisplayName                  = p.GetPropertyValue<string>("web_name");
                    
                    var currentPrice = p.GetPropertyValue<double>("now_cost") / 10;
                    var costChangeSinceStart = p.GetPropertyValue<double>("cost_change_start") / 10;

                    object indexInfo = new PremierLeagueFantasyIndex
                    {
                        EaIndex = new Assignable<double>(p.GetPropertyValue<double>("ea_index")),
                        Influence = new Assignable<double>(p.GetPropertyValue<double>("influence")),
                        Creativity = new Assignable<double>(p.GetPropertyValue<double>("creativity")),
                        Threat = new Assignable<double>(p.GetPropertyValue<double>("threat")),
                    };

                    player.Fantasy                      = new FantasyPlayer
                    {
                        Position                        = pos,
                        CurrentPrice                    = currentPrice,
                        OriginalPrice                   = currentPrice + costChangeSinceStart,
                        Unavailable                     = p.GetPropertyValue<string>("status") != "a",
                        ChanceOfPlayingNextFixture      = p.GetPropertyValue<double>("chance_of_playing_this_round", -1) / 100,
                        News                            = p.GetPropertyValue<string>("news"),
                        OwnagePercent                   = p.GetPropertyValue<double>("selected_by_percent", -1),
                        TransfersDetailsForSeason       = new TransferDetails
                        {
                            TransfersIn                 = p.GetPropertyValue<int>("transfers_in"),
                            TransfersOut                = p.GetPropertyValue<int>("transfers_out"),
                        },
                        TransfersDetailsForGW           = new TransferDetails
                        {
                            TransfersIn                 = p.GetPropertyValue<int>("transfers_in_event"),
                            TransfersOut                = p.GetPropertyValue<int>("transfers_out_event"),
                        },
                        IndexInfo = indexInfo,
                    };

                    player.Statistics                   = new PlayerStatistics
                    {
                        PlayedMinutes                   = p.GetPropertyValue<int>("minutes"),
                        Goals                           = p.GetPropertyValue<int>("goals_scored"),
                        Assists                         = p.GetPropertyValue<int>("assists"),
                        TimesInDreamteam                = p.GetPropertyValue<int>("dreamteam_count"),
                        YellowCards                     = p.GetPropertyValue<int>("yellow_cards"),
                        RedCards                        = p.GetPropertyValue<int>("red_cards"),
                        BonusPoints                     = p.GetPropertyValue<int>("bonus"),
                        Form                            = p.GetPropertyValue<double>("form"),
                        PenaltiesMissed                 = p.GetPropertyValue<int>("penalties_missed"),
                        PenaltiesSaved                  = p.GetPropertyValue<int>("penalties_scored"),
                        Saves                           = p.GetPropertyValue<int>("saves"),
                        TotalPoints                     = p.GetPropertyValue<int>("total_points"),
                        CleanSheets                     = p.GetPropertyValue<int>("clean_sheets"),
                        PointsPerGame                   = p.GetPropertyValue<double>("points_per_game"),
                        OwnGoals                        = p.GetPropertyValue<int>("own_goals"),


                        // todo:
                        //Appearances = "",
                        //Crosses = 
                        //Offsides = 
                        //PenaltiesScored = 
                        //Substitutions = 
                        //Shots = 
                    };

                    var teamID = p.GetPropertyValue<string>("team");
                    var team = teams.SingleOrDefault(x => x.ID == teamID);
                    if (team != null)
                    {
                        player.Team = team;
                        team.Players = team.Players.Append(player);
                    }
                    else
                    {
                        
                    }

                    // Update Team statistics   (todo: will get wrong if player changes team)
                    //team.Statistics.GoalsFor = player.Statistics.Goals;
                }
            }



            // Fixtures
            if (fixturesJson != null)
            {
                var fixtures = fixturesJson.GetPropertyValue<JArray>("fixtures");
                foreach (var f in fixtures)
                {
                    try
                    {
                        var fixture = new Fixture();

                        var obj = f.ToObjectOrDefault<JObject>();
                        fixture.Time = obj.GetPropertyValue<DateTime>("date");

                        var homeTeamName = obj.GetPropertyValue<string>("homeTeamName");
                        var awayTeamName = obj.GetPropertyValue<string>("awayTeamName");
                        var homeTeam = leagueTeams.SingleOrDefault(x => x.Team.Name == homeTeamName || x.Team.MatchName(homeTeamName));
                        var awayTeam = leagueTeams.SingleOrDefault(x => x.Team.Name == awayTeamName || x.Team.MatchName(awayTeamName));
                        fixture.HomeTeam = homeTeam;
                        fixture.AwayTeam = awayTeam;

                        var resultsJson = obj.GetPropertyValue<JObject>("result");
                        var status = obj.GetPropertyValue<string>("status");
                        var finished = status == "FINISHED";
                        var min = obj.GetPropertyValue<int>("minute");      // todo: correct?
                        if (finished && min <= 0)
                            min = 90;
                        var goalsHome = resultsJson?.GetPropertyValue<int>("goalsHomeTeam") ?? 0;
                        var goalsAway = resultsJson?.GetPropertyValue<int>("goalsAwayTeam") ?? 0;
                        fixture.Statistics = new FixtureStatistics
                        {
                            PlayedMinutes = min,
                            GameFinished = finished,
                            Score = FixtureScore.Create(goalsHome, goalsAway),
                        };


                        var matchday = obj.GetPropertyValue<int>("matchday");
                        var gw = gameweeks.SingleOrDefault(x => x.Number == matchday);
                        if (gw == null)
                        {
                            gw = new Gameweek(league)
                            {
                                Number = matchday,
                            };
                            gameweeks.Add(gw);
                        }
                        fixture.Gameweek = gw;
                        gw.Fixtures = gw.Fixtures.Append(fixture);


                        // Update Team statistics
                        if (fixture.HomeTeam != null && fixture.AwayTeam != null)
                        {
                            fixture.HomeTeam.UpdateStatisticsBasedOnFixture(fixture);
                            fixture.AwayTeam.UpdateStatisticsBasedOnFixture(fixture);
                        }
                        else
                        {

                        }
                    }
                    catch (Exception ex)
                    {
                        throw;
                    }
                }
            }
            

            
            league.Teams = leagueTeams.ToArray();
            league.Gameweeks = gameweeks.ToArray();
            return league;
        }
        public static int GetPlayedMinutesBeforeFixtureForTeam(this LeagueTeam team, Fixture fixture)
        {
            var playedMinutes = 0;
            var league = fixture.Gameweek.League;
            for (var i = 0; i < league.Gameweeks.Length; i++)
            {
                var gw = league.Gameweeks[i];
                if (gw.Number > fixture.Gameweek.Number)
                    break;

                var fixtures = team.GetFixturesByTeam(gw.Fixtures).OrderBy(x => x.Time);
                foreach (var fix in fixtures)
                {
                    if (fix == fixture)
                        break;
                    playedMinutes += fix.Statistics.PlayedMinutes;
                }
            }
            return playedMinutes;
        }
 public static LeagueTeam GetLeagueTeam(this Player player, Fixture fixture)
 {
     return GetLeagueTeam(player, fixture.Gameweek);
 }
 public static bool HasHomeTeamAdvantage(this Team team, Fixture fixture)
 {
     if (team.ID == fixture.HomeTeam?.Team?.ID)
         return true;
     return false;
 }
 public static bool HasHomeTeamAdvantage(this Player player, Fixture fixture)
 {
     if (player != null)
     {
         //if (player.Team.ID == fixture.HomeTeam.Team.ID)
         //    return true;
         return HasHomeTeamAdvantage(player.Team, fixture);
     }
     return false;
 }
        public static TeamFixtureOutcome GetTeamFixtureOutcome(this LeagueTeam team, Fixture fixture)
        {
            var res = TeamFixtureOutcome.None;
            if (fixture.HomeTeam == null || fixture.AwayTeam == null)
            {
                res = TeamFixtureOutcome.Undetermined;
            }
            else if (!fixture.Statistics.GameFinished)
            {
                res = TeamFixtureOutcome.Undetermined;
            }
            else
            {
                if (fixture.Statistics.Score.GoalsForHomeTeam == fixture.Statistics.Score.GoalsForAwayTeam)
                    res = TeamFixtureOutcome.Draw;
                else
                {
                    var teamIsHome = team.Team.ID == fixture.HomeTeam.Team.ID;
                    var teamIsAway = team.Team.ID == fixture.AwayTeam.Team.ID;

                    if (fixture.Statistics.Score.GoalsForHomeTeam > fixture.Statistics.Score.GoalsForAwayTeam)
                        res = teamIsHome
                            ? TeamFixtureOutcome.Win
                            : teamIsAway
                                ? TeamFixtureOutcome.Loss
                                : TeamFixtureOutcome.Undetermined;
                    else
                        res = teamIsHome
                            ? TeamFixtureOutcome.Loss
                            : teamIsAway
                                ? TeamFixtureOutcome.Win
                                : TeamFixtureOutcome.Undetermined;
                }
            }
            return res;
        }
        private FixtureOdds CalculateOdds(Fixture fixture)
        {
            FixtureOdds odds = null;
            //var provider = TypeContainer.GetInstance<IFixtureOddsProvider>();
            var provider = _settings.FixtureOddsProvider;
            if (provider != null)
            {
                odds = provider.GetFixtureOdds(fixture).WaitForResult();
            }
            return odds;

            //var odds = new FixtureOdds();
            //odds.Fixture = fixture;

            //// todo: implement odds algorithm
            //odds.HomeWin = fixture.HomeTeam.Team.Rating > fixture.AwayTeam.Team.Rating ? 1 : 3;
            //odds.Draw = fixture.HomeTeam.Team.Rating == fixture.AwayTeam.Team.Rating ? 1 : 5;
            //odds.AwayWin = fixture.AwayTeam.Team.Rating > fixture.HomeTeam.Team.Rating ? 1 : 3;

            //// todo: implement prediction
            ////fixtureOdds.PredictedScore =
            //return odds;
        }