/**
 *  Copyright (C) 2002-2012   The FreeCol Team
 *
 *  This file is part of FreeCol.
 *
 *  FreeCol is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  FreeCol is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with FreeCol.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.sf.freecol.common.model;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import java.util.logging.Logger;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;

import net.sf.freecol.common.model.Map.Direction;
import net.sf.freecol.common.model.Map.Position;
import net.sf.freecol.common.model.Player.Stance;
import net.sf.freecol.common.util.RandomChoice;
import net.sf.freecol.common.util.Utils;


/**
 * Represents a single tile on the <code>Map</code>.
 *
 * @see Map
 */
public final class Tile extends UnitLocation implements Named, Ownable {

    private static final Logger logger = Logger.getLogger(Tile.class.getName());

    /**
     * This must be distinct from ColonyTile/Building.UNIT_CHANGE or
     * the colony panel can get confused.
     */
    public static final String UNIT_CHANGE = "TILE_UNIT_CHANGE";

    /**
     * Flag to assign to the high seas count to flag that the high seas
     * connectivity needs recalculation after reading in the map.
     */
    public static final int FLAG_RECALCULATE = Integer.MAX_VALUE;

    /**
     * The maximum distance that will still be considered "near" when
     * determining the location name.
     *
     * @see #getLocationName
     */
    public static final int NEAR_RADIUS = 8;

    /**
     * The type of the tile.
     * Beware: this may appear to be null in the client when the tile is
     * unexplored.
     */
    private TileType type;

    /**
     * The tile coordinates in the enclosing map.
     */
    private int x, y;

    /** The player that consider this tile to be their land. */
    private Player owner;

    /**
     * A pointer to the settlement located on this tile or 'null' if there is no
     * settlement on this tile.
     */
    private Settlement settlement;

    /**
     * Stores all Improvements and Resources (if any)
     */
    private TileItemContainer tileItemContainer;

    /**
     * Indicates which colony or Indian settlement that owns this tile ('null'
     * indicates no owner). A colony owns the tile it is located on, and every
     * tile with a worker on it. Note that while units and settlements are owned
     * by a player, a tile is owned by a settlement.
     */
    private Settlement owningSettlement;

    /**
     * Stores each player's image of this tile. Only initialized when needed.
     */
    private java.util.Map<Player, PlayerExploredTile> playerExploredTiles;

    /**
     * Describe region here.
     */
    private Region region;

    /**
     * The number of tiles to traverse to get to the high seas.
     */
    private int highSeasCount = -1;

    /**
     * Does this tile have an explicit moveToEurope state.  If null,
     * just use the defaults (usually not, unless water and on map edge),
     * otherwise use the explicit value provided here.
     */
    private Boolean moveToEurope;

    /**
     * The style of this Tile, as determined by adjacent tiles.
     */
    private int style;

    /**
     * An artificial contiguous-region number to identify connected
     * parts of the map.  That is, all land tiles with the same
     * contiguity number can be reached by a land unit on any of
     * those tiles in the absence of extra-geographic blockages like
     * settlements and other units.  Similarly for water tiles/naval
     * units.
     *
     * This is used to quickly scope out the sort of paths available
     * to a unit attempting to reach some destination.  It only needs
     * serialization from server to client, as it is set by the
     * TerrainGenerator on map import or creation.
     */
    private int contiguity = -1;


    /**
     * A constructor to use.
     *
     * @param game The <code>Game</code> this <code>Tile</code> belongs to.
     * @param type The type.
     * @param locX The x-position of this tile on the map.
     * @param locY The y-position of this tile on the map.
     */
    public Tile(Game game, TileType type, int locX, int locY) {
        super(game);

        this.type = type;

        x = locX;
        y = locY;

        owningSettlement = null;
        settlement = null;

        if (!isViewShared()) {
            playerExploredTiles = new HashMap<Player, PlayerExploredTile>();
        }
    }

    /**
     * Initialize this object from an XML-representation of this object.
     *
     * @param game The <code>Game</code> this <code>Tile</code> should be
     *            created in.
     * @param in The input stream containing the XML.
     * @throws XMLStreamException if a problem was encountered during parsing.
     */
    public Tile(Game game, XMLStreamReader in) throws XMLStreamException {
        super(game, in);

        if (!isViewShared()) {
            playerExploredTiles = new HashMap<Player, PlayerExploredTile>();
        }

        readFromXML(in);
    }

    /**
     * Initiates a new <code>Tile</code> with the given ID. The object should
     * later be initialized by calling either
     * {@link #readFromXML(XMLStreamReader)}.
     *
     * @param game The <code>Game</code> in which this object belong.
     * @param id The unique identifier for this object.
     */
    public Tile(Game game, String id) {
        super(game, id);

        if (!isViewShared()) {
            playerExploredTiles = new HashMap<Player, PlayerExploredTile>();
        }
    }

    // ------------------------------------------------------------ static methods
    /**
     * Creates a temporary copy of this tile for planning purposes.
     * The copy is identical except:
     *   - it is not present on the map
     *   - it has no owner, owning settlement or settlement
     * The latter is not a problem as the main use of this routine
     * is by Colony.getScratchColony() which needs to change these fields
     * anyway.
     * Note that the following fields are shared--- do not mutate them!
     *   + The tile item container.
     *   + The player explored tiles.
     * Colony.getCorrespondingWorkLocation() depends on the tics being shared.
     *
     * @return A scratch version of this tile.
     */
    public Tile getScratchTile() {
        Game game = getGame();
        Tile scratch = new Tile(game, type, x, y);
        scratch.owner = null;
        scratch.settlement = null;
        scratch.owningSettlement = null;
        if (tileItemContainer == null) {
            tileItemContainer = new TileItemContainer(getGame(), this);
        }
        scratch.tileItemContainer = tileItemContainer;
        scratch.playerExploredTiles = playerExploredTiles;
        scratch.region = region;
        scratch.highSeasCount = highSeasCount;
        scratch.moveToEurope = moveToEurope;
        scratch.style = style;
        return scratch;
    }

    /**
     * Special handling on dispose to avoid mutating the shared fields.
     */
    public void disposeScratchTile() {
        tileItemContainer = null;
        playerExploredTiles = null;
        dispose();
    }

    public boolean isViewShared() {
        return (getGame().getViewOwner() != null);
    }

    /**
     * Get the <code>Region</code> value.
     *
     * @return a <code>Region</code> value
     */
    public Region getRegion() {
        return region;
    }

    /**
     * Set the <code>Region</code> value.
     *
     * @param newRegion The new Region value.
     */
    public void setRegion(final Region newRegion) {
        this.region = newRegion;
    }

    /**
     * Return the discoverable Region of this Tile, or
     * <code>null</code> if there is none.
     *
     * @return a <code>Region</code> value
     */
    public Region getDiscoverableRegion() {
        if (region == null) {
            return null;
        } else {
            return region.getDiscoverableRegion();
        }
    }

    /**
     * Gets the name of this tile, or shows "unexplored" if not explored by player.
     *
     * @return The name as a <code>String</code>.
     */
    public String getNameKey() {
        if (isViewShared()) {
            if (isExplored()) {
                return getType().getNameKey();
            } else {
                return "unexplored";
            }
        } else {
            Player player = getGame().getCurrentPlayer();
            if (player != null) {
                PlayerExploredTile pet = getPlayerExploredTile(player);
                return (pet != null) ? getType().getNameKey() : "unexplored";
            } else {
                logger.warning("player == null");
                return "";
            }
        }
    }

    /**
     * Returns a description of the <code>Tile</code>, with the name of the tile
     * and any improvements on it (road/plow/etc) from <code>TileItemContainer</code>.
     * @return The description label for this tile
     */
    public StringTemplate getLabel() {
        StringTemplate label = StringTemplate.key(type.getNameKey());
        if (tileItemContainer != null) {
            List<String> keys = new ArrayList<String>();
            for (TileItem item : tileItemContainer.getTileItems()) {
                if (item instanceof Resource) {
                    keys.add(((Resource) item).getType().getNameKey());
                } else if (item instanceof TileImprovement
                           && ((TileImprovement) item).isComplete()) {
                    keys.add(((TileImprovement) item).getType().getNameKey());
                }
            }
            if (!keys.isEmpty()) {
                label = StringTemplate.label("/")
                    .add(type.getNameKey());
                for (String key : keys) {
                    label.add(key);
                }
            }
        }
        return label;
    }

    /**
     * Returns the name of this location.
     *
     * @return The name of this location.
     */
    public StringTemplate getLocationName() {
        if (settlement == null) {
            Settlement nearSettlement = null;
            for (Tile tile: getSurroundingTiles(NEAR_RADIUS)) {
                nearSettlement = tile.getSettlement();
                if (nearSettlement != null) {
                    int x = getX() - tile.getX();
                    int y = getY() - tile.getY();
                    double theta = Math.atan2(y, x) + Math.PI/2 + Math.PI/8;
                    if (theta < 0) {
                        theta += 2*Math.PI;
                    }
                    Direction direction = Direction.values()[(int) Math.floor(theta / (Math.PI/4))];

                    return StringTemplate.template("nameLocation")
                        .add("%name%", type.getNameKey())
                        .addStringTemplate("%location%", StringTemplate.template("nearLocation")
                                           .add("%direction%", "direction." + direction.toString())
                                           .addName("%location%", nearSettlement.getName()));
                }
            }
            if (region != null && region.getName() != null) {
                return StringTemplate.template("nameLocation")
                    .add("%name%", type.getNameKey())
                    .add("%location%", region.getNameKey());
            } else {
                return StringTemplate.key(type.getNameKey());
            }
        } else {
            return settlement.getLocationName();
        }
    }

    /**
     * Returns the name of this location for a particular player.
     *
     * @param player The <code>Player</code> to prepare the location name for.
     * @return The name of this location.
     */
    public StringTemplate getLocationNameFor(Player player) {
        return (settlement == null) ? getLocationName()
            : settlement.getLocationNameFor(player);
    }

    /**
     * Get the <code>Style</code> value.
     *
     * @return an <code>int</code> value
     */
    public int getStyle() {
        return style;
    }

    /**
     * Set the <code>Style</code> value.
     *
     * @param newStyle The new Style value.
     */
    public void setStyle(final int newStyle) {
        this.style = newStyle;
    }

    /**
     * Gets the distance in tiles between this <code>Tile</code> and the
     * specified one.
     *
     * @param tile The <code>Tile</code> to check the distance to.
     * @return Distance
     */
    public int getDistanceTo(Tile tile) {
        return getPosition().getDistance(tile.getPosition());
    }

    /**
     * Gets the direction to a neighbouring tile from this one.
     *
     * @param tile The other <code>Tile</code>.
     * @return The direction to the other tile, or null if not a neighbour.
     */
    public Direction getDirection(Tile tile) {
        return getMap().getDirection(this, tile);
    }

    /**
     * Returns the <code>TileItemContainer</code>.
     *
     * @return a <code>TileItemContainer</code> value
     */
    public TileItemContainer getTileItemContainer() {
        return tileItemContainer;
    }

    /**
     * Sets the <code>TileItemContainer</code>.
     *
     * @param newTileItemContainer a <code>TileItemContainer</code> value
     */
    public void setTileItemContainer(TileItemContainer newTileItemContainer) {
        tileItemContainer = newTileItemContainer;
    }

    /**
     * Returns a List of <code>TileImprovements</code>.
     *
     * @return a List of <code>TileImprovements</code>
     */
    public List<TileImprovement> getTileImprovements() {
        if (tileItemContainer == null) {
            return Collections.emptyList();
        } else {
            return tileItemContainer.getImprovements();
        }
    }

    /**
     * Returns a List of completed <code>TileImprovements</code>.
     *
     * @return a List of <code>TileImprovements</code>
     */
    public List<TileImprovement> getCompletedTileImprovements() {
        if (tileItemContainer == null) {
            return Collections.emptyList();
        } else {
            List<TileImprovement> result = new ArrayList<TileImprovement>();
            for (TileImprovement improvement : tileItemContainer.getImprovements()) {
                if (improvement.getTurnsToComplete() == 0) {
                    result.add(improvement);
                }
            }
            return result;
        }
    }

    /**
     * Returns a weighted list of natural disasters than can strike
     * this tile. This list comprises all natural disasters that can
     * strike a tile of this type or a completed tile improvement
     * present.
     *
     * @return a <code>List<RandomChoice<Disaster>></code> value
     */
    public List<RandomChoice<Disaster>> getDisasters() {
        List<RandomChoice<Disaster>> disasters
            = new ArrayList<RandomChoice<Disaster>>();
        disasters.addAll(type.getDisasters());
        for (TileImprovement ti : getCompletedTileImprovements()) {
            disasters.addAll(ti.getType().getDisasters());
        }
        return disasters;
    }


    /**
     * Gets the <code>Unit</code> that is currently defending this
     * <code>Tile</code>.
     * <p>If this tile has a settlement, the units inside the settlement
     * are also considered as potential defenders.
     * <p>As this method is quite expensive, it should not be used to test
     * for the presence of enemy units.
     *
     * @param attacker The target that would be attacking this tile.
     * @return The <code>Unit</code> that has been chosen to defend this
     *         tile.
     */
    public Unit getDefendingUnit(Unit attacker) {
        CombatModel cm = getGame().getCombatModel();
        Unit defender = null;
        float defenderPower = -1.0f;
        float power;

        // Check the units on the tile...
        for (Unit u : getUnitList()) {
            if (isLand() != u.isNaval()) {
                // On land, ships are normally docked in port and
                // cannot defend.  Except if beached (see below).
                // On ocean tiles, land units behave as ship cargo and
                // cannot defend
                power = cm.getDefencePower(attacker, u);
                if (Unit.betterDefender(defender, defenderPower, u, power)) {
                    defender = u;
                    defenderPower = power;
                }
            }
        }

        // ...then a settlement defender if any...
        if ((defender == null || !defender.isDefensiveUnit())
            && getSettlement() != null) {
            Unit u = null;
            try {
                // HACK: The AI is prone to removing all units in a
                // settlement which causes Colony.getDefendingUnit()
                // to throw.
                u = settlement.getDefendingUnit(attacker);
            } catch (IllegalStateException e) {
                logger.warning("Empty settlement: " + settlement.getName());
            }
            // This routine can be called on the client for the pre-combat
            // popup where enemy settlement defenders are not visible,
            // thus u == null is valid.
            if (u != null) {
                power = cm.getDefencePower(attacker, u);
                if (Unit.betterDefender(defender, defenderPower, u, power)) {
                    defender = u;
                    defenderPower = power;
                }
            }
        }

        // ...finally, if we have failed to find a valid defender
        // for a land tile, allow a beached naval unit to defend (and
        // lose) as a last resort.
        if (defender == null && isLand()) defender = getFirstUnit();

        return defender;
    }


    public void dispose() {
        if (settlement != null) {
            settlement.dispose();
        }
        if (tileItemContainer != null) {
            tileItemContainer.dispose();
        }

        super.dispose();
    }

    /**
     * Returns the total amount of Units at this Location. This also includes
     * units in a carrier
     *
     * @return The total amount of Units at this Location.
     */
    public int getTotalUnitCount() {
        int result = 0;
        for (Unit unit : getUnitList()) {
            result++;
            result += unit.getUnitCount();
        }
        return result;
    }

    /**
     * Checks if this <code>Tile</code> contains the specified
     * <code>Locatable</code>.
     *
     * @param locatable The <code>Locatable</code> to test the presence of.
     * @return
     *            <ul>
     *            <li><i>true</i> if the specified <code>Locatable</code> is
     *            on this <code>Tile</code> and
     *            <li><i>false</i> otherwise.
     *            </ul>
     */
    public boolean contains(Locatable locatable) {
        if (locatable instanceof TileItem) {
            return tileItemContainer != null && tileItemContainer.contains((TileItem) locatable);
        } else {
            return super.contains(locatable);
        }
    }

    /**
     * Gets the <code>Map</code> in which this <code>Tile</code> belongs.
     *
     * @return The <code>Map</code>.
     */
    public Map getMap() {
        return getGame().getMap();
    }

    /**
     * Gets whether this tile is connected to the high seas.
     *
     * @return True if this tile is connected to the high seas.
     */
    public boolean isHighSeasConnected() {
        return highSeasCount >= 0;
    }

    /**
     * Gets the high seas count.
     *
     * @return The high seas count value.
     */
    public int getHighSeasCount() {
        return this.highSeasCount;
    }

    /**
     * Set the high seas count.
     *
     * @param count The new high seas count value.
     */
    public void setHighSeasCount(final int count) {
        this.highSeasCount = count;
    }

    /**
     * Get the move-to-Europe state of the tile.
     *
     * @return The move-to-Europe state of the tile.
     */
    public Boolean getMoveToEurope() {
        return moveToEurope;
    }

    /**
     * Set the move-to-Europe state of the tile.
     *
     * @param moveToEurope The new move-to-Europe state for the tile.
     */
    public void setMoveToEurope(Boolean moveToEurope) {
        this.moveToEurope = moveToEurope;
    }

    /**
     * Can a unit move to the high seas from this tile?
     *
     * @return True if a unit can move to high seas from this tile.
     */
    public boolean isDirectlyHighSeasConnected() {
        return (getMoveToEurope() != null) ? getMoveToEurope().booleanValue()
            : (type == null) ? false
            : type.isDirectlyHighSeasConnected();
    }

    /**
     * Check if the tile has been explored.
     *
     * @return true if tile is known.
     */
    public boolean isExplored() {
        return type != null;
    }

    /**
     * Is this tile in the polar regions?
     *
     * @return True if the tile is polar.
     */
    public boolean isPolar() {
        return getMap().isPolar(this);
    }

    /**
     * Returns <code>true</code> if this Tile is a land Tile, 'false' otherwise.
     *
     * @return <code>true</code> if this Tile is a land Tile, 'false' otherwise.
     */
    public boolean isLand() {
        return type != null && !type.isWater();
    }

    /**
     * Returns <code>true</code> if this Tile is forested.
     *
     * @return <code>true</code> if this Tile is forested.
     */
    public boolean isForested() {
        return type != null && type.isForested();
    }

    /**
     * Returns <code>true</code> if this Tile has a River.
     *
     * @return <code>true</code> if this Tile has a River.
     */
    public boolean hasRiver() {
        return tileItemContainer != null && getTileItemContainer().getRiver() != null;
    }

    /**
     * Returns <code>true</code> if this Tile has a resource on it.
     *
     * @return <code>true</code> if this Tile has a resource on it.
     */
    public boolean hasResource() {
        return tileItemContainer != null && getTileItemContainer().getResource() != null;
    }

    /**
     * Returns <code>true</code> if this Tile has a lostCityRumour on it.
     *
     * @return <code>true</code> if this Tile has a lostCityRumour on it.
     */
    public boolean hasLostCityRumour() {
        return tileItemContainer != null && getTileItemContainer().getLostCityRumour() != null;
    }

    /**
     * Returns <code>true</code> if this Tile has a road.
     *
     * @return <code>true</code> if this Tile has a road.
     */
    public boolean hasRoad() {
        return tileItemContainer != null && getTileItemContainer().getRoad() != null;
    }

    /**
     * Returns the road on this tile, if there is one, and
     * <code>null</code> otherwise.
     *
     * @return a <code>TileImprovement</code> value
     */
    public TileImprovement getRoad() {
        if (tileItemContainer == null) {
            return null;
        } else {
            return getTileItemContainer().getRoad();
        }
    }

    /**
     * Returns the type of this Tile. Returns UNKNOWN if the type of this Tile
     * is unknown.
     *
     * @return The type of this Tile.
     */
    public TileType getType() {
        return type;
    }

    /**
     * The nation that consider this tile to be their property.
     *
     * @return The player owning this tile.
     */
    public Player getOwner() {
        return owner;
    }

    /**
     * Sets the nation that should consider this tile to be their property.
     *
     * @param owner The player, new owner of this tile.
     * @see #getOwner
     */
    public void setOwner(Player owner) {
        this.owner = owner;
    }

    /**
     * Puts a <code>Settlement</code> on this <code>Tile</code>. A
     * <code>Tile</code> can only have one <code>Settlement</code> located
     * on it. The <code>Settlement</code> will also become the owner of this
     * <code>Tile</code>.
     *
     * @param s The <code>Settlement</code> that shall be located on this
     *            <code>Tile</code>.
     * @see #getSettlement
     */
    public void setSettlement(Settlement s) {
        settlement = s;
        changeOwningSettlement(s);
    }

    /**
     * Gets the <code>Settlement</code> located on this <code>Tile</code>.
     *
     * @return The <code>Settlement</code> that is located on this
     *         <code>Tile</code> or <i>null</i> if no <code>Settlement</code>
     *         apply.
     * @see #setSettlement
     */
    public Settlement getSettlement() {
        return settlement;
    }

    /**
     * Gets the <code>Colony</code> located on this <code>Tile</code>. Only
     * a convenience method for {@link #getSettlement} that makes sure that
     * the settlement is a colony.
     *
     * @return The <code>Colony</code> that is located on this
     *         <code>Tile</code> or <i>null</i> if none found.
     * @see #getSettlement
     */
    public Colony getColony() {
        return (settlement != null && settlement instanceof Colony)
            ? (Colony) settlement
            : null;
    }

    /**
     * Gets the <code>IndianSettlement</code> located on this
     * <code>Tile</code>. Only a convenience method for {@link
     * #getSettlement} that makes sure that the settlement is a native
     * settlement.
     *
     * @return The <code>IndianSettlement</code> that is located on this
     *         <code>Tile</code> or <i>null</i> if none found.
     * @see #getSettlement
     */
    public IndianSettlement getIndianSettlement() {
        return (settlement != null && settlement instanceof IndianSettlement)
            ? (IndianSettlement) settlement
            : null;
    }

    /**
     * Gets the owning settlement for this tile.
     *
     * @return The <code>Settlement</code> that owns this tile.
     * @see #setOwner
     */
    public Settlement getOwningSettlement() {
        return owningSettlement;
    }

    /**
     * Sets the settlement that owns this tile.
     *
     * @param owner The <code>Settlement</code> to own this tile.
     * @see #getOwner
     */
    public void setOwningSettlement(Settlement owner) {
        this.owningSettlement = owner;
    }

    /**
     * Changes the owning settlement for this tile.
     *
     * @param settlement The new owning <code>Settlement</code> for this tile.
     */
    public void changeOwningSettlement(Settlement settlement) {
        if (owningSettlement != null) {
            owningSettlement.removeTile(this);
        }
        setOwningSettlement(settlement);
        if (settlement != null) {
            settlement.addTile(this);
        }
    }

    /**
     * Change the tile ownership.  Also change the owning settlement
     * as the two are commonly related.
     *
     * @param player The <code>Player</code> to own the tile.
     * @param settlement The <code>Settlement</code> to own the tile.
     */
    public void changeOwnership(Player player, Settlement settlement) {
        Player old = getOwner();
        setOwner(player);
        changeOwningSettlement(settlement);
        updatePlayerExploredTiles(old);
    }

    /**
     * Is this tile under active use?
     *
     * @return True if a colony is using this tile.
     */
    public boolean isInUse() {
        return getOwningSettlement() instanceof Colony
            && ((Colony) getOwningSettlement()).isTileInUse(this);
    }

    /**
     * Adds a tile item to this tile.
     *
     * @param item The <code>TileItem</code> to add.
     */
    private void addTileItem(TileItem item) {
        if (tileItemContainer == null) {
            tileItemContainer = new TileItemContainer(getGame(), this);
        }
        tileItemContainer.addTileItem(item);
        updatePlayerExploredTiles();
    }

    /**
     * Gets the lost city rumour on this <code>Tile</code> if any.
     *
     * @return The <code>LostCityRumour</code> on this tile, or null if none.
     */
    public LostCityRumour getLostCityRumour() {
        return (tileItemContainer == null) ? null
            : tileItemContainer.getLostCityRumour();
    }

    /**
     * Adds a lost city rumour to this tile.
     *
     * @param rumour The <code>LostCityRumour</code> to add.
     */
    public void addLostCityRumour(LostCityRumour rumour) {
        addTileItem(rumour);
    }

    /**
     * Removes the lost city rumour from this <code>Tile</code> if there
     * is one.
     */
    public void removeLostCityRumour() {
        if (tileItemContainer != null) {
            tileItemContainer.removeAll(LostCityRumour.class);
            updatePlayerExploredTiles();
        }
    }

    /**
     * Returns the river on this <code>Tile</code> if any
     * @return River <code>TileImprovement</code>
     */
    public TileImprovement getRiver() {
        if (tileItemContainer == null) {
            return null;
        } else {
            return tileItemContainer.getRiver();
        }
    }

    /**
     * Returns the style of a river <code>TileImprovement</code> on this <code>Tile</code>.
     *
     * @return an <code>TileImprovementStyle</code> value
     */
    public TileImprovementStyle getRiverStyle() {
        if (tileItemContainer == null) {
            return null;
        } else {
            TileImprovement river = tileItemContainer.getRiver();
            if (river == null) {
                return null;
            } else {
                return river.getStyle();
            }
        }
    }

    /**
     * Returns the neighbouring Tile of the given Tile in the given direction.
     *
     * @param direction
     *            The direction in which the neighbour tile is located.
     * @return The neighbouring Tile of the given Tile in the given direction.
     */
    public Tile getNeighbourOrNull(Direction direction) {
        Position position = getPosition();
        if (getMap().isValid(position)) {
            Position neighbourPosition = position.getAdjacent(direction);
            return getMap().getTile(neighbourPosition);
        } else {
            return null;
        }
    }

    /**
     * Determine whether this tile has adjacent tiles that are unexplored.
     *
     * @return True if at least one neighbouring tiles is unexplored.
     */
    public boolean hasUnexploredAdjacent() {
        for (Tile t : getSurroundingTiles(1)) {
            if (!t.isExplored()) return true;
        }
        return false;
    }

    /**
     * Is this a shoreline tile?  The tile can be water or land, and the
     * water can be ocean, river or an inland lake.  If this is true for
     * a land tile with a colony, the colony can build docks.
     *
     * @return True if this tile is shore.
     */
    public boolean isShore() {
        for (Tile t : getSurroundingTiles(1)) {
            if (t.isLand() != this.isLand()) return true;
        }
        return false;
    }

    /**
     * Adds a <code>Resource</code> to this <code>Tile</code>.
     *
     * @param resource The <code>Resource</code> to add.
     */
    public void addResource(Resource resource) {
        if (resource == null) return;
        addTileItem(resource);
    }

    /**
     * Sets the type for this Tile.
     *
     * @param t The new TileType for this Tile.
     */
    public void setType(TileType t) {
        if (t == null) {
            throw new IllegalArgumentException("Tile type must not be null");
        }
        type = t;
        if (tileItemContainer != null) {
            tileItemContainer.removeIncompatibleImprovements();
        }
        if (!isLand()) {
            settlement = null;
        }

        updatePlayerExploredTiles();
    }

     /**
     * Returns the x-coordinate of this Tile.
     *
     * @return The x-coordinate of this Tile.
     */
    public int getX() {
        return x;
    }

    /**
     * Returns the y-coordinate of this Tile.
     *
     * @return The y-coordinate of this Tile.
     */
    public int getY() {
        return y;
    }

    /**
     * Gets the <code>Position</code> of this <code>Tile</code>.
     *
     * @return The <code>Position</code> of this <code>Tile</code>.
     */
    public Position getPosition() {
        return new Position(x, y);
    }

    // TODO: this is used only to update false Tiles, a practice that
    // should be killed with fire.
    public void setPosition(int x, int y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Get the contiguity identifier for this tile.
     *
     * @return A contiguity number.
     */
    public int getContiguity() {
        return contiguity;
    }

    /**
     * Sets the contiguity identifier for this tile.
     *
     * @param contiguity A contiguity number.
     */
    public void setContiguity(int contiguity) {
        this.contiguity = contiguity;
    }

    /**
     * Is this tile connected to another across the same contiguous piece
     * of land or water?
     *
     * @param other The other <code>Tile</code> to check.
     * @return True if the tiles are connected.
     */
    public boolean isConnectedTo(Tile other) {
        return getContiguity() == other.getContiguity();
    }

    /**
     * Gets a <code>Unit</code> that can become active. This is preferably a
     * <code>Unit</code> not currently performing any work.
     *
     * @return A <code>Unit</code> with <code>movesLeft > 0</code> or
     *         <i>null</i> if no such <code>Unit</code> is located on this
     *         <code>Tile</code>.
     */
    public Unit getMovableUnit() {
        if (getFirstUnit() != null) {
            Iterator<Unit> unitIterator = getUnitIterator();
            while (unitIterator.hasNext()) {
                Unit u = unitIterator.next();

                Iterator<Unit> childUnitIterator = u.getUnitIterator();
                while (childUnitIterator.hasNext()) {
                    Unit childUnit = childUnitIterator.next();

                    if (childUnit.getMovesLeft() > 0
                        && childUnit.getState() == Unit.UnitState.ACTIVE) {
                        return childUnit;
                    }
                }

                if (u.getMovesLeft() > 0
                    && u.getState() == Unit.UnitState.ACTIVE) {
                    return u;
                }
            }
        } else {
            return null;
        }

        Iterator<Unit> unitIterator = getUnitIterator();
        while (unitIterator.hasNext()) {
            Unit u = unitIterator.next();

            Iterator<Unit> childUnitIterator = u.getUnitIterator();
            while (childUnitIterator.hasNext()) {
                Unit childUnit = childUnitIterator.next();

                if ((childUnit.getMovesLeft() > 0)) {
                    return childUnit;
                }
            }

            if (u.getMovesLeft() > 0) {
                return u;
            }
        }

        return null;
    }

    /**
     * Gets the <code>Tile</code> where this <code>Location</code> is
     * located or null if no <code>Tile</code> applies.
     *
     * @return This <code>Tile</code>.
     */
    public Tile getTile() {
        return this;
    }

    /**
     * Adds a <code>Locatable</code> to this Location.
     *
     * @param locatable The <code>Locatable</code> to add to this Location.
     */
    public boolean add(Locatable locatable) {
        if (locatable instanceof TileItem) {
            addTileItem((TileItem) locatable);
            return true;
        } else if (locatable instanceof Unit) {
            if (super.add(locatable)) {
                ((Unit)locatable).setState(Unit.UnitState.ACTIVE);
                return true;
            }
            return false;
        } else {
            return super.add(locatable);
        }
    }

    /**
     * Removes a <code>Locatable</code> from this Location.
     *
     * @param locatable The <code>Locatable</code> to remove from this
     *            Location.
     */
    public boolean remove(Locatable locatable) {
        if (locatable instanceof TileItem) {
            Player old = getOwner();
            tileItemContainer.addTileItem((TileItem) locatable);
            updatePlayerExploredTiles(old);
            return true;
        } else {
            return super.remove(locatable);
        }
    }

    /**
     * Checks whether or not the specified locatable may be added to this
     * <code>Location</code>.
     *
     * @param locatable a <code>Locatable</code> value
     * @return a <code>boolean</code> value
     */
    public boolean canAdd(Locatable locatable) {
        if (locatable instanceof Unit) {
            return ((Unit)locatable).isTileAccessible(this);
        } else if (locatable instanceof TileImprovement) {
            return ((TileImprovement) locatable).getType().isTileTypeAllowed(getType());
        } else {
            return false;
        }
    }

    /**
     * The potential of this tile to produce a certain type of goods.
     *
     * @param goodsType The type of goods to check the potential for.
     * @param unitType an <code>UnitType</code> value
     * @return The normal potential of this tile to produce that amount of
     *         goods.
     */
    public int potential(GoodsType goodsType, UnitType unitType) {
        return getTileTypePotential(getType(), goodsType, getTileItemContainer(), unitType);
    }

    /**
     * Gets the maximum potential for producing the given type of goods. The
     * maximum potential is the potential of a tile after the tile has been
     * plowed/built road on.
     *
     * @param goodsType The type of goods.
     * @param unitType an <code>UnitType</code> value
     * @return The maximum potential.
     */
    public int getMaximumPotential(GoodsType goodsType, UnitType unitType) {
        // If we consider maximum potential to the effect of having
        // all possible improvements done, iterate through the
        // improvements and get the bonuses of all related ones.  If
        // there are options to change tiletype using an improvement,
        // consider that too.
        final Specification spec = getSpecification();
        List<TileType> tileTypes = new ArrayList<TileType>();
        tileTypes.add(type);

        // Add to the list the various possible tile type changes
        for (TileImprovementType impType : spec.getTileImprovementTypeList()) {
            if (impType.getChange(type) != null) {
                // There is an option to change TileType
                tileTypes.add(impType.getChange(type));
            }
        }

        int maxProduction = 0;
        for (TileType tileType : tileTypes) {
            float potential = tileType.getProductionOf(goodsType, unitType);
            if (tileType == type && hasResource()) {
                for (TileItem item : tileItemContainer.getTileItems()) {
                    if (item instanceof Resource) {
                        potential = ((Resource)item).getBonus(goodsType, unitType, (int) potential);
                    }
                }
            }
            for (TileImprovementType impType : spec.getTileImprovementTypeList()) {
                if (impType.isNatural()
                    || !impType.isTileTypeAllowed(tileType)) continue;
                if (impType.getBonus(goodsType) > 0) {
                    potential = impType.getProductionModifier(goodsType).applyTo(potential);
                }
            }
            maxProduction = Math.max((int)potential, maxProduction);
        }
        return maxProduction;
    }

    /**
     * Describe <code>getProductionBonus</code> method here.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return a <code>Modifier</code> value
     */
    public List<Modifier> getProductionModifiers(GoodsType goodsType,
                                                 UnitType unitType) {
        List<Modifier> result = new ArrayList<Modifier>();
        result.addAll(type.getProductionBonus(goodsType));
        if (tileItemContainer != null) {
            result.addAll(tileItemContainer.getProductionModifiers(goodsType,
                                                                   unitType));
        }
        return result;
    }

    /**
     * Checks whether this <code>Tile</code> can have a road or not. This
     * method will return <code>false</code> if a road has already been built.
     *
     * @return The result.
     */
    public boolean canGetRoad() {
        return isLand() && (tileItemContainer == null || tileItemContainer.getRoad() == null);
    }

    /**
     * Finds the TileImprovement of a given Type, or null if there is no match.
     */
    public TileImprovement findTileImprovementType(TileImprovementType type) {
        if (tileItemContainer == null) {
            return null;
        } else {
            return tileItemContainer.findTileImprovementType(type);
        }
    }

    /**
     * Will check whether this tile has a completed improvement of the given
     * type.
     *
     * Useful for checking whether the tile for instance has a road or is
     * plowed.
     *
     * @param type
     *            The type to check for.
     * @return Whether the tile has the improvement and the improvement is
     *         completed.
     */
    public boolean hasImprovement(TileImprovementType type) {
        if (type.changeContainsTarget(getType())) {
            return true;
        } else if (tileItemContainer != null) {
            return tileItemContainer.hasImprovement(type);
        }
        return false;
    }

    /**
     * Calculates the potential of a certain <code>GoodsType</code>.
     *
     * @param tileType
     *            The <code>TileType</code>.
     * @param goodsType
     *            The <code>GoodsType</code> to check the potential for.
     * @param tiContainer
     *            The <code>TileItemContainer</code> with any TileItems to
     *            give bonuses.
     * @param unitType an <code>UnitType</code> value
     *            The Bonus Fish to be considered if valid
     * @return The amount of goods.
     */
    public static int getTileTypePotential(TileType tileType, GoodsType goodsType,
                                           TileItemContainer tiContainer, UnitType unitType) {
        if (tileType == null || goodsType == null || !goodsType.isFarmed()) {
            return 0;
        }
        // Get tile potential + bonus if any
        int potential = tileType.getProductionOf(goodsType, unitType);
        if (tiContainer != null) {
            potential = tiContainer.getTotalBonusPotential(goodsType, unitType, potential, false);
        }
        return potential;
    }

    /**
     * Sorts GoodsTypes according to potential based on TileType,
     * TileItemContainer if any.
     *
     * @return The sorted GoodsTypes.
     */
    public List<AbstractGoods> getSortedPotential() {
        return getSortedPotential(null, null);
    }

    /**
     * Sorts GoodsTypes according to potential based on TileType,
     * TileItemContainer if any.
     *
     * @param unit the <code>Unit</code> to work on this Tile
     *
     * @return The sorted GoodsTypes.
     */
    public List<AbstractGoods> getSortedPotential(Unit unit) {
        return getSortedPotential(unit.getType(), unit.getOwner());
    }

    /**
     * Sorts GoodsTypes according to potential based on TileType,
     * TileItemContainer if any.
     *
     * @param unitType the <code>UnitType</code> to work on this Tile
     * @param owner the <code>Player</code> owning the unit
     *
     * @return The sorted GoodsTypes.
     */
    public List<AbstractGoods> getSortedPotential(UnitType unitType, Player owner) {

        List<AbstractGoods> goodsTypeList = new ArrayList<AbstractGoods>();
        if (getType() != null) {
            // It is necessary to consider all farmed goods, since the
            // tile might have a resource that produces goods not
            // produced by the tile type.
            for (GoodsType goodsType : getSpecification().getFarmedGoodsTypeList()) {
                int potential = potential(goodsType, unitType);
                if (potential > 0) {
                    goodsTypeList.add(new AbstractGoods(goodsType, potential));
                }
            }
            if (owner == null || owner.getMarket() == null) {
                Collections.sort(goodsTypeList, new Comparator<AbstractGoods>() {
                        public int compare(AbstractGoods o, AbstractGoods p) {
                            return p.getAmount() - o.getAmount();
                        }
                    });
            } else {
                final Market market = owner.getMarket();
                Collections.sort(goodsTypeList, new Comparator<AbstractGoods>() {
                        public int compare(AbstractGoods o, AbstractGoods p) {
                            return market.getSalePrice(p.getType(), p.getAmount())
                                - market.getSalePrice(o.getType(), o.getAmount());
                        }
                    });
            }
        }
        return goodsTypeList;
    }

    /**
     * This method is called only when a new turn is beginning. It
     * will reduce the quantity of the bonus <code>Resource</code>
     * that is on the tile, if any and if applicable.
     *
     * @return The resource if it is exhausted by this call (so it can
     *     be used in a message), otherwise null.
     * @see ResourceType
     */
    public Resource expendResource(GoodsType goodsType, UnitType unitType, Settlement settlement) {
        if (hasResource() && tileItemContainer.getResource().getQuantity() != -1) {
            Resource resource = tileItemContainer.getResource();
            // Potential of this Tile and Improvements
            // TODO: review
            int potential = getTileTypePotential(getType(), goodsType, tileItemContainer, unitType);
            for (TileItem item : tileItemContainer.getTileItems()) {
                if (item instanceof TileImprovement) {
                    potential += ((TileImprovement) item).getBonus(goodsType);
                }
            }

            if (resource.useQuantity(goodsType, unitType, potential) == 0) {
                tileItemContainer.removeTileItem(resource);
                updatePlayerExploredTiles();
                return resource;
            }
        }
        return null;
    }

    /**
     * Updates the <code>PlayerExploredTile</code> for each player. This
     * update will only be performed if the player
     * {@link Player#canSee(Tile) can see} this <code>Tile</code>.
     */
    public void updatePlayerExploredTiles() {
        updatePlayerExploredTiles(null);
    }

    /**
     * Updates the <code>PlayerExploredTile</code> for each player. This
     * update will only be performed if the player
     * {@link Player#canSee(Tile) can see} this <code>Tile</code>.
     *
     * @param oldPlayer The optional <code>Player</code> that formerly
     *     had visibility of this tile and should see the change.
     */
    public void updatePlayerExploredTiles(Player oldPlayer) {
        if (playerExploredTiles == null || getGame().getViewOwner() != null) {
            return;
        }
        for (Player player : getGame().getLiveEuropeanPlayers()) {
            if (player == oldPlayer || player.canSee(this)) {
                updatePlayerExploredTile(player, false);
            }
        }
    }

    /**
     * Gets the <code>PlayerExploredTile</code> for the given
     * <code>Player</code>.
     *
     * @param player The <code>Player</code>.
     * @see PlayerExploredTile
     */
    public PlayerExploredTile getPlayerExploredTile(Player player) {
        return (playerExploredTiles == null) ? null
            : playerExploredTiles.get(player);
    }

    /**
     * Updates the information about this <code>Tile</code> for the given
     * <code>Player</code>.
     *
     * @param player The <code>Player</code>.
     * @param full If true, also update any hidden information specific to a
     *    settlement present on the tile.
     */
    public void updatePlayerExploredTile(Player player, boolean full) {
        if (playerExploredTiles == null || getGame().getViewOwner() != null
            || !player.isEuropean()) {
            return;
        }
        PlayerExploredTile pet = playerExploredTiles.get(player);
        if (pet == null) {
            pet = new PlayerExploredTile(getGame(), player, this);
            playerExploredTiles.put(player, pet);
        }
        pet.update(full);
    }

    /**
     * Checks if this <code>Tile</code> has been explored by the given
     * <code>Player</code>.
     *
     * @param player The <code>Player</code>.
     * @return <code>true</code> if this <code>Tile</code> has been explored
     *         by the given <code>Player</code> and <code>false</code>
     *         otherwise.
     */
    public boolean isExploredBy(Player player) {
        if (!player.isEuropean()) return true;
        if (!isExplored()) return false;
        return getPlayerExploredTile(player) != null;
    }

    /**
     * Sets this <code>Tile</code> to be explored by the given
     * <code>Player</code>.
     *
     * @param player The <code>Player</code>.
     * @param explored <code>true</code> if this <code>Tile</code> should be
     *            explored by the given <code>Player</code> and
     *            <code>false</code> otherwise.
     */
    public void setExploredBy(Player player, boolean explored) {
        if (!player.isEuropean()) return;
        if (explored) {
            updatePlayerExploredTile(player, false);
        } else {
            if (playerExploredTiles != null) {
                playerExploredTiles.remove(player);
            }
        }
    }

    /**
     * This is a hack.  When a missionary is removed, its player
     * disposes of it.  However they can still exist in the PETs.
     * Ideally players that can not see the change should still see
     * the old missionary, but referring to a disposed unit is a Bad
     * Thing.  For now, we clean up the PET-missionaries but do not
     * explicitly update the rest of the PET.  This needs to go away
     * at next save-break when we properly virtualize the settlements.
     *
     * @param old The old missionary <code>Unit</code> to fix.
     */
    public void fixMissionary(Unit old) {
        for (PlayerExploredTile pet : playerExploredTiles.values()) {
            if (pet.getMissionary() == old) pet.setMissionary(null);
        }
    }


    /**
     * Returns the number of turns it takes for a non-expert pioneer to perform
     * the given <code>TileImprovementType</code>. It will check if it is valid
     * for this <code>TileType</code>.
     *
     * @param workType The <code>TileImprovementType</code>
     *
     * @return The number of turns it should take a non-expert pioneer to finish
     *         the work.
     */
    public int getWorkAmount(TileImprovementType workType) {
        if (workType == null) {
            return -1;
        }
        if (!workType.isTileAllowed(this)) {
            return -1;
        }
        // Return the basic work turns + additional work turns
        return (getType().getBasicWorkTurns() + workType.getAddWorkTurns());
    }

    /**
     * Returns the unit who is occupying the tile
     * @return the unit who is occupying the tile
     * @see #isOccupied()
     */
    public Unit getOccupyingUnit() {
        Unit unit = getFirstUnit();
        Player owner = null;
        if (getOwningSettlement() != null) {
            owner = getOwningSettlement().getOwner();
        }
        if (owner != null && unit != null && unit.getOwner() != owner
            && unit.getOwner().atWarWith(owner)) {
            for (Unit enemyUnit : getUnitList()) {
                if (enemyUnit.isOffensiveUnit()) return enemyUnit;
            }
        }
        return null;
    }

    /**
     * Checks whether there is a fortified enemy unit in the tile.
     * Units can't produce in occupied tiles
     * @return <code>true</code> if an fortified enemy unit is in the tile
     */
    public boolean isOccupied() {
        return getOccupyingUnit() != null;
    }

    /**
     * Determines whether this tile is adjacent to the specified tile.
     *
     * @param tile A potentially adjacent <code>Tile</code>.
     * @return <code>true</code> if the tile is adjacent to this tile
     */
    public boolean isAdjacent(Tile tile) {
        return (tile == null) ? false : this.getDistanceTo(tile) == 1;
    }

    /**
     * Gets the adjacent Tile in a given direction.
     *
     * @param direction The <code>Direction</code> to check.
     * @return The adjacent tile in the specified direction, or null
     *     if invalid.
     */
    public Tile getAdjacentTile(Direction direction) {
        return getMap().getAdjacentTile(getX(), getY(), direction);
    }
    
    /**
     * Gets all the tiles surrounding a tile within the given range.
     * The center tile is not included.
     *
     * @param range How far away do we need to go starting from this.
     * @return The tiles surrounding this tile.
     */
    public Iterable<Tile> getSurroundingTiles(int range) {
        return getMap().getCircleTiles(this, true, range);
    }

    /**
     * Gets all the tiles surrounding this tile within the given
     * inclusive upper and lower bounds.
     *
     * getSurroundingTiles(r) is equivalent to getSurroundingTiles(1, r), 
     * thus this tile is included if rangeMin is zero.
     *
     * @param rangeMin The inclusive minimum distance from this tile.
     * @param rangeMax The inclusive maximum distance from this tile.
     * @return A list of the tiles surrounding this tile.
     */
    public List<Tile> getSurroundingTiles(int rangeMin, int rangeMax) {
        List<Tile> result = new ArrayList<Tile>();
        if (rangeMin > rangeMax || rangeMin < 0) return result;

        if (rangeMin == 0) result.add(this);

        if (rangeMax > 0) {
            for (Tile t : getSurroundingTiles(rangeMax)) {
                // add all tiles up to rangeMax
                result.add(t);
            }
        }
        if (rangeMin > 1) {
            for (Tile t : getSurroundingTiles(rangeMin - 1)) {
                // remove the tiles closer than rangeMin
                result.remove(t);
            }
        }
        return result;
    }

    /**
     * Finds the nearest settlement to this tile.
     *
     * @param owner If non-null, the settlement should be owned by this player.
     * @param radius The maximum radius of the search.
     * @return The nearest settlement, or null if none.
     */
    public Settlement getNearestSettlement(Player owner, int radius) {
        if (radius <= 0) radius = INFINITY;
        Map map = getGame().getMap();
        for (Tile t : map.getCircleTiles(this, true, radius)) {
            if (t == this) continue;
            Settlement settlement = t.getSettlement();
            if (settlement != null
                && (owner == null || settlement.getOwner() == owner)) {
                return settlement;
            }
        }
        return null;
    }

    /**
     * Finds a safe tile to put a unit on, near to this one.
     * Useful on return from Europe.
     *
     * @param player The owner of the unit to place (may be null).
     * @param random An optional pseudo-random number source.
     * @return A vacant tile near this one.
     */
    public Tile getSafeTile(Player player, Random random) {
        if ((getFirstUnit() == null
                || getFirstUnit().getOwner() == player)
            && (getSettlement() == null
                || getSettlement().getOwner() == player)) {
            return this;
        }

        for (int r = 1; true; r++) {
            List<Tile> tiles = getSurroundingTiles(r, r);
            if (random != null) Collections.shuffle(tiles, random);
            for (Tile t : tiles) {
                if ((t.getFirstUnit() == null
                        || t.getFirstUnit().getOwner() == player)
                    && (t.getSettlement() == null
                        || t.getSettlement().getOwner() == player)) {
                    return t;
                }
            }
        }
    }


    // Serialization

    /**
     * Write a minimal version of the tile.  Useful if the player
     * has not explored the tile.
     *
     * @param out The target stream.
     */
    public void toXMLMinimal(XMLStreamWriter out) throws XMLStreamException {
        out.writeStartElement(getXMLElementTagName());
        super.writeAttributes(out);
        out.writeAttribute("x", Integer.toString(x));
        out.writeAttribute("y", Integer.toString(y));
        out.writeAttribute("style", Integer.toString(style));
        if (moveToEurope != null) {
            out.writeAttribute("moveToEurope", Boolean.toString(moveToEurope));
        }
        out.writeEndElement();
    }

    /**
     * This method writes an XML-representation of this object to the given
     * stream.
     *
     * <br>
     * <br>
     *
     * Only attributes visible to the given <code>Player</code> will be added
     * to that representation if <code>showAll</code> is set to
     * <code>false</code>.
     *
     * @param out The target stream.
     * @param player The <code>Player</code> this XML-representation should be
     *            made for, or <code>null</code> if
     *            <code>showAll == true</code>.
     * @param showAll Only attributes visible to <code>player</code> will be
     *            added to the representation if <code>showAll</code> is set
     *            to <i>false</i>.
     * @param toSavedGame If <code>true</code> then information that is only
     *            needed when saving a game is added.
     * @throws XMLStreamException if there are any problems writing to the
     *             stream.
     */
    protected void toXMLImpl(XMLStreamWriter out, Player player,
                             boolean showAll, boolean toSavedGame)
        throws XMLStreamException {
        if (!showAll) {
            if (toSavedGame) {
                logger.warning("toSavedGame is true, but showAll is false");
            }
            if (player == null) {
                logger.warning("player is null, but showAll is false");
            }
        }
        PlayerExploredTile pet = (showAll || toSavedGame) ? null
            : getPlayerExploredTile(player);

        // Start element:
        out.writeStartElement(getXMLElementTagName());

        super.writeAttributes(out);
        out.writeAttribute("x", Integer.toString(x));
        out.writeAttribute("y", Integer.toString(y));
        out.writeAttribute("style", Integer.toString(style));

        writeAttribute(out, "type", getType());
        writeAttribute(out, "region", getRegion());
        if (moveToEurope != null) {
            out.writeAttribute("moveToEurope", Boolean.toString(moveToEurope));
        }

        // Compatibility note: this used to be a boolean
        if (highSeasCount >= 0) {
            out.writeAttribute("connected", Integer.toString(highSeasCount));
        }

        if (contiguity >= 0) {
            out.writeAttribute("contiguity", Integer.toString(contiguity));
        }

        if (showAll || toSavedGame || player.canSee(this)) {
            if (owner != null) {
                out.writeAttribute("owner", owner.getId());
            }
            if (owningSettlement != null) {
                out.writeAttribute("owningSettlement",
                    owningSettlement.getId());
            }
        } else if (pet != null) {
            if (pet.getOwner() != null) {
                out.writeAttribute("owner", pet.getOwner().getId());
            }
            if (pet.getOwningSettlement() != null) {
                out.writeAttribute("owningSettlement",
                    pet.getOwningSettlement().getId());
            }
        }
        // End of attributes

        if (showAll || toSavedGame || player.canSee(this)) {
            if (settlement != null) {
                settlement.toXML(out, player, showAll, toSavedGame);
            }
            // Show enemy units if there is no enemy settlement.
            if ((showAll || toSavedGame || settlement == null
                 || settlement.getOwner() == player)
                && !isEmpty()) {
                super.writeChildren(out, player, showAll, toSavedGame);
            }
        } else if (pet != null) {
            // Only display the settlement if we know it owns the tile
            // and we have a useful level of information about it.
            // This is a compromise, but something more precise is too
            // complex for the present.
            if (settlement != null
                && settlement == pet.getOwningSettlement()
                && settlement.getOwner() == pet.getOwner()
                && !(settlement instanceof Colony
                    && pet.getColonyUnitCount() <= 0)) {
                settlement.toXML(out, player, showAll, toSavedGame);
            }
        }
        if (tileItemContainer != null) {
            tileItemContainer.toXML(out, player, showAll, toSavedGame);
        }

        // Save the pets.
        if (toSavedGame && playerExploredTiles != null) {
            for (Entry<Player, PlayerExploredTile> entry
                     : playerExploredTiles.entrySet()) {
                entry.getValue().toXML(out, entry.getKey(),
                    showAll, toSavedGame);
            }
        }

        out.writeEndElement();
    }

    /**
     * {@inheritDoc}
     */
    protected void readAttributes(XMLStreamReader in)
        throws XMLStreamException {
        super.readAttributes(in);

        x = Integer.parseInt(in.getAttributeValue(null, "x"));

        y = Integer.parseInt(in.getAttributeValue(null, "y"));

        style = getAttribute(in, "style", 0);

        String typeString = in.getAttributeValue(null, "type");
        if (typeString != null) {
            type = getSpecification().getTileType(typeString);
        }

        String str = in.getAttributeValue(null, "connected");
        if (str == null || "".equals(str)) {
            highSeasCount = -1;
            // @compat 0.10.5
            // High seas should have connected==0.  If it does not, this
            // is probably an old save file, so flag a recalculation.
            if ("model.tile.highSeas".equals(typeString)) {
                highSeasCount = Tile.FLAG_RECALCULATE;
            }
            // @end compatibility code
        } else {
            try {
                highSeasCount = Integer.parseInt(str);
            } catch (NumberFormatException nfe) {
                highSeasCount = -1;
                // @compat 0.10.5
                // < 0.10.6 used to have a simple boolean connected
                // attribute, but it is now highSeasCount, the number of
                // tiles to get to a tile where a unit can move
                // directly to the high seas.
                highSeasCount = Tile.FLAG_RECALCULATE;
                // @end compatibility code
            }
        }

        owner = getFreeColGameObject(in, "owner", Player.class, null);

        region = getFreeColGameObject(in, "region", Region.class, null);

        moveToEurope = (in.getAttributeValue(null, "moveToEurope") == null)
            ? null
            : getAttribute(in, "moveToEurope", false);

        contiguity = getAttribute(in, "contiguity", -1);

        final String owningSettlementStr
            = in.getAttributeValue(null, "owningSettlement");
        Settlement newOwningSettlement = null;
        if (owningSettlementStr != null) {
            newOwningSettlement = getGame().getFreeColGameObject(owningSettlementStr,
                                                                 Settlement.class);
            if (newOwningSettlement == null) {
                if (owningSettlementStr.startsWith(IndianSettlement.getXMLElementTagName())) {
                    newOwningSettlement = new IndianSettlement(getGame(), owningSettlementStr);
                } else if (owningSettlementStr.startsWith(Colony.getXMLElementTagName())) {
                    newOwningSettlement = new Colony(getGame(), owningSettlementStr);
                } else {
                    logger.warning("Unknown type of Settlement: "
                        + owningSettlementStr);
                }
            }
        } else {
            newOwningSettlement = null;
        }

        changeOwningSettlement(newOwningSettlement);
    }

    /**
     * {@inheritDoc}
     */
    protected void readChildren(XMLStreamReader in) throws XMLStreamException {
        Settlement oldSettlement = settlement;
        Player oldSettlementOwner = (settlement == null) ? null
            : settlement.getOwner();
        settlement = null;

        super.readChildren(in);

        // Player settlement list is not passed in player updates
        // so do it here.  TODO: something better.
        Player settlementOwner = (settlement == null) ? null
            : settlement.getOwner();
        if (settlement == null && oldSettlement != null) {
            // Settlement disappeared
            logger.info("Settlement " + oldSettlement.getName() + " removed.");
            oldSettlementOwner.removeSettlement(oldSettlement);
        } else if (settlement != null && oldSettlement == null) {
            // Settlement appeared
            settlementOwner.addSettlement(settlement);
            owner = settlementOwner;
        } else if (settlementOwner != oldSettlementOwner) {
            // Settlement changed owner
            logger.info("Settlement " + oldSettlement.getName() + " captured"
                + " from " + oldSettlement.getOwner()
                + " to " + settlementOwner);
            settlement.setOwner(settlementOwner);
            oldSettlementOwner.removeSettlement(oldSettlement);
            settlementOwner.addSettlement(settlement);
            owner = settlementOwner;
        }

        if (getColony() != null && getColony().isTileInUse(this)) {
            getColony().invalidateCache();
        }
    }

    /**
     * {@inheritDoc}
     */
    protected void readChild(XMLStreamReader in) throws XMLStreamException {
        if (in.getLocalName().equals(Colony.getXMLElementTagName())) {
            settlement = updateFreeColGameObject(in, Colony.class);
        } else if (in.getLocalName().equals(IndianSettlement.getXMLElementTagName())) {
            settlement = updateFreeColGameObject(in, IndianSettlement.class);
        } else if (in.getLocalName().equals(UNITS_TAG_NAME)) { // @compat 0.10.1
            while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
                if (in.getLocalName().equals(Unit.getXMLElementTagName())) {
                    add(updateFreeColGameObject(in, Unit.class));
                }
            }
            // end compatibility code
        } else if (in.getLocalName().equals(TileItemContainer.getXMLElementTagName())) {
            tileItemContainer = getGame().getFreeColGameObject(in.getAttributeValue(null, ID_ATTRIBUTE),
                                                               TileItemContainer.class);
            if (tileItemContainer != null) {
                tileItemContainer.readFromXML(in);
            } else {
                tileItemContainer = new TileItemContainer(getGame(), this, in);
            }
        } else if (in.getLocalName().equals(PlayerExploredTile.getXMLElementTagName())) {
            // Only from a savegame:
            Player player = getGame().getFreeColGameObject(in.getAttributeValue(null, "player"),
                                                           Player.class);
            PlayerExploredTile pet = getPlayerExploredTile(player);
            if (pet == null) {
                pet = new PlayerExploredTile(getGame(), in);
                playerExploredTiles.put(player, pet);
            } else {
                pet.readFromXML(in);
            }
        } else {
            super.readChild(in);
        }

        // Fix bug where missionary locations get cleared.
        // TODO: Remove this when PETs have been revised to not store
        // the actual unit.
        Unit missionary = null;
        if (settlement instanceof IndianSettlement
            && (missionary = ((IndianSettlement)settlement).getMissionary())
            != null) {
            missionary.setLocationNoUpdate(settlement);
        }
    }

    /**
     * Fixes visible pets where there is a settlement present but the
     * tile is not owned correctly as ownership was not implemented in
     * 0.9.x.
     * Need to do this after reading the game so that canSee() is valid.
     * TODO: remove when 0.9.x is not supported.
     */
    public void fixup09x() {
        if (playerExploredTiles == null) return;
        for (Entry<Player, PlayerExploredTile> e
                 : playerExploredTiles.entrySet()) {
            Player p = e.getKey();
            PlayerExploredTile pet = e.getValue();
            if (settlement != null) {
                if (pet.getOwner() == null
                    || pet.getOwningSettlement() == null) {
                    if (p.canSee(this)) {
                        // Correct with an ordinary update
                        pet.update(false);
                    } else if (settlement instanceof Colony) {
                        if (pet.getColonyUnitCount() > 0) {
                            // Have seen the colony, update the ownership
                            // and the stockade level but not the unit count
                            // as that is the one that was seen.
                            pet.setOwner(settlement.getOwner());
                            pet.setOwningSettlement(settlement);
                            pet.setColonyStockadeKey(((Colony) settlement)
                                .getStockadeKey());
                        }
                    } else if (settlement instanceof IndianSettlement) {
                        // Unclear what has been seen, update just the ownership
                        pet.setOwner(settlement.getOwner());
                        pet.setOwningSettlement(settlement);
                    }
                }
            } else {
                if (pet.getOwningSettlement() != null
                    && pet.getOwner() == null) {
                    pet.setOwner(pet.getOwningSettlement().getOwner());
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "[" + getId()
            + " " + ((type == null) ? "unknown"
                : Utils.lastPart(type.getId(), "."))
            + " " + x + "," + y
            + ((getSettlement() == null) ? "" : " " + getSettlement().getName())
            + "]";
    }

    /**
     * Returns the tag name of the root element representing this object.
     *
     * @return "tile".
     */
    public static String getXMLElementTagName() {
        return "tile";
    }
}
