public class EntityFactory {

  public static Entity createClick(World world, int x, int y, float startScale, float speed) {
    Entity e = world.createEntity();
    EntityEdit edit = e.edit();

    MutableMapPosition pos = edit.create(MutableMapPosition.class);
    pos.x = x;
    pos.y = y;

    Sprite sprite = new Sprite();
    sprite.name = "click";
    sprite.color = new Color(1f, 1f, 1f, 0.5f);
    sprite.rotation = 0f;
    sprite.scaleX = startScale;
    sprite.scaleY = startScale;
    edit.add(sprite);

    Expires expires = edit.create(Expires.class);
    expires.delay = 1f;

    ScaleAnimation scaleAnimation = edit.create(ScaleAnimation.class);
    scaleAnimation.speed = speed;

    ColorAnimation colorAnimation = edit.create(ColorAnimation.class);
    colorAnimation.alphaAnimate = true;
    colorAnimation.alphaSpeed = -1f;

    return e;
  }

  public static Entity createEmpire(World world, int x, int y, String name, Empire empire) {
    Entity e = world.createEntity();

    EntityEdit edit = e.edit();

    ArmyCommand command = new ArmyCommand();
    edit.add(empire).add(new EventsCount()).add(new Discoveries()).add(new Policies()).add(new Diplomacy())
        .add(command).add(new Score());

    Sprite sprite = new Sprite();
    sprite.name = "camp";
    sprite.rotation = 0f;
    sprite.scaleX = 1f;
    sprite.scaleY = 1f;
    sprite.color = empire.color;
    edit.add(sprite);

    InfluenceSource source = new InfluenceSource();
    edit.add(new MapPosition(x, y)).add(source).add(new Destination(command.forbiddenTiles, 2));

    edit.add(new Name(name)).add(new Description("Tribe of " + name))
        .add(new TextBox(() -> name + ": " + source.power()));

    if (empire.isComputerControlled())
      edit.add(new AIControlled());

    return e;
  }

  public static Entity createArmy(World world, MapPosition pos, String name, Empire empire, Entity source,
      int militaryPower) {
    Entity e = world.createEntity();
    EntityEdit edit = e.edit();

    edit.add(new Counter(Colors.contrast(empire.color), empire.color, militaryPower));

    edit.add(pos).add(new Name(name)).add(new Description(name))
        .add(new Destination(source.getComponent(ArmyCommand.class).forbiddenTiles, 1))
        .add(new Army(source, militaryPower)).add(empire);

    if (empire.isComputerControlled())
      edit.add(new AIControlled());

    return e;
  }

  public static Entity createFadingTileLabel(World world, String label, Color color, float x, float y, float duration) {
    Entity e = world.createEntity();
    EntityEdit edit = e.edit();

    MutableMapPosition position = edit.create(MutableMapPosition.class);
    position.x = x;
    position.y = y;

    FadingMessage fading = edit.create(FadingMessage.class);
    fading.label = label;
    fading.color = color;
    fading.duration = duration;
    fading.vx = 0f;
    fading.vy = 1.3f;

    return e;
  }

  public static Entity createFadingTileIcon(World world, TextureRegion icon, Color color, float x, float y,
      float duration) {
    Entity e = world.createEntity();
    EntityEdit edit = e.edit();

    MutableMapPosition position = edit.create(MutableMapPosition.class);
    position.x = x;
    position.y = y;

    FadingMessage fading = edit.create(FadingMessage.class);
    fading.icon = icon;
    fading.color = color;
    fading.duration = duration;
    fading.vx = 0f;
    fading.vy = 1.3f;

    return e;
  }

} public interface EventHandler {

  /** Get the score progress toward next event of this type. */
  int getProgress(Entity e);

  /** Get the reason for this event. */
  String getReason(Entity e);

  /**
   * Execute the event on the entity.
   * 
   * @return true if it could execute the event (false if some system
   *         condition prevented it)
   */
  boolean execute(Entity e);

  /** A pretty printing name for the event. */
  String getType();

} public final class Discovery {

  public final String name;

  public final Set<Terrain> terrains = EnumSet.noneOf(Terrain.class);

  public final Set<String> groups;

  public final Set<Set<String>> previous;

  public ObjectFloatMap<Faction> factions;

  /**
   * Indexed by effect name, contains the delta for the corresponding variable.
   * Positive numbers are positive modifiers for the discoverer.
   */
  public final Map<String, Object> effects;

  private Discovery(String name, List<List<String>> previous, List<String> groups, List<Terrain> terrains,
      Map<String, Object> effects) {
    this.name = name;
    this.effects = effects != null ? effects : Collections.emptyMap();
    this.previous = previous.stream().filter(l -> !l.isEmpty()).map(l -> set(l)).collect(toSet());
    this.groups = set(groups);
    if (terrains != null)
      this.terrains.addAll(terrains);
  }

  @SuppressWarnings("unchecked")
  private static <T> Set<T> set(List<T> list) {
    if (list == null || list.isEmpty())
      return Collections.emptySet();
    if (list.size() == 1)
      return Collections.singleton(list.get(0));
    else if (list instanceof Set)
      return (Set<T>) list;
    else
      return new HashSet<>(list);
  }

  public Discovery(String name) {
    this(name, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null);
  }

  public String getName() {
    return name;
  }

  @Override
  public String toString() {
    return name;
  }

  @Override
  public int hashCode() {
    return name.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this)
      return true;
    else if (obj instanceof Discovery)
      return name.equals(((Discovery) obj).name);
    else
      return false;
  }

  public static final ReadOnlySerializer<Discovery> SER = new ReadOnlySerializer<Discovery>() {
    @SuppressWarnings("unchecked")
    @Override
    public Discovery read(Json json, JsonValue data, Class type) {
      return new Discovery(data.getString("name"), //
          json.readValue(ArrayList.class, ArrayList.class, data.get("previous")), //
          json.readValue(ArrayList.class, data.get("groups")), //
          json.readValue(ArrayList.class, Terrain.class, data.get("terrains")), //
          json.readValue(HashMap.class, data.get("effects")));
    }
  };

} public final class Empire extends Component {

  private static final AtomicInteger COUNTER = new AtomicInteger(0);

  public final int id;

  public final Color color;

  public final Color backColor;

  public final Culture culture;

  private final boolean computer;

  public Empire(Color color, Culture culture, boolean computer) {
    this.id = COUNTER.getAndIncrement();
    this.color = color;
    this.backColor = Colors.contrast(color);
    this.culture = culture;
    this.computer = computer;
  }

  public boolean isComputerControlled() {
    return computer;
  }

  public Color getColor() {
    return color;
  }

  public Culture getCulture() {
    return culture;
  }

  public String newCityName() {
    return culture.newCityName();
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this)
      return true;
    else if (obj instanceof Empire)
      return id == ((Empire) obj).id;
    else
      return false;
  }

  @Override
  public int hashCode() {
    return id;
  }

  @Override
  public String toString() {
    return culture.name + "(ia=" + computer + ")";
  }

} public enum Policy {

  FOOD("Our people prefer eating "),

  WEAPON("When fighting, our people use "),

  MOVE("We go farther and better by "),

  TERRAIN("Our preferred terrain is "),

  RELATION("Our preferred relation is "),

  VALUE("Our most honored value is "),

  ART("Our most excellent art is "),

  GOVERNMENT("We are governed by "),

  JOBS("Our people are mostly ");

  public final String msg;

  private Policy(String msg) {
    this.msg = msg;
  }

  public static final Policy get(String name) {
    try {
      return Policy.valueOf(name);
    } catch (IllegalArgumentException e) {
      return null;
    }
  }

} public final class SessionSettings implements Cloneable {

  /** List different colors that are easy to recognize on-screen. */
  public static final List<Color> COLORS = Arrays.asList(Color.RED, Color.GREEN, Color.BLUE, Color.PURPLE,
      Color.YELLOW, Color.TEAL, Color.WHITE, Color.BLACK, Color.GRAY, Color.PINK, Color.LIGHT_GRAY, Color.ORANGE,
      Color.MAGENTA, Color.DARK_GRAY, Color.CYAN, Color.OLIVE, Color.MAROON, Color.NAVY);

  private final Map<String, Culture> cultures;

  private final Map<String, Discovery> discoveries;

  public final List<Empire> empires = new ArrayList<>();

  public final Properties map;

  public final EnumValue<MapGenerator.Generator> mapType;

  public final BooleanValue startWithDiplomacy = new BooleanValue(false);

  public SessionSettings() {
    // init map parameters
    mapType = new EnumValue<>(MapGenerator.Generator.ISLAND);
    map = mapType.get().algo.getDefaultValues();

    // load cultures from data file
    Json json = new Json();
    this.cultures = cultures(json).stream().collect(toMap(Culture::getName, c -> c));
    this.discoveries = discoveries(json).stream().collect(toMap(Discovery::getName, c -> c));

    // generate some default empires
    boolean ai = false;
    for (Culture culture : cultures.values()) {
      empires.add(new Empire(guessColor(), culture, ai));
      ai = true;
    }
  }

  public SessionSettings(SessionSettings settings) {
    // load cultures and discoveries from data file
    Json json = new Json();
    this.cultures = cultures(json).stream().collect(toMap(Culture::getName, c -> c));
    this.discoveries = discoveries(json).stream().collect(toMap(Discovery::getName, c -> c));

    // copy map creation options
    this.map = new Properties();
    this.map.putAll(settings.map);
    this.mapType = new EnumValue<>(MapGenerator.Generator.ISLAND);
    this.mapType.set(settings.mapType.get());
    this.startWithDiplomacy.set(settings.startWithDiplomacy.get());

    // copy empires using new cultures (as they have a city iterator)
    for (Empire empire : settings.empires)
      this.empires.add(new Empire(empire.color, cultures.get(empire.culture.name), empire.isComputerControlled()));
  }

  private static List<Culture> cultures(Json json) {
    json.setSerializer(Culture.class, Culture.SER);
    List<Culture> cultures = new ArrayList<>();
    for (FileHandle f : files("data/cultures/", "antiquity.json"))
      cultures.addAll(Arrays.asList(json.fromJson(Culture[].class, f)));
    return cultures;
  }

  private static List<Discovery> discoveries(Json json) {
    // TODO add ideas from http://doodlegod.wikia.com/wiki/Doodle_Devil
    // TODO add ideas from http://doodlegod.wikia.com/wiki/Doodle_God_2
    // TODO The mythic discoveries could be used as 'totem' animals
    json.setSerializer(Discovery.class, Discovery.SER);
    List<Discovery> discoveries = new ArrayList<>();
    for (FileHandle f : files("data/discoveries/", "nature.json", "prehistory.json", "antiquity.json", "classic.json"))
      discoveries.addAll(Arrays.asList(json.fromJson(Discovery[].class, f)));
    return discoveries;
  }

  private static Iterable<FileHandle> files(String dir, String ... files) {
    return Arrays.stream(files).map(f -> Gdx.files.internal(dir + f)).collect(toList());
  }

  public Map<String, Discovery> getDiscoveries() {
    return discoveries;
  }

  public Collection<Culture> getCultures() {
    return Collections.unmodifiableCollection(cultures.values());
  }

  public Culture getCulture(String name) {
    return cultures.get(name);
  }

  public Color guessColor() {
    List<Color> usedColors = empires.stream().map(Empire::getColor).collect(toList());
    Optional<Color> color = COLORS.stream().filter(c -> !usedColors.contains(c)).findFirst();
    if (color.isPresent())
      return color.get();
    else {
      // must use an existing color
      return COLORS.get(new Random().nextInt(COLORS.size()));
    }
  }

  private final Random rand = new Random();

  public Culture guessCulture() {
    Map<Culture, Integer> usedCultures = cultures.values().stream().distinct()
        .collect(toMap(c -> c, c -> (int) empires.stream().filter(e -> e.culture == c).count()));
    List<Culture> list = cultures
        .values()
        .stream()
        .sorted(
            (c1, c2) -> -Integer.compare(c1.cities.size - usedCultures.get(c1), c2.cities.size - usedCultures.get(c2)))
        .limit(3).collect(toList());
    if (list.isEmpty())
      throw new IllegalStateException("No remaining culture!");
    return list.get(abs(rand.nextInt() % list.size()));
  }
} public final class GameMap {

  public final Terrain[][] map;

  private final Entity[][] entityByCoord;

  private final MapPosition[][] posByCoord;

  private final Influence[][] influenceByCoord;

  public final int width, height;

  /** Represents the whole map as a single image. */
  public final Texture texture;

  public final Empire[] empires;

  public GameMap(Terrain[][] map, Collection<Empire> empires) {
    this.map = map;
    this.empires = empires.toArray(new Empire[0]);
    width = map.length;
    height = map[0].length;

    entityByCoord = new Entity[width][height];
    influenceByCoord = new Influence[width][height];
    posByCoord = new MapPosition[width][height];

    Pixmap pixmap = new Pixmap(width, height, Pixmap.Format.RGBA8888);

    for (int x = 0; x < width; x++) {
      for (int y = 0; y < height; y++) {
        MapPosition pos = posByCoord[x][y] = new MapPosition(x, y);
        influenceByCoord[x][y] = new Influence(pos, map[x][y]);
        pixmap.setColor(map[x][y].getColor());
        pixmap.drawPixel(x, y);
      }
    }

    texture = new Texture(pixmap);
    pixmap.dispose();
  }

  public Entity getEntityAt(int x, int y) {
    if (isOnMap(x, y))
      return entityByCoord[x][y];
    else
      return null;
  }

  public Entity getEntityAt(MapPosition pos) {
    return getEntityAt(pos.x, pos.y);
  }

  public boolean hasEntity(MapPosition pos) {
    return getEntityAt(pos) != null;
  }

  public Terrain getTerrainAt(MapPosition pos) {
    return getTerrainAt(pos.x, pos.y);
  }

  public Terrain getTerrainAt(int x, int y) {
    if (isOnMap(x, y))
      return map[x][y];
    else
      throw new IllegalStateException("(" + x + ", " + y + ") is outside map boundaries.");
  }

  public Influence getInfluenceAt(MapPosition pos) {
    return getInfluenceAt(pos.x, pos.y);
  }

  public Influence getInfluenceAt(int x, int y) {
    if (isOnMap(x, y))
      return influenceByCoord[x][y];
    else
      throw new IllegalStateException("(" + x + ", " + y + ") is outside map boundaries.");
  }

  public boolean isOnMap(MapPosition p) {
    return isOnMap(p.x, p.y);
  }

  public boolean isOnMap(int x, int y) {
    return x >= 0 && x < posByCoord.length && y >= 0 && y < posByCoord[0].length;
  }

  public MapPosition getPositionAt(int x, int y) {
    if (isOnMap(x, y))
      return posByCoord[x][y];
    else
      throw new IllegalStateException("(" + x + ", " + y + ") is outside map boundaries.");
  }

  public void setEntity(Entity e, int x, int y) {
    entityByCoord[x][y] = e;
  }

  public void setEntity(Entity e, MapPosition p) {
    setEntity(e, p.x, p.y);
  }

  public void moveEntity(Entity e, MapPosition from, MapPosition to) {
    entityByCoord[from.x][from.y] = null;
    entityByCoord[to.x][to.y] = e;
  }

  public Array<MapPosition> getNeighbors(int x, int y, int n) {
    Array<MapPosition> coordinates = new Array<MapPosition>();
    int min;
    int myrow;
    for (int row = y - n; row < y + n + 1; row++) {
      min = MyMath.min(2 * (row - y + n), n, -2 * (row - y - n) + 1);
      for (int col = x - min; col < x + min + 1; col++) {
        if ((col < 0) || (col >= width))
          continue;
        if (x == col && y == row)
          continue;
        else if (x % 2 == 0)
          myrow = 2 * y - row;
        else
          myrow = row;
        if ((myrow < 0) || (myrow >= height))
          continue;
        coordinates.add(new MapPosition(col, myrow));
      }
    }
    return coordinates;
  }

  public Array<MapPosition> getNeighbors(MapPosition pos) {
    return getNeighbors(pos.x, pos.y, 1);
  }

} public final class MapPosition extends Component {

  public final int x, y;

  public MapPosition(int x, int y) {
    this.x = x;
    this.y = y;
  }

  @Override
  public int hashCode() {
    return x + y * 31;
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == this)
      return true;
    else if (obj instanceof MapPosition) {
      MapPosition p = (MapPosition) obj;
      return x == p.x && y == p.y;
    }
    return false;
  }

  @Override
  public String toString() {
    return "(" + x + "," + y + ")";
  }
}
 public enum Terrain {

  DEEP_WATER("deep water", Color.NAVY, 50, false),

  SHALLOW_WATER("shallow water", Color.BLUE, 30, false),

  DESERT("desert", Color.YELLOW, 50, false),

  PLAIN("plain", Color.GREEN, 10, true),

  GRASSLAND("grassland", new Color(0, 0.5f, 0, 1), 15, true),

  FOREST("forest", Color.OLIVE, 30, true),

  HILLS("hills", Color.ORANGE, 30, true),

  MOUNTAIN("mountain", Color.MAROON, 50, false),

  ARCTIC("arctic", Color.WHITE, 100, false);

  private final Color color;

  private final int moveCost;

  private final String desc;

  private final boolean canStart;

  private Terrain(String desc, Color color, int moveCost, boolean canStart) {
    this.desc = desc;
    this.moveCost = moveCost;
    this.color = color;
    this.canStart = canStart;
  }

  public Color getColor() {
    return color;
  }

  public boolean moveBlock() {
    return false;
  }

  public boolean canStart() {
    return canStart;
  }

  public int moveCost() {
    return moveCost;
  }

  public int getTexture() {
    return ordinal();
  }

  public String getDesc() {
    return desc;
  }

} @Wire
public final class NotificationsSystem extends VoidEntitySystem {

  private static final Comparator<Notification> INFO_FIRST = new Comparator<Notification>() {
    @Override
    public int compare(Notification o1, Notification o2) {
      if (o1.requiresAction()) {
        if (o2.requiresAction())
          return o1.msg.compareTo(o2.msg);
        else
          return 1;
      } else {
        if (o2.requiresAction()) return -1;
        else
          return o1.msg.compareTo(o2.msg);
      }
    }
  };

  /** Notifications registered for next turn. */
  private final List<Notification> notifications = new ArrayList<>();

  /** Notifications for current turn. */
  private final List<Notification> current = new ArrayList<>();

  private final Assets icons;

  public NotificationsSystem(Assets icons) {
    this.icons = icons;
  }

  @Override
  protected void processSystem() {
    current.clear();
    current.addAll(notifications);
    notifications.clear();
  }

  public List<Notification> getNotifications() {
    current.removeIf(n -> n.discard != null && n.discard.canDiscard());
    current.sort(INFO_FIRST);
    return Collections.unmodifiableList(current);
  }

  public void addNotification(Runnable action, Condition discard, Assets.Icon type, String msg, Object ... args) {
    if (args == null || args.length == 0)
      notifications.add(new Notification(icons.getDrawable(type), action, discard, msg));
    else
      notifications.add(new Notification(icons.getDrawable(type), action, discard, format(msg, args)));
  }

  @FunctionalInterface
  public interface Condition {
    boolean canDiscard();
  }

  public static final class Notification {

    public final String msg;

    public final Drawable type;

    public final Runnable action;

    private final Condition discard;

    public Notification(Drawable type, Runnable action, Condition discard, String msg) {
      this.type = type;
      this.action = action;
      this.discard = discard;
      this.msg = msg;
    }

    public boolean requiresAction() {
      return discard != null && !discard.canDiscard();
    }

  }

  public boolean canFinishTurn() {
    for (Notification n : current)
      if (n.requiresAction())
        return false;
    return true;
  }

  public void discard(Notification n) {
    if (!n.requiresAction())
      current.remove(n);
  }

} public final class Description extends Component {

  public String desc;

  public final AtlasRegion texture;

  public Description(String desc) {
    this(desc, null);
  }

  public Description(String desc, AtlasRegion texture) {
    this.desc = desc;
    this.texture = texture;
  }

  @Override
  public String toString() {
    return desc;
  }

} public final class Name extends Component {

  public String name;

  public Name(String name) {
    this.name = name;
  }

  @Override
  public String toString() {
    return name;
  }

}
 public final class OverworldScreen extends AbstractScreen {

  private static final Logger log = LoggerFactory.getLogger(OverworldScreen.class);

  public final GameMap map;

  private final SpriteRenderSystem spriteRenderSystem;

  private final CounterRenderSystem counterRenderSystem;

  private final DiplomacyRenderSystem diplomacyRenderSystem;

  private final InfluenceRenderSystem influenceRenderSystem;

  private final TextBoxRenderSystem textBoxRenderSystem;

  private final DestinationRenderSystem destinationRenderSystem;

  private final FadingMessageRenderSystem fadingMessageRenderSystem;

  private final EffectsSystem effectsSystem;

  private final EventsSystem eventsSystem;

  private final DiscoverySystem discoverySystem;

  private final PoliciesSystem policiesSystem;

  private final DiplomaticSystem diplomaticSystem;

  private final InfluenceSystem influenceSystem;

  final RevoltSystem revoltSystem;

  private final DisasterSystem disasterSystem;

  private final ArmiesSystem armiesSystem;

  private final DestinationSystem destinationSystem;

  private final ScoreSystem scoreSystem;

  private final AISourceDestinationSystem aiDestination;

  private final AIDiscoverySystem aiDiscovery;

  private final AIDiplomaticSystem aiDiplomacy;

  private final AIArmyCommandSystem aiCommand;

  private final AIArmyMovementSystem aiArmies;

  final MapRenderer mapRenderer;

  private final MapHighlighter mapHighlighter;

  private final Map<MapPosition, String> highlightedTiles = new HashMap<>();

  private boolean renderHighlighter;

  private final List<String> indications = new ArrayList<>();

  private final InputManager inputManager;

  /** Stage to display the HUD. */
  private final Stage stage;

  private final CameraMovementSystem cameraMovementSystem;

  private boolean firstShow = true;

  private int turnNumber = 0;

  public MapPosition selectedTile;

  private final PauseMenuScreen pauseScreen;

  private final DiplomacyMenuScreen diplomacyScreen;

  private final DiscoveryMenuScreen discoveryScreen;

  private final AskDiscoveryScreen askDiscoveryScreen;

  private final AskEventScreen askEventScreen;

  private final PoliciesMenuScreen policiesScreen;

  private final ScoresMenuScreen scoresScreen;

  private final ArmiesMenuScreen armiesScreen;

  private final TutorialMenuScreen tutorialScreen;

  private final NotificationsSystem notificationsSystem;

  public Entity player;

  private final List<Entity> empires;

  public OverworldScreen(ThingsThatWereGame game, SpriteBatch batch, World world, SessionSettings settings) {
    super(game, world, batch);

    cameraMovementSystem = world.setSystem(new CameraMovementSystem(camera));

    stage = new Stage(new ScreenViewport(), new SpriteBatch());

    map = new GameMap(settings.mapType.get().algo.getMapData(settings.map), settings.empires);

    notificationsSystem = world.setSystem(new NotificationsSystem(game.assets), true);
    eventsSystem = world.setSystem(new EventsSystem(), true);
    effectsSystem = world.setSystem(new EffectsSystem(), true);
    world.setSystem(new SpecialDiscoveriesSystem(this), true);
    discoverySystem = world.setSystem(new DiscoverySystem(settings, map, this), true);
    policiesSystem = world.setSystem(new PoliciesSystem(), true);
    diplomaticSystem = world.setSystem(new DiplomaticSystem(this, settings.startWithDiplomacy.get()), true);
    influenceSystem = world.setSystem(new InfluenceSystem(map), true);
    revoltSystem = world.setSystem(new RevoltSystem(map, settings, this), true);
    disasterSystem = world.setSystem(new DisasterSystem(this), true);
    armiesSystem = world.setSystem(new ArmiesSystem(map, this), true);
    destinationSystem = world.setSystem(new DestinationSystem(map, this), true);
    scoreSystem = world.setSystem(new ScoreSystem(settings, this), true);
    aiDestination = world.setSystem(new AISourceDestinationSystem(map, this), true);
    aiDiscovery = world.setSystem(new AIDiscoverySystem(), true);
    aiDiplomacy = world.setSystem(new AIDiplomaticSystem(map), true);
    aiCommand = world.setSystem(new AIArmyCommandSystem(map, this), true);
    aiArmies = world.setSystem(new AIArmyMovementSystem(map, this), true);
    influenceRenderSystem = world.setSystem(new InfluenceRenderSystem(camera, batch, map), true);
    diplomacyRenderSystem = world.setSystem(new DiplomacyRenderSystem(camera, batch), true);
    textBoxRenderSystem = world.setSystem(new TextBoxRenderSystem(game.assets, camera, batch), true);
    destinationRenderSystem = world.setSystem(new DestinationRenderSystem(camera, batch), true);
    spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera, batch), true);
    counterRenderSystem = world.setSystem(new CounterRenderSystem(game.assets, camera, batch), true);
    fadingMessageRenderSystem = world.setSystem(new FadingMessageRenderSystem(game.assets, camera, batch), true);

    world.initialize();
    empires = fillWorldWithEntities();
    discoverySystem.process();
    policiesSystem.process();
    diplomaticSystem.process();
    influenceSystem.process();
    revoltSystem.process();
    disasterSystem.process();
    armiesSystem.process();
    destinationSystem.process();
    scoreSystem.process();
    diplomacyRenderSystem.preprocess();
    influenceRenderSystem.preprocess();
    textBoxRenderSystem.preprocess();
    notificationsSystem.process();
    log.info("The world is initialized");

    inputManager = new InputManager(game.assets, camera, world, this, stage, map);
    inputManager.menuBuilder.buildTurnMenu();
    inputManager.menuBuilder.buildNotificationMenu();
    inputManager.menuBuilder.buildEmpireMenu();

    world.setManager(new PlayerManager());

    mapRenderer = new MapRenderer(camera, batch, map);
    mapHighlighter = new MapHighlighter(game.assets, camera, batch);

    renderHighlighter = false;

    tutorialScreen = new TutorialMenuScreen(game, world, batch, this);
    pauseScreen = new PauseMenuScreen(game, world, batch, this);
    diplomacyScreen = new DiplomacyMenuScreen(game, world, batch, this, empires, player, diplomaticSystem);
    discoveryScreen = new DiscoveryMenuScreen(game, world, batch, this, player, discoverySystem);
    askDiscoveryScreen = new AskDiscoveryScreen(game, world, batch, this, player, discoverySystem);
    askEventScreen = new AskEventScreen(game, world, batch, this);
    policiesScreen = new PoliciesMenuScreen(game, world, batch, this, player, policiesSystem, effectsSystem,
        revoltSystem);
    scoresScreen = new ScoresMenuScreen(game, world, batch, this, scoreSystem);
    armiesScreen = new ArmiesMenuScreen(game, world, batch, this, player, armiesSystem);
  }

  @Override
  public void show() {
    super.show();

    inputManager.setEnabled(true);
  }

  @Override
  public void render(float delta) {
    super.render(delta);

    if (firstShow) {
      if (selectedTile != null)
        cameraMovementSystem.move(selectedTile);
      else
        cameraMovementSystem.move(new MapPosition(map.width / 2, map.height / 2));
      firstShow = false;
    }

    mapRenderer.render();
    diplomacyRenderSystem.process();
    influenceRenderSystem.process();
    destinationRenderSystem.process();
    counterRenderSystem.process();
    spriteRenderSystem.process();
    textBoxRenderSystem.process();

    if (renderHighlighter) {
      mapHighlighter.render(highlightedTiles);
    }

    // render AFTER map was drawn
    fadingMessageRenderSystem.process();

    stage.act(delta);
    stage.draw();
  }

  @Override
  public void resize(int width, int height) {
    super.resize(width, height);

    // update map camera and keep same center
    Vector3 pos = camera.position.cpy();
    camera.setToOrtho(false, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
    camera.position.set(pos);

    // update GUI view
    stage.getViewport().update(width, height, true);
    /*
     * The GUI use a ScreenViewport, meaning it won't scale when screen size
     * change. This is fine because we don't want the GUI size to change,
     * becoming zoomed in and ugly or zoomed and unreadable. However it has a
     * small side effect: the existing menu were placed according to the
     * vertical stage size. The stage size changed with the screen (game window)
     * one. So we must recompute the GUI elements coordinates. The simlest way
     * to do it is to recreate the menu.
     */
    inputManager.reloadMenus();
  }

  @Override
  public void dispose() {
    stage.dispose();
  }

  private List<Entity> fillWorldWithEntities() {
    // add the entity for players cities
    List<Entity> list = new ArrayList<>();
    for (Empire empire : map.empires)
      list.add(createEmpire(empire.newCityName(), empire));

    selectedTile = player.getComponent(MapPosition.class);

    // You have to process the world once to get all the entities loaded up with
    // the "Stats" component - I'm not sure why, but if you don't, the bag of
    // entities that turnManagementSystem gets is empty?
    world.process();

    return list;
  }

  private Entity createEmpire(String name, Empire empire) {
    int x, y;
    do {
      x = MathUtils.random(map.width - 4) + 2;
      y = MathUtils.random(map.height - 4) + 2;
    } while (!map.getTerrainAt(x, y).canStart()
    // avoid having two cities on the same tile
        || map.getEntityAt(x, y) != null
        // or on neighbor tiles
        || hasNeighbourCity(x, y));
    Entity entity = EntityFactory.createEmpire(world, x, y, name, empire);
    if (!empire.isComputerControlled())
      player = entity;
    map.setEntity(entity, x, y);
    InfluenceSource source = entity.getComponent(InfluenceSource.class);
    source.influencedTiles.add(map.getPositionAt(x, y));
    for (MapPosition p : map.getNeighbors(x, y, 1)) {
      if (map.isOnMap(p)) {
        Influence inf = map.getInfluenceAt(p);
        inf.setInfluence(entity, inf.getMaxInfluence());
        source.influencedTiles.add(p);
      }
    }
    log.info("Created {} for empire {}", name, empire);
    return entity;
  }

  private boolean hasNeighbourCity(int x, int y) {
    for (MapPosition pos : map.getNeighbors(x, y, 2))
      if (map.getEntityAt(pos.x, pos.y) != null)
        return true;
    return false;
  }

  private void processTurn() {
    log.info("Process new turn");
    long start = System.currentTimeMillis();

    // If they don't have AI, give control to the human player
    inputManager.setEnabled(true);
    stage.setKeyboardFocus(null);
    stage.setScrollFocus(null);

    // compute AI before 'really' processing turn
    aiDestination.process();
    aiDiscovery.process();
    aiDiplomacy.process();
    aiCommand.process();
    // make sure armies created by command are assigned a destination
    aiArmies.process();

    discoverySystem.process();
    policiesSystem.process();
    diplomaticSystem.process();
    armiesSystem.process();
    destinationSystem.process();
    influenceSystem.process();
    revoltSystem.process();
    disasterSystem.process();
    scoreSystem.process();
    eventsSystem.process();

    world.setDelta(0);
    world.process();
    
    updateRendering();
    notificationsSystem.process();
    turnNumber++;

    log.debug("Processed turn #{} in {}ms", turnNumber, System.currentTimeMillis() - start);
  }
  
  public void updateRendering() {
    diplomacyRenderSystem.preprocess();
    influenceRenderSystem.preprocess();
    textBoxRenderSystem.preprocess();    
  }

  public void flag(Entity source, int x, int y) {
    destinationSystem.computePath(source, new MapPosition(x, y));
  }

  public void highlightFlagRange(Entity source) {
    highlightedTiles.clear();
    for (MapPosition tile : destinationSystem.getTargetTiles(source))
      highlightedTiles.put(tile, null);
    renderHighlighter = true;
    mapHighlighter.setColor(0f, 0f, 0.2f, 0.3f);
    indications.add("Select destination in highlighted tiles for " + source.getComponent(Description.class));

    inputManager.reloadMenus();
  }

  public void stopHighlighing() {
    renderHighlighter = false;
    highlightedTiles.clear();
    indications.clear();

    inputManager.reloadMenus();
  }

  public Map<MapPosition, String> getHighlightedTiles() {
    return highlightedTiles;
  }

  public int getTurnNumber() {
    return turnNumber;
  }

  public int getCurrentYear() {
    return (turnNumber * 10) - 4000;
  }

  public boolean canFinishTurn() {
    return notificationsSystem.canFinishTurn()
    // make sure no player action on map is waiting for input
        && !renderHighlighter;
  }

  public void endTurn() {
    if (canFinishTurn())
      processTurn();

    // update the menu data
    inputManager.reloadMenus();
  }

  public void pauseMenu() {
    game.setScreen(pauseScreen);
  }

  public void diplomacyMenu() {
    game.setScreen(diplomacyScreen);
  }

  public void discoveryMenu() {
    game.setScreen(discoveryScreen);
  }

  public void askDiscovery() {
    game.setScreen(askDiscoveryScreen);
  }

  public void askEvent(String label, Map<String, Runnable> choices) {
    askEventScreen.setEvent(label, choices);
    game.setScreen(askEventScreen);
  }

  public void armiesMenu() {
    game.setScreen(armiesScreen);
  }

  public void policiesMenu() {
    game.setScreen(policiesScreen);
  }

  public void scoresMenu() {
    game.setScreen(scoresScreen);
  }

  public void tutorialMenu() {
    game.setScreen(tutorialScreen);
  }

  public void select(Entity e, boolean flagIfMoveable) {
    inputManager.select(e.getComponent(MapPosition.class), e, flagIfMoveable);
    cameraMovementSystem.move(selectedTile);
  }

  public List<String> getIndications() {
    return indications;
  }

  public void displayColoredInfluence(boolean b) {
    influenceRenderSystem.displayColoredInfluence(b);
  }

  public boolean displayColoredInfluence() {
    return influenceRenderSystem.displayColoredInfluence();
  }

} public enum Icon {
    DISCOVERY("discovery-bulb"),
    DIPLOMACY("diplomacy-handshake"),
    BUILDINGS("buildings-hammer"),
    REVOLT("revolt-clenched-fist"),
    FLAG("finish-flag"),
    MILITARY("military-swords"),
    DISEASE("disease-skull"),
    END_TURN("empty-hourglass");

    private final String name;

    Icon(String name) {
        this.name = name;
    }
}
import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.EntitySystem;
import com.artemis.annotations.Wire;
import com.artemis.utils.ImmutableBag;
import com.badlogic.gdx.utils.IntIntMap.Entry;
import com.badlogic.gdx.utils.IntMap;
import com.badlogic.gdx.utils.ObjectFloatMap;
import com.galvarez.ttw.EntityFactory;
import com.galvarez.ttw.model.EventsSystem.EventHandler;
import com.galvarez.ttw.model.components.*;
import com.galvarez.ttw.model.data.Discovery;
import com.galvarez.ttw.model.data.Empire;
import com.galvarez.ttw.model.data.Policy;
import com.galvarez.ttw.model.data.SessionSettings;
import com.galvarez.ttw.model.map.GameMap;
import com.galvarez.ttw.model.map.MapPosition;
import com.galvarez.ttw.model.map.Terrain;
import com.galvarez.ttw.rendering.NotificationsSystem;
import com.galvarez.ttw.rendering.components.Description;
import com.galvarez.ttw.rendering.components.Name;
import com.galvarez.ttw.screens.overworld.OverworldScreen;
import com.galvarez.ttw.utils.Assets.Icon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import static java.lang.Math.min;
import static java.util.stream.Collectors.toList;
package com.galvarez.ttw.model;




/**
 * For every empire, compute the new discovery.
 * <p>
 * Every turn a certain progress is made toward discovery is made, depending on
 * its capital power and influence.
 * </p>
 * 
 * @author Guillaume Alvarez
 */
@Wire
public final class DiscoverySystem extends EntitySystem implements EventHandler {

  private static final Logger log = LoggerFactory.getLogger(DiscoverySystem.class);

  /** Increase to speed progress up. */
  private static final int PROGRESS_PER_TILE = 1;

  private final OverworldScreen screen;

  private final Map<String, Discovery> discoveries;

  /** Discoveries free to be discovered by any empire. */
  private final Map<String, Discovery> toDiscover;

  private final GameMap map;

  private final Random rand = new Random();

  private NotificationsSystem notifications;

  private EffectsSystem effects;

  private SpecialDiscoveriesSystem special;

  private AIDiscoverySystem aiSystem;

  private ComponentMapper<Discoveries> empires;

  private ComponentMapper<Diplomacy> relations;

  private ComponentMapper<InfluenceSource> influences;

  private ComponentMapper<AIControlled> ai;

  @SuppressWarnings("unchecked")
  public DiscoverySystem(SessionSettings s, GameMap map, OverworldScreen screen) {
    super(Aspect.getAspectForAll(Discoveries.class));
    this.discoveries = s.getDiscoveries();
    this.toDiscover = new HashMap<>(discoveries);
    this.map = map;
    this.screen = screen;
  }

  @Override
  public String getType() {
    return "Discovery";
  }

  @Override
  protected void initialize() {
    super.initialize();

    special.checkDiscoveries(discoveries);

    // check previous exist
    // set factions scores
    Set<String> groups = new HashSet<>();
    for (Discovery d : discoveries.values())
      groups.addAll(d.groups);
    for (Discovery d : discoveries.values()) {
      d.factions = getFactionsScores(d);

      for (Set<String> list : d.previous)
        for (String previous : list)
          if (!discoveries.containsKey(previous) && !groups.contains(previous))
            log.warn("Cannot find previous {} for discovery {}", previous, d);
    }

    world.getSystem(EventsSystem.class).addEventType(this);
  }

  private ObjectFloatMap<Faction> getFactionsScores(Discovery discovery) {
    ObjectFloatMap<Faction> scores = effects.getFactionsScores(discovery.effects);
    for (String group : discovery.groups)
      if (Policy.get(group) != null) {
        for (Faction f : Faction.values())
          scores.put(f, scores.get(f, 0f) * 1.5f);
        scores.getAndIncrement(Faction.CULTURAL, 0, 0.5f);
      }
    if (scores.size == 0)
      scores.getAndIncrement(Faction.CULTURAL, 0, 0.5f);
    return scores;
  }

  @Override
  protected boolean checkProcessing() {
    return false;
  }

  @Override
  protected void inserted(Entity e) {
    super.inserted(e);
    Discoveries d = empires.get(e);
    d.nextPossible = possibleDiscoveries(e, d);
  }

  @Override
  protected void processEntities(ImmutableBag<Entity> entities) {
    // progress toward next discovery is triggered by EventsSystem
  }

  /**
   * Compute the possible discoveries for the empire, associated to the faction
   * that recommends them.
   */
  private Map<Faction, Research> possibleDiscoveries(Entity empire, Discoveries labs) {
    // collect current state
    Set<String> done = new HashSet<>();
    Map<String, List<String>> groups = new HashMap<>();
    for (Discovery d : labs.done) {
      done.add(d.name);
      done.addAll(d.groups);
      for (String g : d.groups) {
        List<String> list = groups.get(g);
        if (list == null)
          groups.put(g, list = new ArrayList<>());
        list.add(d.name);
      }
    }

    Set<Discovery> toDiscover = new HashSet<>(this.toDiscover.values());

    // can only discover discoveries from already known groups if discovered by
    // neighbors, avoid having the same empire discovering all cereals
    toDiscover.removeIf(d -> !d.groups.isEmpty() && groups.keySet().containsAll(d.groups));

    // add discoveries already made by neighboring empires
    for (Entity neighbor : getNeighbors(empire))
      for (Discovery d : empires.get(neighbor).done)
        if (!done.contains(d.name))
          toDiscover.add(d);

    // select new possible discoveries
    List<Research> possible = toDiscover.stream().filter(d -> !done.contains(d.name) && hasTerrain(empire, d.terrains))
        .map(d -> toResearch(d, done, groups)).filter(Objects::nonNull).collect(toList());

    Map<Faction, Research> res = new EnumMap<>(Faction.class);
    for (Faction f : Faction.values()) {
      if (possible.isEmpty()) {
        log.debug("Found no {} research among {} for {}", f, possible, empire.getComponent(Description.class));