public async Task <IActionResult> GetAvailableBacklineDefense(short x, short y, int?maxTravelSeconds)
        {
            var currentVillages = await(
                from user in CurrentSets.ActiveUser
                join village in CurrentSets.Village on user.PlayerId equals village.PlayerId
                join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyAtHome).Include(cv => cv.ArmyOwned)
                on village.VillageId equals currentVillage.VillageId
                where currentVillage.ArmyAtHomeId != null && currentVillage.ArmyOwnedId != null
                select new { Village = village, CurrentVillage = currentVillage }
                ).ToListAsync();

            var enemyVillages = await(
                from enemy in CurrentSets.EnemyTribe
                join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId
                join village in CurrentSets.Village on player.PlayerId equals village.PlayerId
                select new Coordinate {
                X = village.X.Value, Y = village.Y.Value
            }
                ).ToListAsync();

            //  Ignore current villages where < 10% of their troops are at home
            currentVillages = currentVillages
                              // + 1 on army owned so we don't divide by 0 accidentally
                              .Where(v => ArmyStats.CalculateTotalPopulation(v.CurrentVillage.ArmyAtHome) / (float)(ArmyStats.CalculateTotalPopulation(v.CurrentVillage.ArmyOwned) + 1) > 0.2f)
                              .ToList();

            var enemyMap        = new Features.Spatial.Quadtree(enemyVillages);
            var backlineSupport = currentVillages
                                  .Where(s => !enemyMap.ContainsInRange(s.Village.X.Value, s.Village.Y.Value, 10))
                                  .ToDictionary(s => s.Village, s => s.CurrentVillage);

            var planner = new Features.Planning.CommandOptionsCalculator(CurrentWorldSettings);

            planner.Requirements.Add(new MinimumDefenseRequirement {
                MinimumDefense = 10000
            }.LimitTroopType(ArmyStats.DefensiveTroopTypes));
            if (maxTravelSeconds != null)
            {
                planner.Requirements.Add(new MaximumTravelTimeRequirement {
                    MaximumTime = TimeSpan.FromSeconds(maxTravelSeconds.Value)
                });
            }

            var options = planner.GenerateOptions(backlineSupport, new Village {
                X = x, Y = y
            });

            var villageIds = options.Select(o => o.SendFrom).Distinct().ToList();
            var playerIds  = await CurrentSets.Village.Where(v => villageIds.Contains(v.VillageId)).Select(v => v.PlayerId.Value).Distinct().ToListAsync();

            if (playerIds.Contains(CurrentPlayerId))
            {
                playerIds.Remove(CurrentPlayerId);
            }


            var players = await CurrentSets.Player.Where(p => playerIds.Contains(p.PlayerId)).Select(p => new { PlayerName = p.PlayerName.UrlDecode(), p.PlayerId }).ToListAsync();

            return(Ok(players));
        }
        public async Task <IActionResult> GetSuggestedActions()
        {
            PreloadWorldData();
            PreloadTranslationData();

            var serverTime          = CurrentServerTime;
            var twoDaysAgo          = serverTime - TimeSpan.FromDays(2);
            var twoDaysAgoTimestamp = new DateTimeOffset(twoDaysAgo).ToUnixTimeSeconds();

            var ownVillageData = await(
                from village in CurrentSets.Village
                join currentVillage in CurrentSets.CurrentVillage
                on village.VillageId equals currentVillage.VillageId
                where village.PlayerId == CurrentPlayerId
                select new { X = village.X.Value, Y = village.Y.Value, village.VillageId, VillageName = village.VillageName.UrlDecode(), currentVillage.ArmyAtHome, currentVillage.ArmyStationed }
                ).ToListAsync();

            var vaultPlayerIds = await CurrentSets.ActiveUser.Select(u => u.PlayerId).ToListAsync();

            var ownVillageMap = new Features.Spatial.Quadtree(ownVillageData.Select(v => new Coordinate {
                X = v.X, Y = v.Y
            }));

            async Task <object> GetRecapSuggestions(CurrentContextDbSets CurrentSets)
            {
                var capturedVillages = await(
                    from conquer in CurrentSets.Conquer
                    join sourcePlayer in CurrentSets.ActiveUser on conquer.OldOwner equals sourcePlayer.PlayerId
                    join village in CurrentSets.Village on conquer.VillageId equals village.VillageId
                    where conquer.UnixTimestamp > twoDaysAgoTimestamp
                    where conquer.NewOwner == null || !vaultPlayerIds.Contains(conquer.NewOwner.Value)
                    where conquer.NewOwner == village.PlayerId
                    select new { X = village.X.Value, Y = village.Y.Value, VillageId = conquer.VillageId, village.VillageName, conquer.OldOwner, conquer.NewOwner, OccurredAt = DateTimeOffset.FromUnixTimeSeconds(conquer.UnixTimestamp).UtcDateTime }
                    ).ToListAsync();

                capturedVillages = capturedVillages
                                   .GroupBy(v => v.VillageId)
                                   .Select(g => g.OrderByDescending(v => v.OccurredAt).First())
                                   .ToList();

                var villageIds       = capturedVillages.Select(v => v.VillageId).ToList();
                var noblesToVillages = await(
                    from user in CurrentSets.ActiveUser
                    join command in CurrentSets.Command on user.PlayerId equals command.SourcePlayerId
                    where villageIds.Contains(command.TargetVillageId)
                    where command.ArmyId != null && command.Army.Snob > 0
                    where command.LandsAt > serverTime
                    select command
                    ).ToListAsync();

                var noblesToVillagesById = noblesToVillages.GroupBy(v => v.TargetVillageId).ToDictionary(g => g.Key, g => g.Count());

                var relevantPlayerIds = capturedVillages
                                        .Select(v => v.OldOwner.Value)
                                        .Concat(capturedVillages.Where(v => v.NewOwner != null).Select(v => v.NewOwner.Value))
                                        .Distinct()
                                        .ToList();

                var playerNamesById = await
                                      CurrentSets.Player.Where(p => relevantPlayerIds.Contains(p.PlayerId))
                                      .ToDictionaryAsync(p => p.PlayerId, p => p.PlayerName);

                var loyaltyCalculator = new Features.Simulation.LoyaltyCalculator(CurrentWorldSettings.GameSpeed);
                var possibleLoyalties = capturedVillages.ToDictionary(
                    v => v.VillageId,
                    v => loyaltyCalculator.PossibleLoyalty(25, serverTime - v.OccurredAt)
                    );

                var tlNONE = await TranslateAsync("NONE");

                return(capturedVillages
                       .Where(v => possibleLoyalties[v.VillageId] < 100)
                       .Where(v => noblesToVillagesById.GetValueOrDefault(v.VillageId, 0) * CurrentWorldSettings.NoblemanLoyaltyMax < possibleLoyalties[v.VillageId])
                       .Select(v => new
                {
                    v.OccurredAt,
                    v.X, v.Y, v.VillageId,
                    VillageName = v.VillageName.UrlDecode(),
                    OldOwnerId = v.OldOwner, NewOwnerId = v.NewOwner,
                    OldOwnerName = playerNamesById.GetValueOrDefault(v.OldOwner ?? -1, tlNONE).UrlDecode(),
                    NewOwnerName = playerNamesById.GetValueOrDefault(v.NewOwner ?? -1, tlNONE).UrlDecode(),
                    IsNearby = ownVillageMap.ContainsInRange(v.X, v.Y, 5),
                    Loyalty = possibleLoyalties[v.VillageId]
                })
                       .OrderBy(v => v.Loyalty)
                       .ToList());
            }

            async Task <object> GetSnipeSuggestions(CurrentContextDbSets CurrentSets)
            {
                var incomingNobles = await(
                    from user in CurrentSets.ActiveUser
                    join command in CurrentSets.Command on user.PlayerId equals command.TargetPlayerId
                    where command.IsAttack
                    where command.TroopType == "snob"
                    where command.LandsAt > serverTime
                    where !command.IsReturning
                    select new { command.TargetVillageId, command.LandsAt }
                    ).ToListAsync();

                var incomingTrains = incomingNobles
                                     .GroupBy(n => n.TargetVillageId)
                                     .ToDictionary(
                    g => g.Key,
                    g => g
                    .OrderBy(n => n.LandsAt)
                    .GroupWhile((prev, curr) => prev.LandsAt - curr.LandsAt < TimeSpan.FromSeconds(1))
                    .Select(t =>
                {
                    var train = t.ToList();
                    return(new { train.First().LandsAt, Range = train.Last().LandsAt - train.First().LandsAt, Train = train });
                })
                    .ToList()
                    );

                var targetVillageIds  = incomingTrains.Keys.ToList();
                var targetVillageInfo = await(
                    from village in CurrentSets.Village
                    join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyStationed) on village.VillageId equals currentVillage.VillageId
                    where targetVillageIds.Contains(village.VillageId)
                    select new { Village = village, CurrentVillage = currentVillage }
                    ).ToListAsync();

                var ownVillages = await(
                    from village in CurrentSets.Village
                    join currentVillage in CurrentSets.CurrentVillage.Include(cv => cv.ArmyAtHome) on village.VillageId equals currentVillage.VillageId
                    where village.PlayerId == CurrentPlayerId
                    where currentVillage.ArmyAtHomeId != null
                    select new { Village = village, CurrentVillage = currentVillage }
                    ).ToDictionaryAsync(d => d.Village, d => d.CurrentVillage);

                var planner         = new Features.Planning.CommandOptionsCalculator(CurrentWorldSettings);
                var timeRequirement = new MaximumTravelTimeRequirement();

                planner.Requirements.Add(new MinimumDefenseRequirement {
                    MinimumDefense = 10000
                }.LimitTroopType(ArmyStats.DefensiveTroopTypes));
                planner.Requirements.Add(timeRequirement);

                return(targetVillageInfo
                       // Don't bother sniping stacked villas
                       .Where(info =>
                {
                    var defPop = ArmyStats.CalculateTotalPopulation(info.CurrentVillage.ArmyStationed, ArmyStats.DefensiveTroopTypes);
                    var numDVsStationed = defPop / (float)Features.CommandClassification.Utils.FullArmyPopulation;
                    return numDVsStationed < 2;
                })
                       // Make a plan for each train
                       .SelectMany(info => incomingTrains[info.Village.VillageId].Select(train =>
                {
                    timeRequirement.MaximumTime = train.LandsAt - serverTime;
                    var plan = planner.GenerateOptions(ownVillages, info.Village).Take(100).ToList();
                    return new
                    {
                        info.Village,
                        Train = train,
                        Plan = plan
                    };
                }))
                       // Ignore plans without any instructions
                       .Where(info => info.Plan.Count > 0)
                       // For each train and target village, return a plan with info on the villa that needs to be sniped
                       .Select(info => new
                {
                    Plan = info.Plan,
                    Train = info.Train.Train,
                    LandsAt = info.Train.LandsAt,
                    TargetVillage = new {
                        Id = info.Village.VillageId,
                        Name = info.Village.VillageName.UrlDecode(),
                        X = info.Village.X.Value,
                        Y = info.Village.Y.Value
                    }
                })
                       .OrderBy(info => info.LandsAt)
                       .ToList());
            }

            async Task <object> GetStackSuggestions(CurrentContextDbSets CurrentSets)
            {
                var enemyVillages = await(
                    from enemy in CurrentSets.EnemyTribe
                    join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId
                    join village in CurrentSets.Village on player.PlayerId equals village.PlayerId
                    select new Coordinate {
                    X = village.X.Value, Y = village.Y.Value
                }
                    ).ToListAsync();

                var enemyMap            = new Features.Spatial.Quadtree(enemyVillages);
                var frontlineVillages   = ownVillageData.Where(v => enemyMap.ContainsInRange(v.X, v.Y, 4.0f)).ToList();
                var frontlineVillageIds = frontlineVillages.Select(v => v.VillageId).ToList();

                var attacksOnFrontline = await(
                    from command in CurrentSets.Command
                    where frontlineVillageIds.Contains(command.TargetVillageId)
                    where command.IsAttack
                    where command.LandsAt > serverTime
                    select new { command.SourceVillageId, command.TargetVillageId }
                    ).ToListAsync();

                var supportToFrontline = await(
                    from support in CurrentSets.Command
                    where frontlineVillageIds.Contains(support.TargetVillageId)
                    where support.LandsAt > serverTime
                    where support.ArmyId != null
                    select new { support.TargetVillageId, support.Army }
                    ).ToListAsync();

                var attackingVillageIds = attacksOnFrontline.Select(c => c.SourceVillageId).Distinct().ToList();
                var attackingVillages   = await CurrentSets
                                          .CurrentVillage
                                          .Where(v => attackingVillageIds.Contains(v.VillageId))
                                          .Select(v => new { v.VillageId, v.ArmyOwned, v.ArmyStationed, v.ArmyTraveling })
                                          .ToDictionaryAsync(
                    v => v.VillageId,
                    v => v
                    );

                var attackingVillagesWithNukes = attackingVillages
                                                 // Get possible troops from village
                                                 .Select(kvp => new { kvp.Key, Army = JSON.Army.Max(kvp.Value.ArmyOwned, kvp.Value.ArmyStationed, kvp.Value.ArmyTraveling) })
                                                 // Get population of offensive troops
                                                 .Select(kvp => new { kvp.Key, OffensivePop = ArmyStats.CalculateTotalPopulation(kvp.Army, ArmyStats.OffensiveTroopTypes.Except(new[] { JSON.TroopType.Heavy }).ToArray()) })
                                                 // Filter by full nukes
                                                 .Where(kvp => kvp.OffensivePop > 0.65f * Features.CommandClassification.Utils.FullArmyPopulation)
                                                 .Select(kvp => kvp.Key)
                                                 .ToList();

                var nukesSentPerVillage = frontlineVillageIds.ToDictionary(id => id, id => 0);

                foreach (var attack in attacksOnFrontline.Where(a => attackingVillagesWithNukes.Contains(a.SourceVillageId)))
                {
                    nukesSentPerVillage[attack.TargetVillageId]++;
                }

                var pendingSupportPerVillage = frontlineVillageIds.ToDictionary(id => id, id => new JSON.Army());

                foreach (var support in supportToFrontline)
                {
                    pendingSupportPerVillage[support.TargetVillageId] += support.Army;
                }

                var battleSimulator        = new Features.Simulation.BattleSimulator();
                var nukesEatablePerVillage = frontlineVillages.ToDictionary(
                    v => v.VillageId,
                    v => battleSimulator.EstimateRequiredNukes(v.ArmyStationed + pendingSupportPerVillage[v.VillageId], 20, CurrentWorldSettings.ArchersEnabled, 100).NukesRequired
                    );

                return(frontlineVillages
                       .Where(v => nukesSentPerVillage[v.VillageId] > 0 && nukesEatablePerVillage[v.VillageId] - nukesSentPerVillage[v.VillageId] < 2)
                       .Select(v => new
                {
                    v.VillageId,
                    v.VillageName,
                    v.X, v.Y,
                    SentNukes = nukesSentPerVillage[v.VillageId],
                    EatableNukes = nukesEatablePerVillage[v.VillageId]
                })
                       .OrderByDescending(v => v.SentNukes - v.EatableNukes)
                       .ThenBy(v => v.VillageId)
                       .ToList());
            }

            async Task <object> GetNobleTargetSuggestions(CurrentContextDbSets CurrentSets)
            {
                var villasWithNobles = await(
                    from village in CurrentSets.Village
                    join currentVillage in CurrentSets.CurrentVillage on village.VillageId equals currentVillage.VillageId
                    where currentVillage.ArmyOwned.Snob > 0
                    where village.PlayerId == CurrentPlayerId
                    select new Coordinate {
                    X = village.X.Value, Y = village.Y.Value
                }
                    ).ToListAsync();

                var enemyCurrentVillas = await(
                    from currentVillage in CurrentSets.CurrentVillage
                    join village in CurrentSets.Village on currentVillage.VillageId equals village.VillageId
                    where !CurrentSets.ActiveUser.Any(au => au.PlayerId == village.PlayerId)
                    select new { X = village.X.Value, Y = village.Y.Value, village.VillageId, village.Points, currentVillage.Loyalty, currentVillage.LoyaltyLastUpdated, currentVillage.ArmyStationed, village.PlayerId, VillageName = village.VillageName.UrlDecode(), PlayerName = village.Player.PlayerName.UrlDecode() }
                    ).ToListAsync();

                var villageMap = new Features.Spatial.Quadtree(villasWithNobles);

                var loyaltyCalculator = new Features.Simulation.LoyaltyCalculator(CurrentWorldSettings.GameSpeed);
                var possibleTargets   = enemyCurrentVillas
                                        .Where(v => villageMap.ContainsInRange(v.X, v.Y, 7.5f)) // Only consider enemy villas within 7.5 fields of any villa with nobles
                                        .Select(v =>
                {
                    var possibleLoyalty = v.Loyalty.HasValue
                            ? loyaltyCalculator.PossibleLoyalty(v.Loyalty.Value, serverTime - v.LoyaltyLastUpdated.Value)
                            : 100;

                    var stationedDVs = ArmyStats.CalculateTotalPopulation(v.ArmyStationed, ArmyStats.DefensiveTroopTypes) / (float)Features.CommandClassification.Utils.FullArmyPopulation;

                    // Select "confidence" in selecting the given target as a suggestion
                    // If < 0.75 DV stationed or loyalty under 50, 100% confident in the suggestion
                    var loyaltyConfidence = 1.0f - (possibleLoyalty - 50) / 50.0f;
                    var stackConfidence   = 1.0f - (stationedDVs - 0.75f) / 0.75f;

                    if (v.ArmyStationed?.LastUpdated != null)
                    {
                        var stackAge     = serverTime - v.ArmyStationed.LastUpdated.Value;
                        var ageFactor    = (TimeSpan.FromHours(48) - stackAge) / TimeSpan.FromHours(48);
                        stackConfidence *= Math.Max(0, (float)Math.Pow(Math.Abs(ageFactor), 0.5f) * Math.Sign(ageFactor));
                    }
                    else
                    {
                        stackConfidence = 0;
                    }

                    return(new
                    {
                        Loyalty = possibleLoyalty,
                        StationedDVs = stationedDVs,
                        DVsSeenAt = v.ArmyStationed?.LastUpdated,
                        Confidence = loyaltyConfidence + stackConfidence,
                        Village = v
                    });
                });

                var confidentTargets = possibleTargets.Where(t => t.Confidence > 1).ToList();

                return(confidentTargets
                       .Select(t => new
                {
                    t.Village.X,
                    t.Village.Y,
                    t.Village.VillageId,
                    t.Village.VillageName,
                    t.Village.PlayerId,
                    t.Village.PlayerName,
                    t.Village.Points,
                    t.Loyalty,
                    t.StationedDVs,
                    t.DVsSeenAt,
                    t.Confidence
                })
                       .OrderByDescending(t => t.Confidence)
                       .ThenBy(t => t.VillageId)
                       .ToList());
            }

            async Task <object> GetUselessStackSuggestions(CurrentContextDbSets CurrentSets)
            {
                var enemyVillages = await(
                    from enemy in CurrentSets.EnemyTribe
                    join player in CurrentSets.Player on enemy.EnemyTribeId equals player.TribeId
                    join village in CurrentSets.Village on player.PlayerId equals village.PlayerId
                    select new Coordinate {
                    X = village.X.Value, Y = village.Y.Value
                }
                    ).ToListAsync();

                var ownSupport = await(
                    from support in CurrentSets.CurrentVillageSupport
                    join targetVillage in CurrentSets.Village on support.TargetVillageId equals targetVillage.VillageId
                    join sourceVillage in CurrentSets.Village on support.SourceVillageId equals sourceVillage.VillageId
                    where targetVillage.PlayerId == null || !vaultPlayerIds.Contains(targetVillage.PlayerId.Value)
                    where sourceVillage.PlayerId == CurrentPlayerId
                    select new { support.SupportingArmy, targetVillage.VillageId }
                    ).ToListAsync();

                var notOwnVillageIds = ownSupport.Select(s => s.VillageId).Distinct().ToList();
                var notOwnVillages   = await(
                    from village in CurrentSets.Village
                    where notOwnVillageIds.Contains(village.VillageId)
                    select new { X = village.X.Value, Y = village.Y.Value, VillageName = village.VillageName.UrlDecode(), village.VillageId }
                    ).ToListAsync();

                var enemyMap = new Features.Spatial.Quadtree(enemyVillages);

                var backlineVillages = ownVillageData.Where(v => !enemyMap.ContainsInRange(new Coordinate {
                    X = v.X, Y = v.Y
                }, 10));

                var stackInfo = backlineVillages
                                // Select backline villages with over 3k pop of support
                                .Select(v => new { Village = new { v.X, v.Y, v.VillageName, v.VillageId }, Population = ArmyStats.CalculateTotalPopulation((JSON.Army)v.ArmyStationed - (JSON.Army)v.ArmyAtHome) })
                                .Where(v => v.Population > 3000)
                                .Select(v => new { v.Village.X, v.Village.Y, v.Village.VillageName, v.Village.VillageId, PopCount = v.Population })
                                // Include support to unknown villages
                                .Concat(notOwnVillages.Select(v => new { v.X, v.Y, v.VillageName, v.VillageId, PopCount = ownSupport.Where(s => s.VillageId == v.VillageId).Sum(s => ArmyStats.CalculateTotalPopulation(s.SupportingArmy)) }))
                                .ToList();

                var villageIds        = stackInfo.Select(i => i.VillageId).Distinct().ToList();
                var villageOwnersById = await(
                    from village in CurrentSets.Village
                    join player in CurrentSets.Player on village.PlayerId equals player.PlayerId
                    let tribe = (from tribe in CurrentSets.Ally
                                 where tribe.TribeId == player.TribeId
                                 select new { tribe.Tag, tribe.TribeId }).FirstOrDefault()
                                where villageIds.Contains(village.VillageId)
                                select new { village.VillageId, Player = player, Tribe = tribe }
                    ).ToDictionaryAsync(
                    v => v.VillageId,
                    v => new { v.Player.PlayerName, v.Player.PlayerId, TribeName = v.Tribe?.Tag, v.Tribe?.TribeId }
                    );

                return(stackInfo
                       .Select(info =>
                {
                    var owner = villageOwnersById.GetValueOrDefault(info.VillageId);
                    return new
                    {
                        info.VillageId,
                        info.VillageName,
                        info.PopCount,
                        info.X,
                        info.Y,
                        PlayerName = owner?.PlayerName == null ? null : owner.PlayerName.UrlDecode(),
                        owner?.PlayerId,
                        TribeName = owner?.TribeName == null ? null : owner.TribeName.UrlDecode(),
                        owner?.TribeId
                    };
                }));
            }

            (var recaps, var snipes, var stacks, var nobles, var uselessStacks) = await ManyTasks.Run(
                WithTemporarySets(GetRecapSuggestions),
                WithTemporarySets(GetSnipeSuggestions),
                WithTemporarySets(GetStackSuggestions),
                WithTemporarySets(GetNobleTargetSuggestions),
                WithTemporarySets(GetUselessStackSuggestions)
                );

            return(Ok(new
            {
                Recaps = recaps,
                Snipes = snipes,
                Stacks = stacks,
                NobleTargets = nobles,
                UselessStacks = uselessStacks
            }));
        }