/**
 *  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.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
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.client.gui.i18n.Messages;
import net.sf.freecol.common.model.ProductionInfo;
import net.sf.freecol.common.util.RandomChoice;


/**
 * Represents a colony. A colony contains {@link Building}s and
 * {@link ColonyTile}s. The latter represents the tiles around the
 * <code>Colony</code> where working is possible.
 */
public class Colony extends Settlement implements Nameable {

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

    public static final String BUILD_QUEUE_TAG = "buildQueueItem";
    public static final String POPULATION_QUEUE_TAG = "populationQueueItem";
    public static final String REARRANGE_WORKERS = "rearrangeWorkers";
    public static final int LIBERTY_PER_REBEL = 200;

    public static final Ability HAS_PORT = new Ability("model.ability.hasPort");

    public static final FreeColGameObjectType SOL_MODIFIER_SOURCE =
        new FreeColGameObjectType("model.source.solModifier");

    public static enum ColonyChangeEvent {
        POPULATION_CHANGE,
        PRODUCTION_CHANGE,
        BONUS_CHANGE,
        WAREHOUSE_CHANGE,
        BUILD_QUEUE_CHANGE,
        UNIT_TYPE_CHANGE
    }

    public static enum NoBuildReason {
        NONE,
        NOT_BUILDING,
        NOT_BUILDABLE,
        POPULATION_TOO_SMALL,
        MISSING_BUILD_ABILITY,
        MISSING_ABILITY,
        WRONG_UPGRADE,
        LIMIT_EXCEEDED
    }

    private class Occupation {
        public WorkLocation workLocation;
        public GoodsType workType;

        public Occupation(WorkLocation workLocation, GoodsType workType) {
            this.workLocation = workLocation;
            this.workType = workType;
        }
    }


    /** A list of ColonyTiles. */
    protected final List<ColonyTile> colonyTiles = new ArrayList<ColonyTile>();

    /** A map of Buildings, indexed by the Id of their basic type. */
    protected final java.util.Map<String, Building> buildingMap
        = new HashMap<String, Building>();

    /** A map of ExportData, indexed by the Ids of GoodsTypes. */
    protected final java.util.Map<String, ExportData> exportData
        = new HashMap<String, ExportData>();

    /** The SoL membership this turn. */
    protected int sonsOfLiberty;

    /** The SoL membership last turn. */
    protected int oldSonsOfLiberty;

    /** The number of tories this turn. */
    protected int tories;

    /** The number of tories last turn. */
    protected int oldTories;

    /** The current production bonus. */
    protected int productionBonus;

    /**
     * The number of immigration points. Immigration points are an
     * abstract game concept. They are generated by but are not
     * identical to crosses.
     */
    protected int immigration;

    /**
     * The number of liberty points. Liberty points are an
     * abstract game concept. They are generated by but are not
     * identical to bells.
     */
    protected int liberty;

    // Whether this colony is landlocked
    protected boolean landLocked = true;

    // Will only be used on enemy colonies:
    protected int unitCount = -1;
    protected int displayUnitCount = -1;
    protected String stockadeKey = null;

    /** The turn in which this colony was established. */
    protected Turn established = new Turn(0);

    /** A list of Buildable items. */
    protected BuildQueue<BuildableType> buildQueue =
        new BuildQueue<BuildableType>(this, BuildQueue.CompletionAction.REMOVE_EXCEPT_LAST,
                                      Consumer.COLONY_PRIORITY);

    /** The colonists that may be born. */
    protected BuildQueue<UnitType> populationQueue =
        new BuildQueue<UnitType>(this, BuildQueue.CompletionAction.SHUFFLE,
                                 Consumer.POPULATION_PRIORITY);

    /**
     * Contains information about production and consumption.
     */
    private ProductionCache productionCache = new ProductionCache(this);



    protected Colony() {
        // empty constructor
    }

    /**
     * Constructor for ServerColony.
     *
     * @param game The <code>Game</code> in which this object belongs.
     * @param owner The <code>Player</code> owning this <code>Colony</code>.
     * @param name The name of the new <code>Colony</code>.
     * @param tile The location of the <code>Colony</code>.
     */
    protected Colony(Game game, Player owner, String name, Tile tile) {
        super(game, owner, name, tile);
    }

    /**
     * Initiates a new <code>Colony</code> from an XML representation.
     *
     * @param game The <code>Game</code> this object belongs to.
     * @param in The input stream containing the XML.
     * @throws XMLStreamException if an error occurred during parsing.
     */
    public Colony(Game game, XMLStreamReader in) throws XMLStreamException {
        super(game, in);
        readFromXML(in);
    }

    /**
     * Initiates a new <code>Colony</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 Colony(Game game, String id) {
        super(game, id);
    }


    /**
     * Creates a temporary copy of this colony for planning purposes.
     * The copy is identical except:
     *   - it is obviously not actually present on the map
     *   - it does not appear in the list of player colonies
     *   - it contains no units in its work locations
     *   - its export data is clear
     *   - its build queue is empty
     *   - its production cache is empty
     *   - its name is prefixed with "scratch"
     * Note that this fields is shared--- do not mutate!
     *   + the population queue
     *
     * @return A scratch version of this colony.
     */
    public Colony getScratchColony() {
        Game game = getGame();
        Player owner = getOwner();
        Colony scratch = new Colony(game, owner, "scratch" + getName(),
            getTile().getScratchTile());
        GoodsContainer container = new GoodsContainer(game, scratch);
        for (Goods g : getCompactGoods()) {
            container.addGoods(g.getType(), g.getAmount());
        }
        scratch.setGoodsContainer(container);
        FeatureContainer fc = scratch.getFeatureContainer();
        FeatureContainer.addFeatures(fc, this);
        scratch.colonyTiles.clear();
        for (ColonyTile ct : colonyTiles) {
            Tile wt = ct.getWorkTile();
            Tile t;
            if (ct.isColonyCenterTile()) {
                t = scratch.getTile();
                t.setSettlement(scratch);
            } else {
                t = wt.getScratchTile();
            }
            if (owner.owns(wt)) {
                t.setOwner(owner);
                t.setOwningSettlement(scratch);
            }
            scratch.colonyTiles.add(new ColonyTile(game, scratch, t));
        }
        scratch.buildingMap.clear();
        for (Entry<String, Building> e : buildingMap.entrySet()) {
            scratch.buildingMap.put(e.getKey(),
                new Building(game, scratch, e.getValue().getType()));
        }
        scratch.exportData.clear();
        scratch.established = established;
        scratch.sonsOfLiberty = sonsOfLiberty;
        scratch.oldSonsOfLiberty = oldSonsOfLiberty;
        scratch.tories = tories;
        scratch.oldTories = oldTories;
        scratch.productionBonus = productionBonus;
        scratch.immigration = immigration;
        scratch.liberty = liberty;
        scratch.landLocked = landLocked;
        scratch.buildQueue.clear();
        scratch.populationQueue = populationQueue;
        // ignore unitCount and stockadeKey
        // leave productionCache as is
        return scratch;
    }

    /**
     * Dispose of this scratch colony.  Special handling to avoid mutating
     * the shared fields on dispose.
     */
    public void disposeScratchColony() {
        populationQueue = null;
        for (ColonyTile ct : colonyTiles) {
            ct.getWorkTile().disposeScratchTile();
        }
        colonyTiles.clear();
        dispose();
    }

    /**
     * Finds the corresponding work location in a scratch colony.
     *
     * @param wl The <code>WorkLocation</code> in the original colony.
     * @return The corresponding <code>WorkLocation</code> or null if not found.
     */
    public WorkLocation getCorrespondingWorkLocation(WorkLocation wl) {
        Colony original = wl.getColony();
        // Insist that this is a scratch colony, and the work location
        // is in the original or vice versa.
        if (getName().equals("scratch" + original.getName())
            || original.getName().equals("scratch" + getName())) {
            if (wl instanceof Building) {
                // Types are unique for buildings, so use that as a key
                BuildingType type = ((Building)wl).getType();
                for (Building b : getBuildings()) {
                    if (b.getType() == type) return b;
                }
            } else if (wl instanceof ColonyTile) {
                // ColonyTiles are harder because the underlying tile is
                // also a scratch-version, but the scratch and original
                // tile share item containers.
                Tile workTile = ((ColonyTile)wl).getWorkTile();
                for (ColonyTile c : getColonyTiles()) {
                    if (c.getWorkTile().getTileItemContainer()
                        == workTile.getTileItemContainer()) return c;
                }
            }
        }
        return null;
    }


    /**
     * Is a building type able to be automatically built at no cost.
     * True when the player has a modifier that collapses the cost to zero.
     *
     * @param buildingType a <code>BuildingType</code> value
     * @return True if the building is available at zero cost.
     */
    public boolean isAutomaticBuild(BuildingType buildingType) {
        float value = owner.applyModifier(100f,
                                          "model.modifier.buildingPriceBonus",
                                          buildingType, getGame().getTurn());
        return value == 0f && canBuild(buildingType);
    }

    /**
     * Add a Building to this Colony.
     *
     * @param building a <code>Building</code> value
     */
    public void addBuilding(final Building building) {
        BuildingType buildingType = building.getType().getFirstLevel();
        buildingMap.put(buildingType.getId(), building);
        addFeatures(building.getType());
        invalidateCache();
    }

    /**
     * Remove a building from this Colony.
     *
     * @param building The <code>Building</code> to remove.
     * @return True if the building was removed.
     */
    public boolean removeBuilding(final Building building) {
        BuildingType buildingType = building.getType().getFirstLevel();
        boolean result = buildingMap.remove(buildingType.getId()) != null;
        removeFeatures(building.getType());
        invalidateCache();
        return result;
    }

    /**
     * Determines if this colony can build the given type of equipment.
     * Unlike canBuildEquipment, this takes goods "reserved"
     * for other purposes into account.
     * This colony-specific version also checks for requirements of the
     * current buildable.
     *
     * @param equipmentType an <code>EquipmentType</code> value
     * @return True if the colony can provide the equipment.
     * @see Settlement#canProvideEquipment(EquipmentType equipmentType)
     */
    @Override
    public boolean canProvideEquipment(EquipmentType equipmentType) {
        BuildableType buildable = getCurrentlyBuilding();
        for (AbstractGoods goods : equipmentType.getRequiredGoods()) {
            int available = getGoodsCount(goods.getType());

            int breedingNumber = goods.getType().getBreedingNumber();
            if (breedingNumber != GoodsType.INFINITY) {
                available -= breedingNumber;
            }

            if (buildable != null) {
                for (AbstractGoods ag : buildable.getRequiredGoods()) {
                    if (ag.getType() == goods.getType()) {
                        available -= ag.getAmount();
                        break;
                    }
                }
            }

            if (available < goods.getAmount()) return false;
        }
        return true;
    }

    /**
     * Adds the goods for n of a piece of equipment to the colony.
     *
     * @param type The <code>EquipmentType</code> to add.
     * @param n The number of pieces of equipment (may be negative).
     */
    public void addEquipmentGoods(EquipmentType type, int n) {
        for (AbstractGoods ag : type.getRequiredGoods()) {
            if (ag.getType().isStorable()) {
                addGoods(ag.getType(), n * ag.getAmount());
            }
        }
    }

    /**
     * Returns true if the colony can reduce its population
     * voluntarily. This is generally the case, but can be prevented
     * by buildings such as the stockade.
     *
     * @return a <code>boolean</code> value
     */
    public boolean canReducePopulation() {
        return getUnitCount() >
            FeatureContainer.applyModifierSet(0f, getGame().getTurn(),
                                              getModifierSet("model.modifier.minimumColonySize"));
    }

    /**
     * Gets the message to display if the colony can not reduce its population.
     *
     * @return A string to describing why a colony can not reduce its
     *     population, or null if it can.
     */
    public String getReducePopulationMessage() {
        if (canReducePopulation()) return null;
        String message = "";
        Set<Modifier> modifierSet
            = getModifierSet("model.modifier.minimumColonySize");
        for (Modifier modifier : modifierSet) {
            FreeColObject source = modifier.getSource();
            if (source instanceof BuildingType) {
                // If the modifier source is a building type, use the
                // building in the colony, which may be of a different
                // level to the modifier source.
                // This prevents the stockade modifier from matching a
                // colony-fort, and thus the message attributing the
                // failure to reduce population to a non-existing
                // stockade, BR#3522055.
                source = getBuilding((BuildingType)source).getType();
            }
            return Messages.message(StringTemplate.template("colonyPanel.minimumColonySize")
                .addName("%object%", source));
        }
        return message;
    }

    /**
     * Updates SoL and builds Buildings that are free if possible.
     *
     * @param difference an <code>int</code> value
     */
    public void updatePopulation(int difference) {
        int population = getUnitCount();
        if (population > 0) {
            getTile().updatePlayerExploredTiles();

            updateSoL();
            updateProductionBonus();
        }
    }

    /**
     * Describe <code>getExportData</code> method here.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return an <code>ExportData</code> value
     */
    public ExportData getExportData(final GoodsType goodsType) {
        ExportData result = exportData.get(goodsType.getId());
        if (result == null) {
            result = new ExportData(goodsType);
            setExportData(result);
        }
        return result;
    }

    /**
     * Describe <code>setExportData</code> method here.
     *
     * @param newExportData an <code>ExportData</code> value
     */
    public final void setExportData(final ExportData newExportData) {
        exportData.put(newExportData.getId(), newExportData);
    }

    /**
     * How much of a goods type can be exported from this colony?
     *
     * @param goodsType The <code>GoodsType</code> to export.
     * @return The amount of this type of goods available for export.
     */
    public int getExportAmount(GoodsType goodsType) {
        int present = getGoodsCount(goodsType);
        int exportable = getExportData(goodsType).getExportLevel();
        return (present < exportable) ? 0 : present - exportable;
    }

    /**
     * How much of a goods type can be imported into this colony?
     *
     * @param goodsType The <code>GoodsType</code> to import.
     * @return The amount of this type of goods that can be imported.
     */
    public int getImportAmount(GoodsType goodsType) {
        int present = getGoodsCount(goodsType);
        if (goodsType.isFoodType()) return Integer.MAX_VALUE;
        int capacity = getWarehouseCapacity();
        return (present > capacity) ? 0 : capacity - present;
    }

    /**
     * Returns whether this colony is landlocked, or has access to water.
     *
     * @return <code>true</code> if there are no adjacent tiles to this
     *         <code>Colony</code>'s tile being water tiles.
     */
    public boolean isLandLocked() {
        return landLocked;
    }

    /**
     * Returns whether this colony has undead units.
     *
     * @return whether this colony has undead units.
     */
    public boolean isUndead() {
        final Iterator<Unit> unitIterator = getUnitIterator();
        return unitIterator.hasNext() && unitIterator.next().isUndead();
    }

    /**
     * Sets the owner of this <code>Colony</code>, including all units
     * within, and change main tile nation ownership.
     *
     * @param owner The <code>Player</code> that shall own this
     *            <code>Settlement</code>.
     * @see Settlement#getOwner
     */
    @Override
    public void changeOwner(Player owner) {
        super.changeOwner(owner);
        // Disable all exports
        for (ExportData exportDatum : exportData.values()) {
            exportDatum.setExported(false);
        }
        // Changing the owner might alter bonuses applied by founding fathers:
        updatePopulation(0);
    }

    /**
     * Collect the buildings for producing the given type of goods.
     *
     * @param goodsType The type of goods.
     * @return A <code>List</code> of <code>Building</code>s which produce
     *         the given type of goods.
     */
    public List<Building> getBuildingsForProducing(GoodsType goodsType) {
        List<Building> buildings = new ArrayList<Building>();
        for (Building building : getBuildings()) {
            if (building.getGoodsOutputType() == goodsType) {
                buildings.add(building);
            }
        }
        return buildings;
    }

    /**
     * Collect the buildings for consuming the given type of goods.
     *
     * @param goodsType The type of goods.
     * @return A <code>List</code> of <code>Building</code>s which consume
     *         the given type of goods.
     * @see Goods
     */
    public List<Building> getBuildingsForConsuming(GoodsType goodsType) {
        List<Building> buildings = new ArrayList<Building>();
        for (Building building : getBuildings()) {
            if (building.getGoodsInputType() == goodsType) {
                buildings.add(building);
            }
        }
        return buildings;
    }

    /**
     * Find a building for producing the given type of goods.
     *
     * @param goodsType The type of goods.
     * @return A <code>Building</code> which produces the given type of goods,
     *         or <code>null</code> if such a building can not be found.
     */
    public Building getBuildingForProducing(GoodsType goodsType) {
        List<Building> buildings = getBuildingsForProducing(goodsType);
        return (buildings.isEmpty()) ? null : buildings.get(0);
    }

    /**
     * Find a building for consuming the given type of goods.
     *
     * @param goodsType The type of goods.
     * @return A <code>Building</code> which consumes the given type of goods,
     *         or <code>null</code> if such a building can not be found.
     */
    public Building getBuildingForConsuming(GoodsType goodsType) {
        List<Building> buildings = getBuildingsForConsuming(goodsType);
        return (buildings.isEmpty()) ? null : buildings.get(0);
    }

    /**
     * Gets a list of every work location in this colony.
     *
     * @return The list of work locations.
     */
    public List<WorkLocation> getAllWorkLocations() {
        List<WorkLocation> result
            = new ArrayList<WorkLocation>(buildingMap.values());
        result.addAll(colonyTiles);
        return result;
    }

    /**
     * Gets a list of all freely available work locations
     * in this colony.
     *
     * @return The list of available <code>WorkLocation</code>s.
     */
    public List<WorkLocation> getAvailableWorkLocations() {
        List<WorkLocation> result
            = new ArrayList<WorkLocation>(buildingMap.values());
        for (ColonyTile ct : colonyTiles) {
            Tile tile = ct.getWorkTile();
            if (tile.getOwningSettlement() == this
                || getOwner().canClaimForSettlement(tile)) {
                result.add(ct);
            }
        }
        return result;
    }

    /**
     * Gets a list of all current work locations in this colony.
     *
     * @return The list of current <code>WorkLocation</code>s.
     */
    public List<WorkLocation> getCurrentWorkLocations() {
        List<WorkLocation> result
            = new ArrayList<WorkLocation>(buildingMap.values());
        for (ColonyTile ct : colonyTiles) {
            Tile tile = ct.getWorkTile();
            if (tile.getOwningSettlement() == this) result.add(ct);
        }
        return result;
    }

    /**
     * Gets a <code>List</code> of every {@link Building} in this
     * <code>Colony</code>.
     *
     * @return The <code>List</code>.
     * @see Building
     */
    public List<Building> getBuildings() {
        return new ArrayList<Building>(buildingMap.values());
    }

    /**
     * Gets a <code>List</code> of every {@link ColonyTile} in this
     * <code>Colony</code>.
     *
     * @return The <code>List</code>.
     * @see ColonyTile
     */
    public List<ColonyTile> getColonyTiles() {
        return colonyTiles;
    }

    /**
     * Returns a weighted list of natural disasters than can strike
     * this colony. This list comprises all natural disasters that can
     * strike the colony's tiles.
     *
     * @return a <code>List<RandomChoice<Disaster>></code> value
     */
    public List<RandomChoice<Disaster>> getDisasters() {
        List<RandomChoice<Disaster>> disasters
            = new ArrayList<RandomChoice<Disaster>>();
        for (ColonyTile tile : colonyTiles) {
            disasters.addAll(tile.getWorkTile().getDisasters());
        }
        return disasters;
    }

    /**
     * Is a tile actually in use by this colony?
     *
     * @param tile The <code>Tile</code> to test.
     * @return True if this tile is actively in use by this colony.
     */
    public boolean isTileInUse(Tile tile) {
        ColonyTile colonyTile = getColonyTile(tile);
        return colonyTile != null && !colonyTile.isEmpty();
    }

    /**
     * Gets a <code>Building</code> of the specified type.
     *
     * @param type The type of the building to get.
     * @return The <code>Building</code>.
     */
    public Building getBuilding(BuildingType type) {
        return buildingMap.get(type.getFirstLevel().getId());
    }


    /**
     * Returns a <code>Building</code> with the given
     * <code>Ability</code>, or null, if none exists.
     *
     * @param ability a <code>String</code> value
     * @return a <code>Building</code> value
     */
    public Building getBuildingWithAbility(String ability) {
        for (Building building : buildingMap.values()) {
            if (building.getType().hasAbility(ability)) {
                return building;
            }
        }
        return null;
    }

    /**
     * Returns the <code>ColonyTile</code> matching the given
     * <code>Tile</code>.
     *
     * @param t The <code>Tile</code> to get the <code>ColonyTile</code>
     *            for.
     * @return The <code>ColonyTile</code>
     */
    public ColonyTile getColonyTile(Tile t) {
        for (ColonyTile c : colonyTiles) {
            if (c.getWorkTile() == t) {
                return c;
            }
        }
        return null;
    }

    /**
     * Increment liberty points by amount given.
     *
     * @param amount an <code>int</code> value
     */
    public void incrementLiberty(int amount) {
        liberty += amount;
    }

    /**
     * Increment immigration points by amount given.
     *
     * @param amount an <code>int</code> value
     */
    public void incrementImmigration(int amount) {
        immigration += amount;
    }

    /**
     * Get the <code>Established</code> value.
     *
     * @return a <code>Turn</code> value
     */
    public Turn getEstablished() {
        return established;
    }

    /**
     * Set the <code>Established</code> value.
     *
     * @param newEstablished The new Established value.
     */
    public void setEstablished(final Turn newEstablished) {
        this.established = newEstablished;
    }

    /**
     * Gets a work location within this colony to put a unit in.
     *
     * @param unit The <code>Unit</code> to place.
     * @return A work location for the unit, or null if none available.
     */
    public WorkLocation getWorkLocationFor(Unit unit) {
        Occupation occupation = getOccupationFor(unit);
        if (occupation == null) {
            logger.warning("Could not find a WorkLocation for: "
                + unit.toString() + " in: " + getName());
            return null;
        }
        if (occupation.workType != null) {
            unit.setWorkType(occupation.workType);
        }
        return occupation.workLocation;
    }

    /**
     * Adds a <code>Unit</code> to an optional
     * <code>WorkLocation</code> in this Colony.
     *
     * @param unit The <code>Unit</code> to add.
     * @param loc The <code>WorkLocation</code> to add to (if null,
     *     one is chosen.
     * @return True if the add succeeded.
     */
    public boolean addUnit(Unit unit, WorkLocation loc) {
        if (!unit.isPerson()) return false;
        if (loc == null) {
            loc = getWorkLocationFor(unit);
            if (loc == null) return false;
        }
        if (!loc.add(unit)) return false;
        Player owner = unit.getOwner();
        owner.modifyScore(unit.getType().getScoreValue());
        updatePopulation(1);
        unit.setState(Unit.UnitState.IN_COLONY);
        if (owner.isAI()) {
            firePropertyChange(REARRANGE_WORKERS, true, false);
        }
        return true;
    }

    /**
     * Removes a <code>Unit</code> from this Colony.
     *
     * @param unit The <code>Unit</code> to remove.
     * @return True if the remove succeeded.
     */
    public boolean removeUnit(Unit unit) {
        Player owner = unit.getOwner();
        for (WorkLocation w : getCurrentWorkLocations()) {
            if (w.contains(unit) && w.remove(unit)) {
                Unit teacher = unit.getTeacher();
                if (teacher != null) {
                    teacher.setStudent(null);
                    unit.setTeacher(null);
                }
                owner.modifyScore(-unit.getType().getScoreValue());
                updatePopulation(-1);
                unit.setState(Unit.UnitState.ACTIVE);
                if (owner.isAI()) {
                    firePropertyChange(REARRANGE_WORKERS, true, false);
                }
                return true;
            }
        }
        return false;
    }

    /**
     * Add goods to this colony.
     *
     * @param type a <code>GoodsType</code> value
     * @param amount an <code>int</code> value
     */
    @Override
    public boolean addGoods(GoodsType type, int amount) {
        super.addGoods(type, amount);
        productionCache.invalidate(type);
        modifySpecialGoods(type, amount);
        return true;
    }

    /**
     * Removes a specified amount of a type of Goods from this Settlement.
     *
     * @param type The type of Goods to remove from this settlement.
     * @param amount The amount of Goods to remove from this settlement.
     * @return The goods removed, or null if none.
     */
    @Override
    public Goods removeGoods(GoodsType type, int amount) {
        Goods removed = super.removeGoods(type, amount);
        productionCache.invalidate(type);
        if (removed != null) modifySpecialGoods(type, -removed.getAmount());
        return removed;
    }

    protected void modifySpecialGoods(GoodsType goodsType, int amount) {
        Set<Modifier> libertyModifiers
            = goodsType.getModifierSet("model.modifier.liberty");
        if (!libertyModifiers.isEmpty()) {
            int newLiberty = (int)FeatureContainer
                .applyModifierSet(amount, getGame().getTurn(),
                                  libertyModifiers);
            incrementLiberty(newLiberty);
            getOwner().incrementLiberty(newLiberty);
        }

        Set<Modifier> immigrationModifiers
            = goodsType.getModifierSet("model.modifier.immigration");
        if (!immigrationModifiers.isEmpty()) {
            int newImmigration = (int)FeatureContainer
                .applyModifierSet(amount, getGame().getTurn(),
                                  immigrationModifiers);
            incrementImmigration(newImmigration);
            getOwner().incrementImmigration(newImmigration);
        }

    }

    /**
     * Gets the total number of units in the work locations.
     *
     * @return The number of <code>Unit</code>s in the work locations.
     */
    public int getWorkLocationUnitCount() {
        int count = 0;
        for (WorkLocation w : getCurrentWorkLocations()) {
            count += w.getUnitCount();
        }
        return count;
    }

    /**
     * Gets the apparent number of units at this colony.
     * Used in client enemy colonies
     *
     * @return The apparent number of <code>Unit</code>s at this colony.
     */
    public int getDisplayUnitCount() {
        return (displayUnitCount > 0) ? displayUnitCount : getUnitCount();
    }

    /**
     * Sets the apparent number of units inside the colony.
     * Used in client enemy colonies
     *
     * @param displayUnitCount The apparent number of <code>Unit</code>s
     *     inside the colony.
     */
    public void setDisplayUnitCount(int displayUnitCount) {
        this.displayUnitCount = displayUnitCount;
    }

    /**
     * Returns true if this colony has a schoolhouse and the unit type is a
     * skilled unit type with a skill level not exceeding the level of the
     * schoolhouse. @see Building#canAdd
     *
     * @param unit The unit to add as a teacher.
     * @return <code>true</code> if this unit type could be added.
     */
    public boolean canTrain(Unit unit) {
        return canTrain(unit.getType());
    }

    /**
     * Returns true if this colony has a schoolhouse and the unit type is a
     * skilled unit type with a skill level not exceeding the level of the
     * schoolhouse. The number of units already in the schoolhouse and
     * the availability of pupils are not taken into account. @see
     * Building#canAdd
     *
     * @param unitType The unit type to add as a teacher.
     * @return <code>true</code> if this unit type could be added.
     */
    public boolean canTrain(UnitType unitType) {
        if (!hasAbility(Ability.CAN_TEACH)) {
            return false;
        }

        for (Building building : buildingMap.values()) {
            if (building.canTeach() && building.canAddType(unitType)) {
                return true;
            }
        }
        return false;
    }
    /**
     * Returns a list of all teachers currently present in the school
     * building.
     */
    public List<Unit> getTeachers() {
        List<Unit> teachers = new ArrayList<Unit>();
        for (Building building : buildingMap.values()) {
            if (building.canTeach()) {
                teachers.addAll(building.getUnitList());
            }
        }
        return teachers;
    }

    /**
     * Find a teacher for the specified student.
     * Do not search if ALLOW_STUDENT_SELECTION is true--- its the player's
     * job then.
     *
     * @param student The student <code>Unit</code> that needs a teacher.
     * @return A potential teacher, or null of none found.
     */
    public Unit findTeacher(Unit student) {
        if (getSpecification().getBoolean(GameOptions.ALLOW_STUDENT_SELECTION))
            return null; // No automatic assignment
        for (Building building : getBuildings()) {
            if (building.canTeach()) {
                for (Unit unit : building.getUnitList()) {
                    if (unit.getStudent() == null
                        && student.canBeStudent(unit)) return unit;
                }
            }
        }
        return null;
    }

    /**
     * Find a student for the specified teacher.
     * Do not search if ALLOW_STUDENT_SELECTION is true--- its the player's
     * job then.
     *
     * @param teacher The teacher <code>Unit</code> that needs a student.
     * @return A potential student, or null of none found.
     */
    public Unit findStudent(final Unit teacher) {
        if (getSpecification().getBoolean(GameOptions.ALLOW_STUDENT_SELECTION))
            return null; // No automatic assignment
        Unit student = null;
        GoodsType expertProduction = teacher.getType().getExpertProduction();
        int skillLevel = INFINITY;
        for (Unit potentialStudent : getUnitList()) {
            /**
             * Always pick the student with the least skill first.
             * Break ties by favouring the one working in the teacher's trade,
             * otherwise first applicant wins.
             */
            if (potentialStudent.getTeacher() == null
                && potentialStudent.canBeStudent(teacher)
                && (student == null
                    || potentialStudent.getSkillLevel() < skillLevel
                    || (potentialStudent.getSkillLevel() == skillLevel
                        && potentialStudent.getWorkType() == expertProduction))) {
                student = potentialStudent;
                skillLevel = student.getSkillLevel();
            }
        }
        return student;
    }


    /**
     * Gets the best defender type available to this colony.
     *
     * @return The best available defender type.
     */
    public UnitType getBestDefenderType() {
        UnitType bestDefender = null;
        for (UnitType unitType : getSpecification().getUnitTypeList()) {
            if (unitType.getDefence() > 0
                && (bestDefender == null
                    || bestDefender.getDefence() < unitType.getDefence())
                && !unitType.hasAbility(Ability.NAVAL_UNIT)
                && unitType.isAvailableTo(getOwner())) {
                bestDefender = unitType;
            }
        }
        return bestDefender;
    }

    /**
     * Gets the total defence power.
     *
     * @return The total defence power.
     */
    public float getTotalDefencePower() {
        CombatModel cm = getGame().getCombatModel();
        float defence = 0.0f;
        for (Unit unit : getTile().getUnitList()) {
            if (unit.isDefensiveUnit()) {
                defence += cm.getDefencePower(null, unit);
            }
        }
        return defence;
    }


    /**
     * Determines whether this colony is sufficiently unprotected and
     * contains something worth pillaging.  To be called by CombatModels
     * when the attacker has defeated an unarmed colony defender.
     *
     * @param attacker The <code>Unit</code> that has defeated the defender.
     * @return True if the attacker can pillage this colony.
     */
    public boolean canBePillaged(Unit attacker) {
        return !hasStockade()
            && attacker.hasAbility("model.ability.pillageUnprotectedColony")
            && !(getBurnableBuildingList().isEmpty()
                 && getShipList().isEmpty()
                 && (getLootableGoodsList().isEmpty()
                     || !attacker.getType().canCarryGoods()
                     || !attacker.hasSpaceLeft())
                 && !canBePlundered());
    }

    /**
     * Checks if this colony can be plundered.  That is, can it yield
     * non-zero gold.
     *
     * @return True if at least one piece of gold can be plundered from this
     *     colony.
     */
    public boolean canBePlundered() {
        return owner.checkGold(1);
    }


    /**
     * Returns <code>true</code> if the number of enemy combat units
     * on all tiles that belong to the colony exceeds the number of
     * friendly combat units. At the moment, only the colony owner's
     * own units are considered friendly, but that could be extended
     * to include the units of allied players.
     *
     * TODO: if a colony is under siege, it should not be possible to
     * put units outside the colony, unless those units are armed.
     *
     * @return a <code>boolean</code> value
     */
    public boolean isUnderSiege() {
        int friendlyUnits = 0;
        int enemyUnits = 0;
        for (ColonyTile colonyTile : colonyTiles) {
            for (Unit unit : colonyTile.getWorkTile().getUnitList()) {
                if (unit.getOwner() == getOwner()) {
                    if (unit.isDefensiveUnit()) {
                        friendlyUnits++;
                    }
                } else if (getOwner().atWarWith(unit.getOwner())) {
                    if (unit.isOffensiveUnit()) {
                        enemyUnits++;
                    }
                }
            }
        }
        return enemyUnits > friendlyUnits;
    }

    /**
     * Gets the buildings in this colony that could be burned by a raid.
     *
     * @return A list of burnable buildings.
     */
    public List<Building> getBurnableBuildingList() {
        List<Building> buildingList = new ArrayList<Building>();
        for (Building building : getBuildings()) {
            if (building.canBeDamaged()) buildingList.add(building);
        }
        return buildingList;
    }

    /**
     * Gets a list of all ships in this colony (although they are really
     * located on the colony tile).
     *
     * @return A list of ships in this colony.
     */
    public List<Unit> getShipList() {
        List<Unit> shipList = new ArrayList<Unit>();
        for (Unit u : getTile().getUnitList()) {
            if (u.isNaval()) shipList.add(u);
        }
        return shipList;
    }

    /**
     * Gets a list of all stored goods in this colony, suitable for
     * being looted.
     *
     * @return A list of lootable goods in this colony.
     */
    public List<Goods> getLootableGoodsList() {
        List<Goods> goodsList = new ArrayList<Goods>();
        for (Goods goods : getGoodsContainer().getGoods()) {
            if (goods.getType().isStorable()) goodsList.add(goods);
        }
        return goodsList;
    }

    /**
     * Returns a <code>List</code> with every unit type this colony may
     * build.
     *
     * @return A <code>List</code> with <code>UnitType</code>
     */
    public List<UnitType> getBuildableUnits() {
        ArrayList<UnitType> buildableUnits = new ArrayList<UnitType>();
        List<UnitType> unitTypes = getSpecification().getUnitTypeList();
        for (UnitType unitType : unitTypes) {
            if (unitType.needsGoodsToBuild() && canBuild(unitType)) {
                buildableUnits.add(unitType);
            }
        }
        return buildableUnits;
    }

    /**
     * Returns the type of building currently being built.
     *
     * @return The type of building currently being built.
     */
    public BuildableType getCurrentlyBuilding() {
        return buildQueue.getCurrentlyBuilding();
    }

    /**
     * Sets the current type of buildable to be built and if it is a building
     * insist that there is only one in the queue.
     *
     * @param buildable The <code>BuildableType</code> to build.
     */
    public void setCurrentlyBuilding(BuildableType buildable) {
        buildQueue.setCurrentlyBuilding(buildable);
    }

    /**
     * Returns how many turns it would take to build the given
     * <code>BuildableType</code>.
     *
     * @param buildable The <code>BuildableType</code> to build.
     * @return The number of turns to build the buildable, negative if
     *     some goods are not being built, UNDEFINED if none is.
     */
    public int getTurnsToComplete(BuildableType buildable) {
        return getTurnsToComplete(buildable, null);
    }

    /**
     * Returns how many turns it would take to build the given
     * <code>BuildableType</code>.
     *
     * @param buildable The <code>BuildableType</code> to build.
     * @param needed The <code>AbstractGoods</code> needed to continue
     *     the build.
     * @return The number of turns to build the buildable, negative if
     *     some goods are not being built, UNDEFINED if none is.
     */
    public int getTurnsToComplete(BuildableType buildable,
                                  AbstractGoods needed) {
        int result = 0;
        boolean goodsMissing = false;
        boolean goodsBeingProduced = false;
        boolean productionMissing = false;

        ProductionInfo info = productionCache.getProductionInfo(buildQueue);
        for (AbstractGoods ag : buildable.getRequiredGoods()) {
            int amountNeeded = ag.getAmount();
            int amountAvailable = getGoodsCount(ag.getType());
            if (amountAvailable >= amountNeeded) continue;
            goodsMissing = true;
            int amountProduced = productionCache.getNetProductionOf(ag.getType());
            if (info != null) {
                for (AbstractGoods consumed : info.getConsumption()) {
                    if (consumed.getType() == ag.getType()) {
                        // add the amount the build queue itself will consume
                        amountProduced += consumed.getAmount();
                        break;
                    }
                }
            }
            if (amountProduced <= 0) {
                productionMissing = true;
                if (needed != null) {
                    needed.setType(ag.getType());
                    needed.setAmount(ag.getAmount());
                }
                continue;
            }
            goodsBeingProduced = true;

            int amountRemaining = amountNeeded - amountAvailable;
            int eta = amountRemaining / amountProduced;
            if (amountRemaining % amountProduced != 0) {
                eta++;
            }
            result = Math.max(result, eta);
        }
        return (!goodsMissing) ? 0
            : (!goodsBeingProduced) ? UNDEFINED
            : (productionMissing) ? -result
            : result;
    }

    /**
     * Get the <code>BuildQueue</code> value.
     *
     * @return a <code>List<Buildable></code> value
     */
    public List<BuildableType> getBuildQueue() {
        return buildQueue.getValues();
    }

    /**
     * Set the <code>BuildQueue</code> value.
     *
     * @param newBuildQueue The new BuildQueue value.
     */
    public void setBuildQueue(final List<BuildableType> newBuildQueue) {
        buildQueue.setValues(newBuildQueue);
    }

    /**
     * Describe <code>getLiberty</code> method here.
     *
     * @return an <code>int</code> value
     */
    public int getLiberty() {
        return liberty;
    }

    /**
     * Adds to the liberty points of the colony. Used only by DebugMenu.
     *
     * @param amount The number of liberty to add.
     */
    public void addLiberty(int amount) {
        getOwner().incrementLiberty(amount);
        List<GoodsType> libertyTypeList = getSpecification()
            .getLibertyGoodsTypeList();
        final int uc = getUnitCount();
        if (calculateRebels(uc, sonsOfLiberty) <= uc + 1
            && amount > 0
            && !libertyTypeList.isEmpty()) {
            addGoods(libertyTypeList.get(0), amount);
        }
        updateSoL();
    }

    /**
     * Return the number of immigration points.
     *
     * @return an <code>int</code> value
     */
    public int getImmigration() {
        return immigration;
    }

    /**
     * Returns the number of goods of a given type used by the settlement
     * each turn.
     *
     * @param goodsType <code>GoodsType</code> values
     * @return an <code>int</code> value
     */
    public int getConsumptionOf(GoodsType goodsType) {
        final Specification spec = getSpecification();
        int result = super.getConsumptionOf(goodsType);
        if (spec.getGoodsType("model.goods.bells").equals(goodsType)) {
            result -= spec.getInteger("model.option.unitsThatUseNoBells");
        }
        return Math.max(0, result);
    }

    /**
     * Calculates the current SoL membership of the colony based on
     * the liberty value and colonists.
     */
    public void updateSoL() {
        int uc = getUnitCount();
        oldSonsOfLiberty = sonsOfLiberty;
        oldTories = tories;
        sonsOfLiberty = calculateSoL(uc, liberty);
        tories = uc - calculateRebels(uc, sonsOfLiberty);
    }

    /**
     * Calculate the SoL membership percentage of the colony based on the
     * number of colonists and liberty.
     *
     * @param uc The proposed number of units in the colony.
     * @param liberty The amount of liberty.
     * @return The percentage of SoLs.
     */
    private static int calculateSoL(int uc, int liberty) {
        if (uc <= 0) return 0;

        int membership = (liberty * 100) / (LIBERTY_PER_REBEL * uc);
        if (membership < 0) {
            membership = 0;
        } else if (membership > 100) {
            membership = 100;
        }
        return membership;
    }

    /**
     * Calculate the number of rebels given a SoL percentage and unit count.
     *
     * @param uc The number of units in the colony.
     * @param solPercent The percentage of SoLs.
     */
    public static int calculateRebels(int uc, int solPercent) {
        return (int)Math.floor(0.01 * solPercent * uc);
    }

    /**
     * Gets the Tory membership percentage of the colony.
     *
     * @return The current Tory membership of the colony.
     */
    public int getTory() {
        return 100 - getSoL();
    }

    /**
     * Returns the production bonus, if any, of the colony.
     *
     * @return The current production bonus of the colony.
     */
    public int getProductionBonus() {
        return productionBonus;
    }

    /**
     * Gets a string representation of the Colony. Currently this method just
     * returns the name of the <code>Colony</code>, but that may change
     * later.
     *
     * @return The name of the colony.
     * @see #getName
     */
    @Override
    public String toString() {
        return getName();
    }

    /**
     * Gets the combined production of all food types.
     *
     * @return an <code>int</code> value
     */
    public int getFoodProduction() {
        int result = 0;
        for (GoodsType foodType : getSpecification().getFoodGoodsTypeList()) {
            result += getTotalProductionOf(foodType);
        }
        return result;
    }

    /**
     * Gets a vacant <code>WorkLocation</code> for the given <code>Unit</code>.
     *
     * @param unit The <code>Unit</code>
     * @return A vacant <code>WorkLocation</code> for the given
     *         <code>Unit</code> or <code>null</code> if there is no such
     *         location.
     */
    public WorkLocation getVacantWorkLocationFor(Unit unit) {
        Occupation occupation = getOccupationFor(unit);
        if (occupation == null) {
            return null;
        } else {
            return occupation.workLocation;
        }
    }

    /**
     * Returns an <code>Occupation</code> for the given <code>Unit</code>.
     *
     * @param unit The <code>Unit</code>
     * @return An <code>Occupation</code> for the given
     *         <code>Unit</code> or <code>null</code> if there is none.
     */
    private Occupation getOccupationFor(Unit unit) {
        for (AbstractGoods consumption : unit.getType().getConsumedGoods()) {
            // TODO: this should consider a list of all consumed
            // goods, weighted by priority. This, in turn, would
            // require the consumption element to state the
            // consequences if consumption can not be satisfied.
            if (consumption.getType().isFoodType()
                && productionCache.getNetProductionOf(consumption.getType()) < consumption.getAmount()) {
                // try to satisfied increased demands
                List<GoodsType> rawTypes = new ArrayList<GoodsType>();
                for (GoodsType type : getSpecification().getGoodsTypeList()) {
                    if (type.getStoredAs() == consumption.getType()) {
                        rawTypes.add(type);
                    }
                }
                rawTypes.add(consumption.getType());
                ColonyTile bestTile = null;
                GoodsType bestWork = null;
                int bestAmount = 0;
                for (ColonyTile tile : colonyTiles) {
                    switch (tile.getNoAddReason(unit)) {
                    case NONE: case ALREADY_PRESENT:
                        for (GoodsType type : rawTypes) {
                            int amount = tile.getPotentialProduction(type,
                                unit.getType());
                            if (amount > bestAmount) {
                                bestAmount = amount;
                                bestWork = type;
                                bestTile = tile;
                            }
                        }
                        break;
                    default:
                        break;
                    }
                }
                if (bestAmount > 0) {
                    return new Occupation(bestTile, bestWork);
                } else {
                    for (GoodsType type : rawTypes) {
                        for (Building building : getBuildingsForProducing(type)) {
                            switch (building.getNoAddReason(unit)) {
                            case NONE: case ALREADY_PRESENT:
                                return new Occupation(building, type);
                            default:
                                break;
                            }
                        }
                    }
                }
            }
        }

        GoodsType expertProduction = unit.getType().getExpertProduction();
        if (expertProduction == null) {
            if (unit.getExperience() > 0) {
                expertProduction = unit.getWorkType();
                if (expertProduction != null && expertProduction.isFarmed()) {
                    ColonyTile colonyTile = getVacantColonyTileFor(unit, false, expertProduction);
                    if (colonyTile != null) {
                        return new Occupation(colonyTile, expertProduction);
                    }
                }
            }
        } else if (expertProduction.isFarmed()) {
            ColonyTile colonyTile = getVacantColonyTileFor(unit, false, expertProduction);
            if (colonyTile != null) {
                return new Occupation(colonyTile, expertProduction);
            }
        } else {
            Building building = getBuildingFor(unit);
            if (building != null) {
                return new Occupation(building, building.getGoodsOutputType());
            }
        }

        ColonyTile bestTile = null;
        GoodsType bestType = null;
        int bestProduction = 0;
        for (GoodsType foodType : getSpecification().getFoodGoodsTypeList()) {
            ColonyTile colonyTile = getVacantColonyTileFor(unit, false, foodType);
            if (colonyTile != null) {
                int production = colonyTile.getPotentialProduction(foodType,
                    unit.getType());
                if (production > bestProduction) {
                    bestProduction = production;
                    bestTile = colonyTile;
                    bestType = foodType;
                }
            }
        }
        if (bestTile != null) {
            return new Occupation(bestTile, bestType);
        }
        Building building = getBuildingFor(unit);
        if (building != null) {
            return new Occupation(building, building.getGoodsOutputType());
        }
        return null;
    }

    /**
     * Gets the Building best suited for the given Unit.
     *
     * @param unit The <code>Unit</code> to get the building for.
     * @return The best <code>Building</code>.
     */
    public Building getBuildingFor(Unit unit) {
        GoodsType expertProduction = unit.getType().getExpertProduction();
        Building best;
        if (expertProduction != null && !expertProduction.isFarmed()
            && (best = getBuildingFor(unit, expertProduction)) != null) {
            return best;
        }
        List<Building> buildings = new ArrayList<Building>(getBuildings());
        for (Building building : buildings) {
            switch (building.getNoAddReason(unit)) {
            case NONE: case ALREADY_PRESENT:
                if (building.getGoodsInputType() == null
                    || getGoodsCount(building.getGoodsInputType()) > 0) {
                    return building;
                }
                break;
            default:
                break;
            }
        }
        return null;
    }

    /**
     * Gets the Building best suited for the given Unit to produce an
     * optional type of goods.
     *
     * @param unit The <code>Unit</code> to get the building for.
     * @param goodsType The optional <code>GoodsType</code> to produce.
     * @return The best <code>Building</code>.
     */
    public Building getBuildingFor(Unit unit, GoodsType goodsType) {
        List<Building> buildings
            = new ArrayList<Building>(getBuildingsForProducing(goodsType));
        Building best = null;
        int bestProd = 0;
        for (Building building : buildings) {
            switch (building.getNoAddReason(unit)) {
            case NONE: case ALREADY_PRESENT:
                int prod = building.getPotentialProduction(goodsType,
                                                           unit.getType());
                if (prod > bestProd) {
                    bestProd = prod;
                    best = building;
                }
                break;
            default:
                break;
            }
        }
        return best;
    }

    /**
     * Returns a vacant <code>ColonyTile</code> where the given
     * <code>unit</code> produces the maximum output of the given
     * <code>goodsType</code>.
     *
     * @param unit The <code>Unit</code> to find a vacant
     *            <code>ColonyTile</code> for.
     * @param allowClaim Allow claiming free tiles from other settlements.
     * @param goodsTypes The types of goods that should be produced.
     * @return The <code>ColonyTile</code> giving the highest production of
     *         the given goods for the given unit or <code>null</code> if
     *         there is no available <code>ColonyTile</code> for producing
     *         that goods.
     */
    public ColonyTile getVacantColonyTileFor(Unit unit, boolean allowClaim,
                                             GoodsType... goodsTypes) {
        ColonyTile best = null;
        int bestProd = 0;
        for (ColonyTile colonyTile : colonyTiles) {
            Tile workTile = colonyTile.getWorkTile();
            switch (colonyTile.getNoAddReason(unit)) {
            case CLAIM_REQUIRED:
                if (owner.getLandPrice(workTile) < 0) allowClaim = false;
                // Fall through.
            case NONE: case ALREADY_PRESENT:
                if (workTile.getOwningSettlement() != this
                    && !allowClaim) break;
                for (GoodsType goodsType : goodsTypes) {
                    int prod = colonyTile.getPotentialProduction(goodsType,
                        unit.getType());
                    if (prod > bestProd) {
                        bestProd = prod;
                        best = colonyTile;
                    }
                }
                break;
            default:
                break;
            }
        }
        return best;
    }

    /**
     * Returns <code>true</code> if this Colony can breed the given
     * type of Goods. Only animals (such as horses) are expected to be
     * breedable.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return a <code>boolean</code> value
     */
    public boolean canBreed(GoodsType goodsType) {
        int breedingNumber = goodsType.getBreedingNumber();
        return (breedingNumber < GoodsType.INFINITY &&
                breedingNumber <= getGoodsCount(goodsType));
    }


    /**
     * Describe <code>canBuild</code> method here.
     *
     * @return a <code>boolean</code> value
     */
    public boolean canBuild() {
        return canBuild(getCurrentlyBuilding());
    }

    /**
     * Returns true if this Colony can build the given BuildableType.
     *
     * @param buildableType a <code>BuildableType</code> value
     * @return a <code>boolean</code> value
     */
    public boolean canBuild(BuildableType buildableType) {
        return (getNoBuildReason(buildableType) == NoBuildReason.NONE);
    }

    /**
     * Return the reason why the give <code>BuildableType</code> can
     * not be built.
     *
     * @param buildableType a <code>BuildableType</code> value
     * @return a <code>NoBuildReason</code> value
     */
    public NoBuildReason getNoBuildReason(BuildableType buildableType) {
        if (buildableType == null) {
            return NoBuildReason.NOT_BUILDING;
        } else if (!buildableType.needsGoodsToBuild()) {
            return NoBuildReason.NOT_BUILDABLE;
        } else if (buildableType.getRequiredPopulation() > getUnitCount()) {
            return NoBuildReason.POPULATION_TOO_SMALL;
        } else {
            for (Entry<String, Boolean> entry
                     : buildableType.getRequiredAbilities().entrySet()) {
                if (hasAbility(entry.getKey()) != entry.getValue()) {
                    return NoBuildReason.MISSING_ABILITY;
                }
            }
            if (buildableType.getLimits() != null) {
                for (Limit limit : buildableType.getLimits()) {
                    if (!limit.evaluate(this)) {
                        return NoBuildReason.LIMIT_EXCEEDED;
                    }
                }
            }
        }
        if (buildableType instanceof BuildingType) {
            BuildingType newBuildingType = (BuildingType) buildableType;
            Building colonyBuilding = this.getBuilding(newBuildingType);
            if (colonyBuilding == null) {
                // the colony has no similar building yet
                if (newBuildingType.getUpgradesFrom() != null) {
                    // we are trying to build an advanced factory, we
                    // should build lower level shop first
                    return NoBuildReason.WRONG_UPGRADE;
                }
            } else {
                // a building of the same family already exists
                if (colonyBuilding.getType().getUpgradesTo()
                    != newBuildingType) {
                    // the existing building's next upgrade is not the
                    // new one we want to build
                    return NoBuildReason.WRONG_UPGRADE;
                }
            }
        } else if (buildableType instanceof UnitType) {
            if (!buildableType.hasAbility("model.ability.person")
                && !hasAbility(Ability.BUILD, buildableType)) {
                return NoBuildReason.MISSING_BUILD_ABILITY;
            }
        }
        return NoBuildReason.NONE;
    }

    /**
     * Returns the price for the remaining hammers and tools for the
     * {@link Building} that is currently being built.
     *
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int getPriceForBuilding() {
        return getPriceForBuilding(getCurrentlyBuilding());
    }

    /**
     * Gets the price for the remaining resources to build a given buildable.
     *
     * @param type The <code>BuildableType</code> to build.
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int getPriceForBuilding(BuildableType type) {
        return priceGoodsForBuilding(getGoodsForBuilding(type));
    }

    /**
     * Gets a price for a map of resources to build a given buildable.
     *
     * @param required The map of resources required.
     * @return The price.
     * @see net.sf.freecol.client.control.InGameController#payForBuilding
     */
    public int priceGoodsForBuilding(HashMap<GoodsType, Integer> required) {
        int price = 0;
        Market market = getOwner().getMarket();
        for (GoodsType goodsType : required.keySet()) {
            int amount = required.get(goodsType);
            if (goodsType.isStorable()) {
                // TODO: magic number!
                price += (market.getBidPrice(goodsType, amount) * 110) / 100;
            } else {
                price += goodsType.getPrice() * amount;
            }
        }
        return price;
    }

    /**
     * Gets a map of the types of goods and amount thereof required to
     * finish a buildable in this colony.
     *
     * @param type The <code>BuildableType</code> to build.
     * @return The map to completion.
     */
    public HashMap<GoodsType, Integer> getGoodsForBuilding(BuildableType type) {
        HashMap<GoodsType, Integer> result = new HashMap<GoodsType, Integer>();
        for (AbstractGoods goods : type.getRequiredGoods()) {
            GoodsType goodsType = goods.getType();
            int remaining = goods.getAmount() - getGoodsCount(goodsType);
            if (remaining > 0) {
                result.put(goodsType, new Integer(remaining));
            }
        }
        return result;
    }

    /**
     * Check if the owner can buy the remaining hammers and tools for
     * the {@link Building} that is currently being built.
     *
     * @exception IllegalStateException If the owner of this <code>Colony</code>
     *                has an insufficient amount of gold.
     * @see #getPriceForBuilding
     */
    public boolean canPayToFinishBuilding() {
        return canPayToFinishBuilding(getCurrentlyBuilding());
    }

    /**
     * Check if the owner can buy the remaining hammers and tools for
     * the {@link Building} given.
     *
     * @param buildableType a <code>BuildableType</code> value
     * @return a <code>boolean</code> value
     * @exception IllegalStateException If the owner of this <code>Colony</code>
     *                has an insufficient amount of gold.
     * @see #getPriceForBuilding
     */
    public boolean canPayToFinishBuilding(BuildableType buildableType) {
        return buildableType != null
            && getOwner().checkGold(getPriceForBuilding(buildableType));
    }

    /**
     * determine if there is a problem with the production of the specified good
     *
     * @param goodsType  for this good
     * @param amount     warehouse amount
     * @param production production per turn
     * @return all warnings
     */
    public Collection<StringTemplate> getWarnings(GoodsType goodsType, int amount, int production) {

        List<StringTemplate> result = new LinkedList<StringTemplate>();

        if (goodsType.isFoodType() && goodsType.isStorable()) {
            if (amount + production < 0) {
                result.add(StringTemplate.template("model.colony.famineFeared")
                           .addName("%colony%", getName())
                           .addAmount("%number%", 0));
            }
        } else {
            //food is never wasted -> new settler is produced
            int waste = (amount + production - getWarehouseCapacity());
            if (waste > 0 && !getExportData(goodsType).isExported() && !goodsType.limitIgnored()) {
                result.add(StringTemplate.template("model.building.warehouseSoonFull")
                           .add("%goods%", goodsType.getNameKey())
                           .addName("%colony%", getName())
                           .addAmount("%amount%", waste));

            }
        }

        BuildableType currentlyBuilding = getCurrentlyBuilding();
        if (currentlyBuilding != null) {
            for (AbstractGoods goods : currentlyBuilding.getRequiredGoods()) {
                if (goods.getType().equals(goodsType) && amount < goods.getAmount()) {
                    result.add(StringTemplate.template("model.colony.buildableNeedsGoods")
                               .addName("%colony%", getName())
                               .add("%buildable%", currentlyBuilding.getNameKey())
                               .addAmount("%amount%", (goods.getAmount() - amount))
                               .add("%goodsType%", goodsType.getNameKey()));
                }
            }
        }

        for (Building b : getBuildingsForProducing(goodsType)) {
            addInsufficientProductionMessage(result,
                productionCache.getProductionInfo(b));
        }
        Building buildingForConsuming = getBuildingForConsuming(goodsType);
        if (buildingForConsuming != null
            && !buildingForConsuming.getGoodsOutputType().isStorable()) {
            //the warnings are for a non-storable good, which is not displayed in the trade report
            addInsufficientProductionMessage(result, productionCache.getProductionInfo(buildingForConsuming));
        }

        return result;
    }

    /**
     * adds a message about insufficient production for a building
     *
     * @param warnings where to add the warnings
     * @param info the <code>ProductionInfo</code> for this building
     */
    private void addInsufficientProductionMessage(List<StringTemplate> warnings, ProductionInfo info) {
        if (info != null && !info.getMaximumProduction().isEmpty()) {
            int missingOutput = info.getMaximumProduction().get(0).getAmount()
                - info.getProduction().get(0).getAmount();
            if (missingOutput > 0) {
                GoodsType outputType = info.getProduction().get(0).getType();
                GoodsType inputType = info.getConsumption().isEmpty()
                    ? null : info.getConsumption().get(0).getType();
                int missingInput = info.getMaximumConsumption().get(0).getAmount()
                    - info.getConsumption().get(0).getAmount();
                warnings.add(StringTemplate.template("model.colony.insufficientProduction")
                             .addAmount("%outputAmount%", missingOutput)
                             .add("%outputType%", outputType.getNameKey())
                             .addName("%colony%", getName())
                             .addAmount("%inputAmount%", missingInput)
                             .add("%inputType%", inputType.getNameKey()));
            }
        }
    }

    /**
     * Returns 1, 0, or -1 to indicate that government would improve,
     * remain the same, or deteriorate if the colony had the given
     * population.
     *
     * @param unitCount The proposed population for the colony.
     * @return 1, 0 or -1.
     */
    public int governmentChange(int unitCount) {
        final Specification spec = getSpecification();
        final int veryBadGovernment
            = spec.getInteger("model.option.veryBadGovernmentLimit");
        final int badGovernment
            = spec.getInteger("model.option.badGovernmentLimit");
        final int veryGoodGovernment
            = spec.getInteger("model.option.veryGoodGovernmentLimit");
        final int goodGovernment
            = spec.getInteger("model.option.goodGovernmentLimit");

        int rebelPercent = calculateSoL(unitCount, liberty);
        int rebelCount = calculateRebels(unitCount, rebelPercent);
        int loyalistCount = unitCount - rebelCount;

        int result = 0;
        if (rebelPercent >= veryGoodGovernment) { // There are no tories left.
            if (sonsOfLiberty < veryGoodGovernment) {
                result = 1;
            }
        } else if (rebelPercent >= goodGovernment) {
            if (sonsOfLiberty >= veryGoodGovernment) {
                result = -1;
            } else if (sonsOfLiberty < goodGovernment) {
                result = 1;
            }
        } else {
            if (sonsOfLiberty >= goodGovernment) {
                result = -1;
            } else { // Now that no bonus is applied, penalties may.
                if (loyalistCount > veryBadGovernment) {
                    if (tories <= veryBadGovernment) {
                        result = -1;
                    }
                } else if (loyalistCount > badGovernment) {
                    if (tories <= badGovernment) {
                        result = -1;
                    } else if (tories > veryBadGovernment) {
                        result = 1;
                    }
                } else {
                    if (tories > badGovernment) {
                        result = 1;
                    }
                }
            }
        }
        return result;
    }

    public ModelMessage checkForGovMgtChangeMessage() {
        final Specification spec = getSpecification();
        final int veryBadGovernment
            = spec.getInteger("model.option.veryBadGovernmentLimit");
        final int badGovernment
            = spec.getInteger("model.option.badGovernmentLimit");
        final int veryGoodGovernment
            = spec.getInteger("model.option.veryGoodGovernmentLimit");
        final int goodGovernment
            = spec.getInteger("model.option.goodGovernmentLimit");

        String msgId = null;
        int number = 0;
        ModelMessage.MessageType msgType = ModelMessage.MessageType.GOVERNMENT_EFFICIENCY;
        if (sonsOfLiberty >= veryGoodGovernment) {
            // there are no tories left
            if (oldSonsOfLiberty < veryGoodGovernment) {
                msgId = "model.colony.veryGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = veryGoodGovernment;
            }
        } else if (sonsOfLiberty >= goodGovernment) {
            if (oldSonsOfLiberty == veryGoodGovernment) {
                msgId = "model.colony.lostVeryGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = veryGoodGovernment;
            } else if (oldSonsOfLiberty < goodGovernment) {
                msgId = "model.colony.goodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = goodGovernment;
            }
        } else {
            if (oldSonsOfLiberty >= goodGovernment) {
                msgId = "model.colony.lostGoodGovernment";
                msgType = ModelMessage.MessageType.SONS_OF_LIBERTY;
                number = goodGovernment;
            }

            // Now that no bonus is applied, penalties may.
            if (tories > veryBadGovernment) {
                if (oldTories <= veryBadGovernment) {
                    // government has become very bad
                    msgId = "model.colony.veryBadGovernment";
                }
            } else if (tories > badGovernment) {
                if (oldTories <= badGovernment) {
                    // government has become bad
                    msgId = "model.colony.badGovernment";
                } else if (oldTories > veryBadGovernment) {
                    // government has improved, but is still bad
                    msgId = "model.colony.governmentImproved1";
                }
            } else if (oldTories > badGovernment) {
                // government was bad, but has improved
                msgId = "model.colony.governmentImproved2";
            }
        }

        GoodsType bells = getSpecification().getGoodsType("model.goods.bells");
        return (msgId == null) ? null
            : new ModelMessage(msgType, msgId, this, bells)
            .addName("%colony%", getName())
            .addAmount("%number%", number);
    }


    /**
     * Update the colony's production bonus.
     */
    protected void updateProductionBonus() {
        final Specification spec = getSpecification();
        final int veryBadGovernment
            = spec.getInteger("model.option.veryBadGovernmentLimit");
        final int badGovernment
            = spec.getInteger("model.option.badGovernmentLimit");
        final int veryGoodGovernment
            = spec.getInteger("model.option.veryGoodGovernmentLimit");
        final int goodGovernment
            = spec.getInteger("model.option.goodGovernmentLimit");
        int newBonus = (sonsOfLiberty >= veryGoodGovernment) ? 2
            : (sonsOfLiberty >= goodGovernment) ? 1
            : (tories > veryBadGovernment) ? -2
            : (tories > badGovernment) ? -1
            : 0;
        if (productionBonus != newBonus) invalidateCache();
        productionBonus = newBonus;
    }

    /**
     * Gets the number of units that would be good to add/remove from this
     * colony.  That is the number of extra units that can be added without
     * damaging the production bonus, or the number of units to remove to
     * improve it.
     *
     * @return The number of units to add to the colony, or if negative
     *      the negation of the number of units to remove.
     */
    public int getPreferredSizeChange() {
        int i, limit, pop = getUnitCount();
        if (productionBonus < 0) {
            limit = pop;
            for (i = 1; i < limit; i++) {
                if (governmentChange(pop - i) == 1) break;
            }
            return -i;
        } else {
            final Specification spec = getSpecification();
            limit = spec.getInteger("model.option.badGovernmentLimit");
            for (i = 1; i < limit; i++) {
                if (governmentChange(pop + i) == -1) break;
            }
            return i - 1;
        }
    }

    public Building getWarehouse() {
        // TODO: it should search for more than one building?
        for (Building building : buildingMap.values()) {
            if (!building.getType().getModifierSet("model.modifier.warehouseStorage").isEmpty()) {
                return building;
            }
        }
        return null;
    }

    /**
     * Returns true when colony has a stockade
     *
     * @return whether the colony has a stockade
     */
    public boolean hasStockade() {
        return (getStockade() != null);
    }

    /**
     * Returns the stockade building
     *
     * @return a <code>Building</code>
     */
    public Building getStockade() {
        // TODO: it should search for more than one building?
        for (Building building : buildingMap.values()) {
            if (!building.getType().getModifierSet(Modifier.DEFENCE).isEmpty()) {
                return building;
            }
        }
        return null;
    }

    /**
     * Gets the stockade key.
     * Uses the "stockadeKey" variable if it is non-null, which should
     * only be true for other player colonies.  Otherwise, get the real value.
     *
     * @return The stockade key.
     */
    public String getStockadeKey() {
        return (stockadeKey != null) ? stockadeKey : getTrueStockadeKey();
    }

    /**
     * Gets the true stockade key, as should be visible to the owner
     * or a player that can see this colony.
     *
     * @return The true stockade key.
     */
    public String getTrueStockadeKey() {
        Building stockade = getStockade();
        return (stockade == null) ? null
            : stockade.getType().getId().substring("model.building".length());
    }

    /**
     * Returns true if the Colony, or its owner has the ability
     * identified by <code>id</code>.
     *
     * @param id The id of the ability to test.
     * @param type An optional <code>FreeColGameObjectType</code> the
     *     ability applies to.
     * @param turn An optional applicable <code>Turn</code>.
     * @return True if the ability is present.
     */
    @Override
    public boolean hasAbility(String id, FreeColGameObjectType type,
                              Turn turn) {
        if (turn == null) turn = getGame().getTurn();
        return super.hasAbility(id, type, turn)
            || (owner != null && owner.hasAbility(id, type, turn));
    }

    /**
     * Returns the current production <code>Modifier</code>, which is
     * generated from the current production bonus.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return a <code>Modifier</code> value
     */
    public Modifier getProductionModifier(GoodsType goodsType) {
        Modifier result = new Modifier(goodsType.getId(), SOL_MODIFIER_SOURCE,
                                       productionBonus, Modifier.Type.ADDITIVE);
        result.setIndex(Modifier.COLONY_PRODUCTION_INDEX);
        return result;
    }

    /**
     * Verify if colony has the ability to bombard an enemy ship
     * adjacent to it.
     * @return true if it can, false otherwise
     */
    public boolean canBombardEnemyShip() {
    	if (isLandLocked()) {
            // only sea-side colonies can bombard
            return false;
        } else {
            // does it have the buildings that give such abilities?
            return hasAbility("model.ability.bombardShips");
        }
    }

    /**
     * Dispose of this colony.
     *
     * @return A list of disposed objects.
     */
    @Override
    public List<FreeColGameObject> disposeList() {
        List<FreeColGameObject> objects = new ArrayList<FreeColGameObject>();
        for (WorkLocation workLocation : getAllWorkLocations()) {
            objects.addAll(((FreeColGameObject) workLocation).disposeList());
        }
        TileImprovement road = getTile().getRoad();
        if (road != null && road.isVirtual()) {
            getTile().getTileItemContainer().removeTileItem(road);
        }
        objects.addAll(super.disposeList());
        return objects;
    }

    /**
     * Disposes this <code>Colony</code>. All <code>WorkLocation</code>s
     * owned by this <code>Colony</code> will also be destroyed.
     */
    @Override
    public void dispose() {
        disposeList();
    }


    /**
     * Returns the net production of the given GoodsType.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return an <code>int</code> value
     */
    public int getNetProductionOf(GoodsType goodsType) {
        return productionCache.getNetProductionOf(goodsType);
    }

    public boolean isProductive(WorkLocation workLocation) {
        ProductionInfo info = productionCache.getProductionInfo(workLocation);
        return info != null && info.getProduction() != null
            && !info.getProduction().isEmpty()
            && info.getProduction().get(0).getAmount() > 0;
    }

    /**
     * Returns the net production of the given GoodsType adjusted by
     * the possible consumption of BuildQueues.
     *
     * @param goodsType a <code>GoodsType</code> value
     * @return an <code>int</code> value
     */
    public int getAdjustedNetProductionOf(GoodsType goodsType) {
        int result = productionCache.getNetProductionOf(goodsType);
        for (BuildQueue<?> queue : new BuildQueue<?>[] { buildQueue,
                                                         populationQueue }) {
            ProductionInfo info = productionCache.getProductionInfo(queue);
            if (info != null) {
                for (AbstractGoods goods : info.getConsumption()) {
                    if (goods.getType() == goodsType) {
                        result += goods.getAmount();
                        break;
                    }
                }
            }
        }
        return result;
    }

    /**
     * Returns the ProductionInfo for the given Object.
     *
     * @param object an <code>Object</code> value
     * @return a <code>ProductionInfo</code> value
     */
    public ProductionInfo getProductionInfo(Object object) {
        return productionCache.getProductionInfo(object);
    }

    /**
     * Invalidates the production cache.
     */
    public void invalidateCache() {
        //logger.finest("invalidating production cache");
        productionCache.invalidate();
    }

    /**
     * Gets a copy of the current production map.
     * Useful in the server at the point net production is applied to a colony.
     *
     * @return A copy of the current production map.
     */
    protected TypeCountMap<GoodsType> getProductionMap() {
        return productionCache.getProductionMap();
    }

    /**
     * Returns a list of all {@link Consumer}s in the colony sorted by
     * priority. Consumers include all object that consume goods,
     * e.g. Units, Buildings and BuildQueues.
     *
     * @return a list of consumers
     */
    public List<Consumer> getConsumers() {
        List<Consumer> result = new ArrayList<Consumer>();
        result.addAll(getUnitList());
        result.addAll(buildingMap.values());
        result.add(buildQueue);
        result.add(populationQueue);

        Collections.sort(result, Consumer.COMPARATOR);
        return result;
    }


    /**
     * Collects tiles that need exploring, plowing or road building
     * which may depend on current use within the colony.
     *
     * @param exploreTiles A list of <code>Tile</code>s to update with tiles
     *     to explore.
     * @param clearTiles A list of <code>Tile</code>s to update with tiles
     *     to clear.
     * @param plowTiles A list of <code>Tile</code>s to update with tiles
     *     to plow.
     * @param roadTiles A list of <code>Tile</code>s to update with tiles
     *     to build roads on.
     */
    public void getColonyTileTodo(List<Tile> exploreTiles,
                                  List<Tile> clearTiles, List<Tile> plowTiles,
                                  List<Tile> roadTiles) {
        final Specification spec = getSpecification();
        final TileImprovementType clearImprovement
            = spec.getTileImprovementType("model.improvement.clearForest");
        final TileImprovementType plowImprovement
            = spec.getTileImprovementType("model.improvement.plow");
        final TileImprovementType roadImprovement
            = spec.getTileImprovementType("model.improvement.road");

        for (Tile t : getTile().getSurroundingTiles(1)) {
            if (t.hasLostCityRumour()) exploreTiles.add(t);
        }

        for (ColonyTile ct : getColonyTiles()) {
            Tile t = ct.getWorkTile();
            if (t == null) continue; // Colony has not claimed the tile yet.

            if ((t.getTileItemContainer() == null
                    || t.getTileItemContainer()
                    .getImprovement(plowImprovement) == null)
                && plowImprovement.isTileTypeAllowed(t.getType())) {
                if (ct.isColonyCenterTile()) {
                    plowTiles.add(t);
                } else {
                    for (Unit u : ct.getUnitList()) {
                        if (u != null && u.getWorkType() != null
                            && plowImprovement.getBonus(u.getWorkType()) > 0) {
                            plowTiles.add(t);
                            break;
                        }
                    }
                }
            }

            // To assess whether other improvements are beneficial we
            // really need a unit, doing work, so we can compare the output
            // with and without the improvement.  This means we can skip
            // further consideration of the colony center tile.
            if (ct.isColonyCenterTile() || ct.isEmpty()) continue;

            TileType oldType = t.getType();
            TileType newType;
            if ((t.getTileItemContainer() == null
                    || t.getTileItemContainer()
                    .getImprovement(clearImprovement) == null)
                && clearImprovement.isTileTypeAllowed(t.getType())
                && (newType = clearImprovement.getChange(oldType)) != null) {
                for (Unit u : ct.getUnitList()) {
                    if (newType.getProductionOf(u.getWorkType(), u.getType())
                        > oldType.getProductionOf(u.getWorkType(), u.getType())) {
                        clearTiles.add(t);
                        break;
                    }
                }
            }

            if (t.getRoad() == null
                && roadImprovement.isTileTypeAllowed(t.getType())) {
                for (Unit u : ct.getUnitList()) {
                    if (roadImprovement.getBonus(u.getWorkType()) > 0) {
                        roadTiles.add(t);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Finds another unit in this colony that would be better at doing the
     * job of the specified unit.
     *
     * @param expert The <code>Unit</code> to consider.
     * @return A better expert, or null if none available.
     */
    public Unit getBetterExpert(Unit expert) {
        GoodsType production = expert.getWorkType();
        GoodsType expertise = expert.getType().getExpertProduction();
        Unit bestExpert = null;
        int bestImprovement = 0;

        if (production == null || expertise == null
            || production == expertise) return null;

        // We have an expert not doing the job of their expertise.
        // Check if there is a non-expert doing the job instead.
        for (Unit nonExpert : getUnitList()) {
            if (nonExpert.getWorkType() != expertise
                || nonExpert.getType() == expert.getType()) continue;

            // We have found a unit of a different type doing the
            // job of this expert's expertise now check if the
            // production would be better if the units swapped
            // positions.
            int expertProductionNow = 0;
            int nonExpertProductionNow = 0;
            int expertProductionPotential = 0;
            int nonExpertProductionPotential = 0;

            // Get the current and potential productions for the
            // work location of the expert.
            WorkLocation ewl = expert.getWorkLocation();
            if (ewl != null) {
                expertProductionNow = ewl.getPotentialProduction(expertise,
                    expert.getType());
                nonExpertProductionPotential
                    = ewl.getPotentialProduction(expertise,
                        nonExpert.getType());
            }

            // Get the current and potential productions for the
            // work location of the non-expert.
            WorkLocation nwl = nonExpert.getWorkTile();
            if (nwl != null) {
                nonExpertProductionNow = nwl.getPotentialProduction(expertise,
                    nonExpert.getType());
                expertProductionPotential
                    = nwl.getPotentialProduction(expertise,
                        expert.getType());
            }

            // Find the unit that achieves the best improvement.
            int improvement = expertProductionPotential
                + nonExpertProductionPotential
                - expertProductionNow
                - nonExpertProductionNow;
            if (improvement > bestImprovement) {
                bestImprovement = improvement;
                bestExpert = nonExpert;
            }
        }
        return bestExpert;
    }


    // Interface Location

    // getId() inherited from FreeColGameObject
    // Overrides the unit list operations.

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean add(Locatable locatable) {
        if (locatable instanceof Unit) {
            return addUnit((Unit)locatable, null);
        }
        return super.add(locatable);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean remove(Locatable locatable) {
        if (locatable instanceof Unit) {
            return removeUnit((Unit)locatable);
        }
        return super.remove(locatable);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean contains(Locatable locatable) {
        if (locatable instanceof Unit) {
            throw new UnsupportedOperationException(); // FIXME!
        }
        return super.contains(locatable);
    }

    /**
     * Gets the unit count.
     * Must handle the unitCount override for foreign colonies.
     *
     * @return The visible unit count.
     */
    @Override
    public int getUnitCount() {
        return (unitCount >= 0) ? unitCount : getUnitList().size();
    }

    /**
     * Gets a list of all units in working in this colony.
     *
     * Totally ignores the unit list provided by the underlying UnitLocation.
     *
     * @return A list of <code>Unit</code>s in this colony.
     */
    @Override
    public List<Unit> getUnitList() {
        ArrayList<Unit> units = new ArrayList<Unit>();
        for (WorkLocation wl : getCurrentWorkLocations()) {
            units.addAll(wl.getUnitList());
        }
        return units;
    }

    /**
     * Returns just this Colony itself.
     *
     * @return This colony.
     */
    @Override
    public final Colony getColony() {
        return this;
    }

    // UnitLocation routines
    // getUnitCapacity inherited from UnitLocation
    // getNoAddReason inherited from Settlement

    // GoodsLocation routines

    /**
     * Returns the capacity of this colony's warehouse. All goods
     * above this limit, except Food, will be removed by the
     * end-of-turn processing.
     *
     * This will return 0 unless additive modifiers are present.  This
     * is intentional.
     *
     * @return The capacity of this <code>Colony</code>'s warehouse.
     */
    public int getGoodsCapacity() {
        return (int) applyModifier(0f, "model.modifier.warehouseStorage",
                                   null, getGame().getTurn());
    }


    // Settlement routines

    /**
     * {@inheritDoc}
     */
    public String getImageKey() {
        if (isUndead()) return "undead";

        int count = getDisplayUnitCount();
        String key = (count <= 3) ? "small"
            : (count <= 7) ? "medium"
            : "large";
        String stockade = getStockadeKey();
        if (stockade != null) key += stockade;
        return "model.settlement." + key + ".image";
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public StringTemplate getLocationNameFor(Player player) {
        // Everyone can always work out a colony name.
        return StringTemplate.name(getName());
    }

    /**
     * Gets the <code>Unit</code> that is currently defending this
     * <code>Colony</code>.
     *
     * Note that this function will only return a unit working inside
     * the colony.  Typically, colonies are also defended by units
     * outside the colony on the same tile.  To consider units outside
     * the colony as well, use
     * @see Tile#getDefendingUnit
     * instead.
     * 
     * Returns an arbitrary unarmed land unit unless Paul Revere
     * is present as founding father, in which case the unit can be
     * armed as well.
     *
     * @param attacker The unit that would be attacking this colony.
     * @return The <code>Unit</code> that has been chosen to defend this
     *         colony, or <code>null</code> if the colony belongs to another
     *         player and client is not permitted to view contents.
     * @see Tile#getDefendingUnit(Unit)
     * @throws IllegalStateException if there are units in the colony
     */
    @Override
    public Unit getDefendingUnit(Unit attacker) {
        List<Unit> unitList = getUnitList();

        if (unitCount >= 0 && unitList.isEmpty()) {
            // There are units, but we don't see them
            return null;
        }

        Unit defender = null;
        float defencePower = -1.0f;
        for (Unit nextUnit : unitList) {
            float unitPower = getGame().getCombatModel()
                .getDefencePower(attacker, nextUnit);
            if (Unit.betterDefender(defender, defencePower,
                    nextUnit, unitPower)) {
                defender = nextUnit;
                defencePower = unitPower;
            }
        }
        if (defender == null) {
            throw new IllegalStateException("Colony " + getName()
                + " contains no units!");
        }
        return defender;
    }

    /**
     * {@inheritDoc}
     */
    public float getDefenceRatio() {
        return getTotalDefencePower() / (1 + getWorkLocationUnitCount());
    }

    /**
     * {@inheritDoc}
     */
    public RandomRange getPlunderRange(Unit attacker) {
        if (canBePlundered()) {
            int upper = (owner.getGold() * (getUnitCount() + 1))
                / (owner.getColoniesPopulation() + 1);
            if (upper > 0) return new RandomRange(100, 1, upper+1, 1);
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public int getSoL() {
        return sonsOfLiberty;
    }

    /**
     * {@inheritDoc}
     */
    public int getUpkeep() {
        int upkeep = 0;
        for (Building building : buildingMap.values()) {
            upkeep += building.getType().getUpkeep();
        }
        return upkeep;
    }

    /**
     * {@inheritDoc}
     */
    public boolean propagateAlarm(Player player, int addToAlarm) {
        return false; // No-op for European colonies.
    }

    /**
     * {@inheritDoc}
     */
    public int getTotalProductionOf(GoodsType goodsType) {
        int amount = 0;
        for (WorkLocation workLocation : getCurrentWorkLocations()) {
            amount += workLocation.getTotalProductionOf(goodsType);
        }
        return amount;
    }


    // Serialization

    /**
     * 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.
     */
    @Override
    protected void toXMLImpl(XMLStreamWriter out, Player player,
                             boolean showAll, boolean toSavedGame)
        throws XMLStreamException {
        PlayerExploredTile pet;

        out.writeStartElement(getXMLElementTagName());

        super.writeAttributes(out);
        out.writeAttribute("established", Integer.toString(established.getNumber()));
        if (showAll || toSavedGame || player.owns(this)) {
            out.writeAttribute("sonsOfLiberty", Integer.toString(sonsOfLiberty));
            out.writeAttribute("oldSonsOfLiberty", Integer.toString(oldSonsOfLiberty));
            out.writeAttribute("tories", Integer.toString(tories));
            out.writeAttribute("oldTories", Integer.toString(oldTories));
            out.writeAttribute("liberty", Integer.toString(liberty));
            out.writeAttribute("immigration", Integer.toString(immigration));
            out.writeAttribute("productionBonus", Integer.toString(productionBonus));
            out.writeAttribute("landLocked", Boolean.toString(landLocked));

        } else if ((pet = getTile().getPlayerExploredTile(player)) != null) {
            if (pet.getColonyUnitCount() > 0) {
                out.writeAttribute("unitCount",
                    Integer.toString(pet.getColonyUnitCount()));
            }
            if (pet.getColonyStockadeKey() != null) {
                out.writeAttribute("stockadeKey",
                    pet.getColonyStockadeKey());
            }
        }

        writeChildren(out, player, showAll, toSavedGame);

        out.writeEndElement();
    }

    /**
     * {@inheritDoc}
     */
    protected void writeChildren(XMLStreamWriter out, Player player,
                                 boolean showAll, boolean toSavedGame)
        throws XMLStreamException {
        if (showAll || toSavedGame || player.owns(this)) {
            for (ExportData data : exportData.values()) {
                data.toXML(out);
            }
            // Only write the features that need specific instantiation,
            // which is currently only those with increments.
            // Fixed features will be added from their origins (usually
            // buildings).
            Turn turn = getGame().getTurn();
            for (Modifier modifier : getModifiers()) {
                if (modifier.hasIncrement() && !modifier.isOutOfDate(turn)) {
                    modifier.toXML(out);
                }
            }

            for (WorkLocation workLocation : getAllWorkLocations()) {
                workLocation.toXML(out, player, showAll, toSavedGame);
            }
            for (BuildableType item : buildQueue.getValues()) {
                out.writeStartElement(BUILD_QUEUE_TAG);
                out.writeAttribute(ID_ATTRIBUTE_TAG, item.getId());
                out.writeEndElement();
            }
            for (BuildableType item : populationQueue.getValues()) {
                out.writeStartElement(POPULATION_QUEUE_TAG);
                out.writeAttribute(ID_ATTRIBUTE_TAG, item.getId());
                out.writeEndElement();
            }
            super.writeChildren(out, player, showAll, toSavedGame);
        }
    }

    /**
     * Initialize this object from an XML-representation of this object.
     *
     * @param in The input stream with the XML.
     */
    public void readAttributes(XMLStreamReader in) throws XMLStreamException {
        super.readAttributes(in);

        owner.addSettlement(this);
        established = new Turn(getAttribute(in, "established", 0));
        sonsOfLiberty = getAttribute(in, "sonsOfLiberty", 0);
        oldSonsOfLiberty = getAttribute(in, "oldSonsOfLiberty", 0);
        tories = getAttribute(in, "tories", 0);
        oldTories = getAttribute(in, "oldTories", 0);
        liberty = getAttribute(in, "liberty", 0);
        immigration = getAttribute(in, "immigration", 0);
        productionBonus = getAttribute(in, "productionBonus", 0);
        landLocked = getAttribute(in, "landLocked", true);
        if (!landLocked) addAbility(HAS_PORT);
        unitCount = getAttribute(in, "unitCount", -1);
        stockadeKey = in.getAttributeValue(null, "stockadeKey");
    }

    public void readChildren(XMLStreamReader in) throws XMLStreamException {
        int oldUnitCount = getUnitCount();
        // Clear containers
        colonyTiles.clear();
        buildingMap.clear();
        exportData.clear();
        buildQueue.clear();
        populationQueue.clear();

        // Read child elements:
        while (in.nextTag() != XMLStreamConstants.END_ELEMENT) {
            if (in.getLocalName().equals(ColonyTile.getXMLElementTagName())) {
                ColonyTile ct = updateFreeColGameObject(in, ColonyTile.class);
                colonyTiles.add(ct);
            } else if (in.getLocalName().equals(Building.getXMLElementTagName())) {
                Building building = updateFreeColGameObject(in, Building.class);
                addBuilding(building);
            } else if (in.getLocalName().equals(ExportData.getXMLElementTagName())) {
                ExportData data = new ExportData();
                data.readFromXML(in);
                exportData.put(data.getId(), data);
            } else if (Modifier.getXMLElementTagName().equals(in.getLocalName())) {
                Modifier m = new Modifier(in, getSpecification());
                if (m.hasIncrement()) {
                    addModifier(m);
                } else {
                    logger.warning("Ignoring non-timed colony modifier: "
                        + m.toString());
                }
            } else if ("buildQueue".equals(in.getLocalName())) {
                // TODO: remove support for old format, move serialization to BuildQueue
                int size = getAttribute(in, ARRAY_SIZE, 0);
                if (size > 0) {
                    for (int x = 0; x < size; x++) {
                        String typeId = in.getAttributeValue(null, "x" + Integer.toString(x));
                        buildQueue.add(getSpecification().getType(typeId, BuildableType.class));
                    }
                }
                in.nextTag();
            } else if (BUILD_QUEUE_TAG.equals(in.getLocalName())) {
                String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG);
                buildQueue.add(getSpecification().getType(id, BuildableType.class));
                in.nextTag();
            } else if (POPULATION_QUEUE_TAG.equals(in.getLocalName())) {
                String id = in.getAttributeValue(null, ID_ATTRIBUTE_TAG);
                populationQueue.add(getSpecification().getType(id, UnitType.class));
                in.nextTag();
            } else {
                super.readChild(in);
            }
        }
        // @compat 0.9.x
        if (populationQueue.isEmpty()) {
            for (UnitType unitType : getSpecification().getUnitTypesWithAbility(Ability.BORN_IN_COLONY)) {
                GoodsType food = getSpecification().getGoodsType("model.goods.food");
                List<AbstractGoods> required = unitType.getRequiredGoods();
                boolean found = false;
                for (AbstractGoods goods : required) {
                    if (goods.getType() == food) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    required.add(new AbstractGoods(food, FOOD_PER_COLONIST));
                    unitType.setRequiredGoods(required);
                }
                populationQueue.add(unitType);
            }
        }
        // end compatibility code

        // Hack to kick AI colonies when the population changes.
        if (owner.isAI() && getUnitCount() != oldUnitCount) {
            firePropertyChange(REARRANGE_WORKERS, true, false);
        }
    }

    /**
     * Partial writer, so that "remove" messages can be brief.
     *
     * @param out The target stream.
     * @param fields The fields to write.
     * @throws XMLStreamException If there are problems writing the stream.
     */
    @Override
    protected void toXMLPartialImpl(XMLStreamWriter out, String[] fields)
        throws XMLStreamException {
        toXMLPartialByClass(out, getClass(), fields);
    }

    /**
     * Partial reader, so that "remove" messages can be brief.
     *
     * @param in The input stream with the XML.
     * @throws XMLStreamException If there are problems reading the stream.
     */
    @Override
    public void readFromXMLPartialImpl(XMLStreamReader in)
        throws XMLStreamException {
        readFromXMLPartialByClass(in, getClass());
    }

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