/**
 *  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;


/**
 * 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";

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

    private TileType type;

    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;

    /**
     * Whether this tile is connected to Europe.
     */
    private boolean connected = false;

    /**
     * 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;

    /**
     * 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.connected = connected;
        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;
    }

    /**
     * Are two locations non-null and either the same or at the same tile.
     *
     * @param l1 The first <code>Location</code>.
     * @param l2 The second <code>Location</code>.
     * @return True if this location is the same or at the same tile.
     */
    public static boolean isSameTile(Location l1, Location l2) {
        return (l1 == null || l2 == null) ? false
            : (l1 == l2) ? true
            : l1.getTile() == l2.getTile();
    }

    /**
     * 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;
        }
    }

    /**
     * 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();
    }

    /**
     * Gets the first <code>Unit</code> on this tile.
     *
     * @return The first <code>Unit</code> on this tile.
     */
    public Unit getFirstUnit() {
        return isEmpty() ? null : getUnitList().get(0);
    }

    /**
     * Gets the last <code>Unit</code> on this tile.
     *
     * @return The last <code>Unit</code> on this tile.
     */
    public Unit getLastUnit() {
        return isEmpty() ? null : getUnitList().get(getUnitCount() - 1);
    }

    /**
     * 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();
    }

    /**
     * Whether this tile is connected to Europe.
     *
     * @return a <code>boolean</code> value
     */
    public boolean isConnected() {
        return (connected || (type != null && type.isConnected()));
    }

    /**
     * Set the <code>Connected</code> value.
     *
     * @param newConnected The new Connected value.
     */
    public void setConnected(final boolean newConnected) {
        this.connected = newConnected;
    }

    /**
     * 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 Europe from this tile?
     *
     * @return True if a unit can move to Europe from this tile.
     */
    public boolean canMoveToEurope() {
        return (getMoveToEurope() != null) ? getMoveToEurope()
            : (type == null) ? false
            : (type.hasAbility("model.ability.moveToEurope")) ? true
            // TODO: remove this when we are confident all the maps have
            // appropriate moveToEurope overrides.
            : isAdjacentToMapEdge() && type.isWater();
    }

    /**
     * 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>int</code> value
     */
    public int getRiverStyle() {
        if (tileItemContainer == null) {
            return 0;
        } else {
            TileImprovement river = tileItemContainer.getRiver();
            if (river == null) {
                return 0;
            } 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, otherwise false
     */
    public boolean hasUnexploredAdjacent() {
        for (Tile t: getSurroundingTiles(1)) {
            if (!t.isExplored()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if this tile has at least one adjacent land tile (if water),
     * or at least one adjacent water tile (if land).
     *
     * @return a <code>boolean</code> value
     */
    public boolean isCoast() {
        for (Direction direction : Direction.values()) {
            Tile otherTile = getNeighbourOrNull(direction);
            if (otherTile != null && otherTile.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;
    }

    /**
     * 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) {
            Unit unit = (Unit) locatable;
            if (unit.isNaval()) {
                return isLand() ? getSettlement() != null : true;
            } else {
                return isLand();
            }
        } 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.

        List<TileType> tileTypes = new ArrayList<TileType>();
        tileTypes.add(getType());

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

        int maxProduction = 0;

        for (TileType tileType : tileTypes) {
            float potential = tileType.getProductionOf(goodsType, unitType);
            if (tileType == getType() && hasResource()) {
                for (TileItem item : tileItemContainer.getTileItems()) {
                    if (item instanceof Resource) {
                        potential = ((Resource) item).getBonus(goodsType, unitType, (int) potential);
                    }
                }
            }
            for (TileImprovementType impType : getSpecification().getTileImprovementTypeList()) {
                if (impType.isNatural() || !impType.isTileTypeAllowed(tileType)) {
                    continue;
                } else 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 Set<Modifier> getProductionBonus(GoodsType goodsType, UnitType unitType) {
        Set<Modifier> result = new HashSet<Modifier>();
        result.addAll(type.getProductionBonus(goodsType));
        if (tileItemContainer != null) {
            Resource resource = tileItemContainer.getResource();
            if (resource != null) {
                result.addAll(resource.getType().getProductionModifier(goodsType, unitType));
            }
            if (!result.isEmpty()) {
                result.addAll(tileItemContainer.getProductionBonus(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);
            }
        }
    }


    /**
     * 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
            && owner.getStance(unit.getOwner()) != Stance.ALLIANCE) {
            for(Unit enemyUnit : getUnitList()) {
                if (enemyUnit.isOffensiveUnit()
                    && enemyUnit.getState() == Unit.UnitState.FORTIFIED) {
                    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 position adjacent Tile to a given Tile, in a given
     * direction.
     *
     * @param direction The direction (N, NE, E, etc.)
     * @return Adjacent tile
     */
     public Tile getAdjacentTile(Direction direction) {
         int x = getX() + ((getY() & 1) != 0 ?
                               direction.getOddDX() : direction.getEvenDX());
         int y = getY() + ((getY() & 1) != 0 ?
                               direction.getOddDY() : direction.getEvenDY());
         return getMap().getTile(x, y);
     }

     /**
      * Returns all the tiles surrounding this tile within the
      * given range. This 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(final int range) {
         return new Iterable<Tile>() {
             public Iterator<Tile> iterator() {
                 final Iterator<Position> m = (range == 1)
                     ? getMap().getAdjacentIterator(getPosition())
                     : getMap().getCircleIterator(getPosition(), true, range);

                 return new Iterator<Tile>() {
                     public boolean hasNext() {
                         return m.hasNext();
                     }

                     public Tile next() {
                         return getMap().getTile(m.next());
                     }

                     public void remove() {
                         m.remove();
                     }
                 };
             }
         };
     }


     /**
      * Returns 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;
     }

    /**
     * Checks if the given <code>Tile</code> is adjacent to the
     * east or west edge of the map.
     *
     * @return <code>true</code> if the given tile is at the edge of the map.
     */
    public boolean isAdjacentToVerticalMapEdge() {
        if ((getNeighbourOrNull(Direction.E) == null)
            || (getNeighbourOrNull(Direction.W) == null)) {
            return true;
        }
        return false;
    }

    /**
     * Checks if the given <code>Tile</code> is adjacent to the edge of the
     * map.
     *
     * @return <code>true</code> if the given tile is at the edge of the map.
     */
    public boolean isAdjacentToMapEdge() {
        for (Direction direction : Direction.values()) {
            if (getNeighbourOrNull(direction) == null) {
                return true;
            }
        }
        return false;
    }


    /**
     * 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();
        Iterator<Position> iter = map.getCircleIterator(getPosition(), true,
                                                        radius);
        while (iter.hasNext()) {
            Tile t = map.getTile(iter.next());
            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;
                }
            }
        }
    }


    /**
     * 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));
        }

        if (connected && !type.isConnected()) {
            out.writeAttribute("connected", Boolean.toString(true));
        }

        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);
        }
        connected = getAttribute(in, "connected", false);
        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);

        final String owningSettlementStr
            = in.getAttributeValue(null, "owningSettlement");
        Settlement newOwningSettlement = null;
        if (owningSettlementStr != null) {
            newOwningSettlement = (Settlement) getGame().getFreeColGameObject(owningSettlementStr);
            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.");
                }
            }
        } 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
            oldSettlement.setOwner(null);
            oldSettlementOwner.removeSettlement(oldSettlement);
        } else if (settlement != null && oldSettlement == null) {
            // Settlement appeared
            settlementOwner.addSettlement(settlement);
            owner = settlementOwner;
        } else if (settlementOwner != oldSettlementOwner) {
            // Settlement changed owner
            oldSettlement.setOwner(null);
            oldSettlementOwner.removeSettlement(oldSettlement);
            settlement.setOwner(settlementOwner);
            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 = (TileItemContainer) getGame().getFreeColGameObject(in.getAttributeValue(null, ID_ATTRIBUTE));
            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 = (Player) getGame().getFreeColGameObject(in.getAttributeValue(null, "player"));
            PlayerExploredTile pet = getPlayerExploredTile(player);
            if (pet == null) {
                pet = new PlayerExploredTile(getGame(), in);
                playerExploredTiles.put(player, pet);
            } else {
                pet.readFromXML(in);
            }
        } else {
            super.readChild(in);
        }
    }

    /**
     * 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());
                }
            }
        }
    }

    /**
     * Returns a String representation of this Tile.
     *
     * @return A String representation of this Tile.
     */
    @Override
    public String toString() {
        return "Tile(" + x + "," + y +"):"
            + ((type == null) ? "unknown" : type.getId());
    }

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