//private static long GetNextDefender(Google.Protobuf.Collections.RepeatedField<BattleAction> battleActions)
        //{
        //    foreach (var element in battleActions)
        //        if (element.BattleResults != null)
        //            if (element.BattleResults.NextDefenderPokemonId != -1)
        //                return element.BattleResults.NextDefenderPokemonId;
        //    return -1L;
        //}

        private static GymStartSessionResponse GymBattleStartSession(Client client, FortData gym, ulong defenderId, IEnumerable <ulong> attackersIDs)
        {
            // Sometimes we get a null from startgymBattle so we try to start battle 3 times
            var numTries = 3;
            GymStartSessionResponse gymStartSessionResponse = null;

            do
            {
                try
                {
                    gymStartSessionResponse = client.Fort.GymStartSession(gym.Id, defenderId, attackersIDs).Result;
                }
                catch (Exception ex)
                {
                    Logger.ExceptionInfo("Exception [gymStartSession]:" + ex.Message);
                    return(null);
                }
                switch (gymStartSessionResponse.Result)
                {
                case GymStartSessionResponse.Types.Result.Unset:
                    Logger.Info("(Gym) Failed with error UNSET");
                    break;

                case GymStartSessionResponse.Types.Result.Success:
                    return(gymStartSessionResponse);

                case GymStartSessionResponse.Types.Result.ErrorGymNotFound:
                    Logger.Info("(Gym) Failed with error ERROR_GYM_NOT_FOUND");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorGymNeutral:
                    Logger.Info("(Gym) Failed with error ERROR_GYM_NEUTRAL");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorGymWrongTeam:
                    Logger.Info("(Gym) Failed with error ERROR_GYM_WRONG_TEAM");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorGymEmpty:
                    Logger.Info("(Gym) Failed with error ERROR_GYM_EMPTY");
                    // Call to DeployPokemon
                    break;

                case GymStartSessionResponse.Types.Result.ErrorInvalidDefender:
                    Logger.Info("(Gym) Failed with error ERROR_INVALID_DEFENDER");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorTrainingInvalidAttackerCount:
                    Logger.Info("(Gym) Failed with error ERROR_TRAINING_INVALID_ATTACKER_COUNT");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorAllPokemonFainted:
                    Logger.Info("(Gym) Failed with error ERROR_ALL_POKEMON_FAINTED");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorTooManyBattles:
                    Logger.Info("(Gym) Failed with error ERROR_TOO_MANY_BATTLES");
                    // Set to try later
                    break;

                case GymStartSessionResponse.Types.Result.ErrorTooManyPlayers:
                    Logger.Info("(Gym) Failed with error ERROR_TOO_MANY_PLAYERS");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorGymBattleLockout:
                    Logger.Info("(Gym) Failed with error ERROR_GYM_BATTLE_LOCKOUT");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorPlayerBelowMinimumLevel:
                    Logger.Info("(Gym) Failed with error ERROR_PLAYER_BELOW_MINIMUM_LEVEL");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorNotInRange:
                    Logger.Info("(Gym) Failed with error ERROR_NOT_IN_RANGE");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorPoiInaccessible:
                    Logger.Info("(Gym) Failed with error ERROR_POI_INACCESSIBLE");
                    break;

                case GymStartSessionResponse.Types.Result.ErrorRaidActive:
                    Logger.Info("(Gym) Failed with error ERROR_RAID_ACTIVE");
                    // Go to Battle Raid
                    break;
                }
                Logger.Info("(Gym) Start Gym Failed (" + numTries + "): " + gymStartSessionResponse.Result);
                numTries--;
            } while (numTries > 0 && gymStartSessionResponse != null);

            return(gymStartSessionResponse);
        }
        private static BattleState AttackDefender(GymStartSessionResponse gymBattleStartResponse, Client client, FortData gym)
        {
            PokemonData Attacker = gymBattleStartResponse.Battle.Attacker?.ActivePokemon.PokemonData;
            PokemonData Defender = gymBattleStartResponse.Battle.Defender?.ActivePokemon.PokemonData;

            if (Attacker == null || Defender == null)
            {
                Logger.Info("(Gym) Attacker or defender is NULL!!");
                return(BattleState.StateUnset);
            }

            Int64 battleStartMs = gymBattleStartResponse.Battle.BattleLog.BattleStartTimestampMs;

            IEnumerable <BattleAction> lastActions = gymBattleStartResponse.Battle.BattleLog.BattleActions.ToList();
            BattleAction lastAction = new BattleAction();

            IEnumerable <DownloadItemTemplatesResponse.Types.ItemTemplate> moveSettings = client.Download.GetItemTemplates().ItemTemplates.Where(x => x.MoveSettings != null);

            Logger.ColoredConsoleWrite(gymColorLog, $"(Gym) Battle against {gymBattleStartResponse.Battle.Defender.ActivePokemon.PokemonData.PokemonId}" +
                                       $" | CP: {gymBattleStartResponse.Battle.Defender.ActivePokemon.PokemonData.Cp}");

            int AttackerEnergy = 0;
            int DefenderEnergy = 0;

            while (true)
            {
                lastAction = lastActions.LastOrDefault();
                var AttackerMove1Settings         = moveSettings.FirstOrDefault(x => x.MoveSettings.MovementId == gymBattleStartResponse.Battle.Attacker.ActivePokemon.PokemonData.Move1).MoveSettings;
                var AttackerMove2Settings         = moveSettings.FirstOrDefault(x => x.MoveSettings.MovementId == gymBattleStartResponse.Battle.Attacker.ActivePokemon.PokemonData.Move2).MoveSettings;
                List <BattleAction> battleActions = new List <BattleAction>();
                BattleAction        battleAction  = new BattleAction();

                battleAction.ActionStartMs   = DateTime.UtcNow.ToUnixTime(); //battleStartMs + RandomNumbers.Next(110, 170);
                battleAction.TargetIndex     = -1;                           // What is that?
                battleAction.ActivePokemonId = Attacker.Id;
                battleAction.TargetPokemonId = Defender.Id;

                // Selection of attacks
                // two each ten times we try to evade attack
                if (RandomNumbers.Next(1, 10) <= 2 || DefenderEnergy == 100)
                {
                    var dodgeAction = new BattleAction();
                    dodgeAction.Type          = BattleActionType.ActionDodge;
                    dodgeAction.ActionStartMs = battleAction.ActionStartMs;
                    dodgeAction.DurationMs    = 500;
                    //dodgeAction.EnergyDelta = ??
                    dodgeAction.TargetIndex     = battleAction.TargetIndex;
                    dodgeAction.TargetPokemonId = battleAction.TargetPokemonId;
                    dodgeAction.ActivePokemonId = battleAction.ActivePokemonId;
                    battleActions.Add(dodgeAction);
                    Logger.Debug("Dodge Action Added");
                    battleAction.ActionStartMs = dodgeAction.ActionStartMs + dodgeAction.DurationMs;
                }
                if (AttackerEnergy >= Math.Abs(AttackerMove2Settings.EnergyDelta))
                {
                    var specialAttack = new BattleAction();
                    specialAttack.Type            = BattleActionType.ActionSpecialAttack;
                    specialAttack.ActionStartMs   = battleAction.ActionStartMs;
                    specialAttack.DurationMs      = AttackerMove2Settings.DurationMs;
                    specialAttack.EnergyDelta     = AttackerMove2Settings.EnergyDelta;
                    specialAttack.TargetIndex     = battleAction.TargetIndex;
                    specialAttack.TargetPokemonId = battleAction.TargetPokemonId;
                    specialAttack.ActivePokemonId = battleAction.ActivePokemonId;
                    specialAttack.DamageWindowsStartTimestampMs = specialAttack.ActionStartMs + AttackerMove2Settings.DamageWindowStartMs;
                    specialAttack.DamageWindowsEndTimestampMs   = specialAttack.ActionStartMs + AttackerMove2Settings.DamageWindowEndMs;
                    battleActions.Add(specialAttack);
                    Logger.Debug("Special Attack Added");
                    battleAction.ActionStartMs = specialAttack.ActionStartMs + specialAttack.DurationMs;
                }
                else
                {
                    // One each nine times we do not attack
                    if (RandomNumbers.Next(1, 9) != 1)
                    {
                        var normalAttack = new BattleAction();
                        normalAttack.Type            = BattleActionType.ActionAttack;
                        normalAttack.ActionStartMs   = battleAction.ActionStartMs;
                        normalAttack.TargetIndex     = battleAction.TargetIndex;
                        normalAttack.TargetPokemonId = battleAction.TargetPokemonId;
                        normalAttack.ActivePokemonId = battleAction.ActivePokemonId;
                        normalAttack.DurationMs      = battleAction.DurationMs;
                        normalAttack.DamageWindowsStartTimestampMs = normalAttack.ActionStartMs + AttackerMove1Settings.DamageWindowStartMs;
                        normalAttack.DamageWindowsEndTimestampMs   = normalAttack.ActionStartMs + AttackerMove1Settings.DamageWindowEndMs;
                        normalAttack.EnergyDelta = AttackerMove1Settings.EnergyDelta;
                        battleActions.Add(normalAttack);
                        Logger.Debug("Normal Attack Added");
                        battleAction.ActionStartMs = normalAttack.ActionStartMs + normalAttack.DurationMs;
                    }
                }

                long TimeBeforeAttack = DateTime.UtcNow.ToUnixTime();
                GymBattleAttackResponse attackResponse = client.Fort.GymBattleAttack(gym.Id, gymBattleStartResponse.Battle.BattleId, battleActions, lastAction, battleStartMs);
                long TimeAfterAttack = DateTime.UtcNow.ToUnixTime();

                var AttackDuration = battleActions.Sum(x => x.DurationMs);
                //if (battleActions.Any(a => a.Type != BattleActionType.ActionSpecialAttack)) AttackDuration = AttackDuration - (int)(TimeAfterAttack - TimeBeforeAttack);
                //if (battleActions.Any(a => a.Type == BattleActionType.ActionSwapPokemon)) AttackDuration = AttackDuration + 2000;
                if (AttackDuration > 0)
                {
                    RandomHelper.RandomSleep(AttackDuration);
                }

                if (attackResponse.Result == GymBattleAttackResponse.Types.Result.Success)
                {
                    if (attackResponse.BattleUpdate.BattleLog != null && attackResponse.BattleUpdate.BattleLog.BattleActions.Count > 0)
                    {
                        lastActions = attackResponse.BattleUpdate.BattleLog.BattleActions.OrderBy(o => o.ActionStartMs).Distinct();
                    }
                    battleStartMs = attackResponse.BattleUpdate.BattleLog.ServerMs;

                    switch (attackResponse.BattleUpdate.BattleLog.State)
                    {
                    case BattleState.Active:
                        if (attackResponse.BattleUpdate.ActiveAttacker != null && attackResponse.BattleUpdate.ActiveDefender != null)
                        {
                            AttackerEnergy = attackResponse.BattleUpdate.ActiveAttacker.CurrentEnergy;
                            var AttackerHealth = attackResponse.BattleUpdate.ActiveAttacker.CurrentHealth;
                            var ActiveAttacker = attackResponse.BattleUpdate.ActiveAttacker.PokemonData.PokemonId;
                            DefenderEnergy = attackResponse.BattleUpdate.ActiveDefender.CurrentEnergy;
                            var DefenderHealth = attackResponse.BattleUpdate.ActiveDefender.CurrentHealth;
                            var ActiveDefender = attackResponse.BattleUpdate.ActiveDefender.PokemonData.PokemonId;
                            Defender = attackResponse.BattleUpdate.ActiveDefender?.PokemonData;
                            Attacker = attackResponse.BattleUpdate.ActiveAttacker?.PokemonData;

                            Logger.Info($"(Gym) - Attacker: {ActiveAttacker.ToString().PadLeft(15)}|E={AttackerEnergy.ToString().PadLeft(5)}|H={AttackerHealth.ToString().PadLeft(5)} " +
                                        $"vs. Defender: {ActiveDefender.ToString().PadLeft(15)}|E={DefenderEnergy.ToString().PadLeft(5)}|H={DefenderHealth.ToString().PadLeft(5)}");

                            if (true)
                            {
                                //Logger.Info($"(Gym) Battle timing. battleStartMs {battleStartMs} | TimeBefore {TimeBeforeAttack} | TimeAfter {TimeAfterAttack} | Duration {AttackDuration}");
                                foreach (BattleAction ba in battleActions)
                                {
                                    Logger.Info($"(Gym) Action -> {ba.ActionStartMs}|{ba.DurationMs.ToString().PadLeft(4)}|{ba.Type}|{ba.DamageWindowsStartTimestampMs}|{ba.DamageWindowsEndTimestampMs}");
                                }
                            }
                        }
                        break;

                    case BattleState.Defeated:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - We have lost");
                        return(BattleState.Defeated);

                    case BattleState.Victory:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Defender " + Defender.PokemonId + " defeated.");
                        foreach (var element in attackResponse.BattleUpdate.BattleLog.BattleActions)
                        {
                            if (element.BattleResults != null)
                            {
                                Logger.Info("(Gym) - Gym points: " + element.BattleResults.GymPointsDelta);
                                Logger.Info("(Gym) - Experience Awarded: " + element.BattleResults.PlayerXpAwarded);
                                Logger.Debug("(Gym) - Next Pokemon: " + element.BattleResults.NextDefenderPokemonId);
                            }
                        }
                        return(BattleState.Victory);

                    case BattleState.TimedOut:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Timed Out");
                        return(BattleState.TimedOut);

                    case BattleState.StateUnset:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Status UNSET");
                        return(BattleState.StateUnset);
                    }
                }
                else
                {
                    switch (attackResponse.Result)
                    {
                    case GymBattleAttackResponse.Types.Result.ErrorInvalidAttackActions:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Attack - ERROR_INVALID_ATTACK_ACTIONS");
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) BattleActions: " + string.Join(", ", (IEnumerable <BattleAction>)battleActions.ToArray()));
                        break;

                    case GymBattleAttackResponse.Types.Result.ErrorNotInRange:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Attack - ERROR_NOT_IN_RANGE");
                        return(BattleState.StateUnset);

                    case GymBattleAttackResponse.Types.Result.ErrorRaidActive:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Attack - ERROR_RAID_ACTIVE");
                        return(BattleState.StateUnset);

                    case GymBattleAttackResponse.Types.Result.ErrorWrongBattleType:
                        Logger.ColoredConsoleWrite(gymColorLog, "(Gym) - Attack - ERROR_WRONG_BATTLE_TYPE");
                        return(BattleState.StateUnset);
                    }

                    //return BattleState.StateUnset;
                }
            }
        }
        public static BattleState AttackGym(FortData gym, Client client)
        {
            GymsLogic.StopAttack = false;
            GymGetInfoResponse      gymGetInfoResponse      = null;
            GymStartSessionResponse gymStartSessionResponse = null;

            gymGetInfoResponse = client.Fort.GymGetInfo(gym.Id, gym.Latitude, gym.Longitude);

            // Get defenders info
            int currentDefender = 0;
            var defenders       = gymGetInfoResponse.GymStatusAndDefenders.GymDefender.Select(x => x.MotivatedPokemon.Pokemon).ToList();
            var defender        = gymGetInfoResponse.GymStatusAndDefenders.GymDefender[currentDefender];

            for (int i = 0; i < defenders.Count(); i++)
            {
                Logger.Debug($"(Gym) Defender {i + 1}: {gymGetInfoResponse.GymStatusAndDefenders.GymDefender[i].MotivatedPokemon.Pokemon.PokemonId}" +
                             $" | CpNow: {gymGetInfoResponse.GymStatusAndDefenders.GymDefender[i].MotivatedPokemon.CpNow}" +
                             $" | MotivationNow: {gymGetInfoResponse.GymStatusAndDefenders.GymDefender[i].MotivatedPokemon.MotivationNow}" +
                             $" | Id: {gymGetInfoResponse.GymStatusAndDefenders.GymDefender[i].MotivatedPokemon.Pokemon.Id}");
            }

            // Get atackers
            IEnumerable <PokemonData> selectedAttackers = getPokeAttackers(defender.MotivatedPokemon.Pokemon, GlobalVars.Gyms.Attackers);
            var pokeAttackers    = selectedAttackers as PokemonData[] ?? selectedAttackers.ToArray();
            var pokeAttackersIds = selectedAttackers.Select(x => x.Id);

            if (selectedAttackers.Count() < 6)
            {
                Logger.ColoredConsoleWrite(gymColorLog, "(Gym) Not enougth pokemons to fight.");
                return(BattleState.StateUnset);
            }

            Logger.ColoredConsoleWrite(gymColorLog, "(Gym) Defender: " + GymsLogic.strPokemon(defender.MotivatedPokemon.Pokemon) + $" [{defender.TrainerPublicProfile.Name} ({defender.TrainerPublicProfile.Level})]");
            GymsLogic.ShowPokemons(selectedAttackers);

            while (currentDefender < defenders.Count())
            {
                try
                {
                    // Initialization of attach agains a defender
                    gymStartSessionResponse = GymBattleStartSession(client, gym, defenders[currentDefender].Id, pokeAttackersIds);
                }
                catch (Exception ex)
                {
                    Logger.ExceptionInfo($"Can't start battle against defender {currentDefender}: " + ex.Message);
                    break;
                }

                if (gymStartSessionResponse == null || gymStartSessionResponse.Battle == null || gymStartSessionResponse.Result != GymStartSessionResponse.Types.Result.Success)
                {
                    return(BattleState.StateUnset);
                }

                // Battle loop
                if (gymStartSessionResponse.Battle.BattleLog.State == BattleState.Active)
                {
                    //Logger.ColoredConsoleWrite(gymColorLog, "(Gym) Battle Started");
                    RandomHelper.RandomSleep(2000);

                    // Attack each defender
                    BattleState attackDefender = AttackDefender(gymStartSessionResponse, client, gym);
                    if (attackDefender == BattleState.Victory)
                    {
                        currentDefender++;
                    }
                    else
                    {
                        return(attackDefender);
                    }
                }
            }
            return(BattleState.Victory);
        }