// Path: src/main/java/pl/joegreen/sergeants/api/response/GameStartApiResponse.java
// @Value
// public class GameStartApiResponse {
//     /**
//      * Index of bot's player.
//      */
//     private int playerIndex;
//     /**
//      * Replay identifier. Use http://bot.generals.io/replays/{replayId} to see the replay in your browser.
//      */
//     @JsonProperty("replay_id")
//     private String replayId;
//     /**
//      * Identifier of the main chat room, useful when sending a chat message ({@link GeneralsApi#sendChatMessage(String, String)}).
//      */
//     @JsonProperty("chat_room")
//     private String chatRoom;
//     /**
//      * Identifier of the team chat room, useful when sending a chat message ({@link GeneralsApi#sendChatMessage(String, String)}).
//      * Can be null if there are no teams in the game.
//      */
//     @JsonProperty("team_chat_room")
//     private String teamChatRoom;
//     /**
//      * Array of player names, indexed by player index.
//      */
//     private String[] usernames;
//     /**
//      * Array if team identifiers of each player, indexed by player index. Can be null if there are no teams/
//      */
//     private Integer[] teams;
// 
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/response/GameUpdateApiResponse.java
// @Value
// public class GameUpdateApiResponse {
//     private int turn;
//     /**
//      * Diff to update the array with map (tiles/armies) representation. Use {@link MapPatcher} to apply the diff.
//      */
//     @JsonProperty("map_diff")
//     private int[] mapDiff;
//     /**
//      * Diff to update the array with cities representation. Use {@link MapPatcher} to apply the diff.
//      */
//     @JsonProperty("cities_diff")
//     private int[] citiesDiff;
//     /**
//      * Array of indexes of generals, indexed with player index. If general of player is not visible/does not exist, contains -1 in appropriate position.
//      */
//     private int[] generals;
//     /**
//      * Officially "the attackIndex field is unnecessary for bots -- it's used by our client to show/hide attack arrows when appropriate."
//      * Some people say it can be used to find out state of moves queue and plan more complex moves ;-)
//      */
//     private int attackIndex;
//     /**
//      * Array indexed by player index, includes things shown in the score table (titles, army, is dead). Does not include stars.
//      */
//     private ScoreApiResponse[] scores;
//     private double[] stars;
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/response/ScoreApiResponse.java
// @Value
// public class ScoreApiResponse {
//     /**
//      * Number of map tiles owned.
//      */
//     private int tiles;
//     /**
//      * Total army size.
//      */
//     private int total;
//     /**
//      * Player index.
//      */
//     @JsonProperty("i")
//     private int index;
//     /**
//      * True if player already lost.
//      */
//     private boolean dead;
// 
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/util/MapPatcher.java
// public class MapPatcher {
// 
//     public static int[] patch(int[] patch, int[] old) {
//         ArrayList<Integer> newArrayList = new ArrayList<>();
//         int patchIndex = 0;
//         while (patchIndex < patch.length) {
//             //matching
//             int matchingElements = patch[patchIndex++];
//             int startingIndex = newArrayList.size();
//             for (int oldIndex = startingIndex; oldIndex < startingIndex + matchingElements; ++oldIndex) {
//                 if (oldIndex < old.length) {
//                     newArrayList.add(old[oldIndex]);
//                 } else {
//                     newArrayList.add(0);
//                 }
//             }
// 
//             //mismatching
//             if (patchIndex < patch.length) {
//                 int misMatchingElements = patch[patchIndex++];
//                 while (misMatchingElements > 0) {
//                     newArrayList.add(patch[patchIndex++]);
//                     misMatchingElements--;
//                 }
//             }
//         }
//         return newArrayList.stream().mapToInt(i -> i).toArray();
//     }
// }


import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import pl.joegreen.sergeants.api.response.GameStartApiResponse;
import pl.joegreen.sergeants.api.response.GameUpdateApiResponse;
import pl.joegreen.sergeants.api.response.ScoreApiResponse;
import pl.joegreen.sergeants.api.util.MapPatcher;
import pl.joegreen.sergeants.framework.model.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
    public ImmutableList<Player> getPlayers() {
        return players;
    }

    @Override
    public GameStarted getGameStartedData() {
        return new GameStartedApiResponseImpl(startData);
    }


    private static ImmutableList<Player> createPlayersInfo(GameStartApiResponse startData, GameUpdateApiResponse firstUpdateData) {
        int numberOfPlayers = startData.getUsernames().length;
        Map<Integer, ScoreApiResponse> scoresMap = createScoresMap(firstUpdateData);
        List<Player> players = IntStream.range(0, numberOfPlayers).mapToObj(playerIndex -> {
            Optional<ScoreApiResponse> scoreForPlayer = Optional.ofNullable(scoresMap.get(playerIndex));
            return new Player(playerIndex,
                    startData.getUsernames()[playerIndex],
                    Optional.ofNullable(startData.getTeams()).map(teams -> teams[playerIndex]).orElse(playerIndex),
                    scoreForPlayer.map(ScoreApiResponse::getTiles).orElse(null),
                    scoreForPlayer.map(ScoreApiResponse::getTotal).orElse(null),
                    scoreForPlayer.map(ScoreApiResponse::isDead).orElse(false));
        }).collect(Collectors.toList());
        return ImmutableList.copyOf(players);
    }

    private static Map<Integer, ScoreApiResponse> createScoresMap(GameUpdateApiResponse firstUpdateData) {
        return Arrays.stream(firstUpdateData.getScores()).collect(Collectors.toMap(ScoreApiResponse::getIndex, Function.identity()));
    }


// Path: src/main/java/pl/joegreen/sergeants/api/response/GameStartApiResponse.java
// @Value
// public class GameStartApiResponse {
//     /**
//      * Index of bot's player.
//      */
//     private int playerIndex;
//     /**
//      * Replay identifier. Use http://bot.generals.io/replays/{replayId} to see the replay in your browser.
//      */
//     @JsonProperty("replay_id")
//     private String replayId;
//     /**
//      * Identifier of the main chat room, useful when sending a chat message ({@link GeneralsApi#sendChatMessage(String, String)}).
//      */
//     @JsonProperty("chat_room")
//     private String chatRoom;
//     /**
//      * Identifier of the team chat room, useful when sending a chat message ({@link GeneralsApi#sendChatMessage(String, String)}).
//      * Can be null if there are no teams in the game.
//      */
//     @JsonProperty("team_chat_room")
//     private String teamChatRoom;
//     /**
//      * Array of player names, indexed by player index.
//      */
//     private String[] usernames;
//     /**
//      * Array if team identifiers of each player, indexed by player index. Can be null if there are no teams/
//      */
//     private Integer[] teams;
// 
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/response/GameUpdateApiResponse.java
// @Value
// public class GameUpdateApiResponse {
//     private int turn;
//     /**
//      * Diff to update the array with map (tiles/armies) representation. Use {@link MapPatcher} to apply the diff.
//      */
//     @JsonProperty("map_diff")
//     private int[] mapDiff;
//     /**
//      * Diff to update the array with cities representation. Use {@link MapPatcher} to apply the diff.
//      */
//     @JsonProperty("cities_diff")
//     private int[] citiesDiff;
//     /**
//      * Array of indexes of generals, indexed with player index. If general of player is not visible/does not exist, contains -1 in appropriate position.
//      */
//     private int[] generals;
//     /**
//      * Officially "the attackIndex field is unnecessary for bots -- it's used by our client to show/hide attack arrows when appropriate."
//      * Some people say it can be used to find out state of moves queue and plan more complex moves ;-)
//      */
//     private int attackIndex;
//     /**
//      * Array indexed by player index, includes things shown in the score table (titles, army, is dead). Does not include stars.
//      */
//     private ScoreApiResponse[] scores;
//     private double[] stars;
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/response/ScoreApiResponse.java
// @Value
// public class ScoreApiResponse {
//     /**
//      * Number of map tiles owned.
//      */
//     private int tiles;
//     /**
//      * Total army size.
//      */
//     private int total;
//     /**
//      * Player index.
//      */
//     @JsonProperty("i")
//     private int index;
//     /**
//      * True if player already lost.
//      */
//     private boolean dead;
// 
// }
// 
// Path: src/main/java/pl/joegreen/sergeants/api/util/MapPatcher.java
// public class MapPatcher {
// 
//     public static int[] patch(int[] patch, int[] old) {
//         ArrayList<Integer> newArrayList = new ArrayList<>();
//         int patchIndex = 0;
//         while (patchIndex < patch.length) {
//             //matching
//             int matchingElements = patch[patchIndex++];
//             int startingIndex = newArrayList.size();
//             for (int oldIndex = startingIndex; oldIndex < startingIndex + matchingElements; ++oldIndex) {
//                 if (oldIndex < old.length) {
//                     newArrayList.add(old[oldIndex]);
//                 } else {
//                     newArrayList.add(0);
//                 }
//             }
// 
//             //mismatching
//             if (patchIndex < patch.length) {
//                 int misMatchingElements = patch[patchIndex++];
//                 while (misMatchingElements > 0) {
//                     newArrayList.add(patch[patchIndex++]);
//                     misMatchingElements--;
//                 }
//             }
//         }
//         return newArrayList.stream().mapToInt(i -> i).toArray();
//     }
// }


// Path: src/main/java/pl/joegreen/sergeants/framework/model/api/UpdatableGameState.java
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import pl.joegreen.sergeants.api.response.GameStartApiResponse;
import pl.joegreen.sergeants.api.response.GameUpdateApiResponse;
import pl.joegreen.sergeants.api.response.ScoreApiResponse;
import pl.joegreen.sergeants.api.util.MapPatcher;
import pl.joegreen.sergeants.framework.model.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

    public ImmutableList<Player> getPlayers() {
        return players;
    }

    @Override
    public GameStarted getGameStartedData() {
        return new GameStartedApiResponseImpl(startData);
    }


    private static ImmutableList<Player> createPlayersInfo(GameStartApiResponse startData, GameUpdateApiResponse firstUpdateData) {
        int numberOfPlayers = startData.getUsernames().length;
        Map<Integer, ScoreApiResponse> scoresMap = createScoresMap(firstUpdateData);
        List<Player> players = IntStream.range(0, numberOfPlayers).mapToObj(playerIndex -> {
            Optional<ScoreApiResponse> scoreForPlayer = Optional.ofNullable(scoresMap.get(playerIndex));
            return new Player(playerIndex,
                    startData.getUsernames()[playerIndex],
                    Optional.ofNullable(startData.getTeams()).map(teams -> teams[playerIndex]).orElse(playerIndex),
                    scoreForPlayer.map(ScoreApiResponse::getTiles).orElse(null),
                    scoreForPlayer.map(ScoreApiResponse::getTotal).orElse(null),
                    scoreForPlayer.map(ScoreApiResponse::isDead).orElse(false));
        }).collect(Collectors.toList());
        return ImmutableList.copyOf(players);
    }

    private static Map<Integer, ScoreApiResponse> createScoresMap(GameUpdateApiResponse firstUpdateData) {
        return Arrays.stream(firstUpdateData.getScores()).collect(Collectors.toMap(ScoreApiResponse::getIndex, Function.identity()));
    }


    private UpdatableGameState(int turn, GameStartApiResponse startData, int[] fields, int[] cities, int[] generals, ImmutableList<Player> players, int attackIndex) {