/**
 *  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.server.ai;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

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

import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTradeItem;
import net.sf.freecol.common.model.CombatModel;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.FeatureContainer;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.GoldTradeItem;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsTradeItem;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.IndianSettlement;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.Market;
import net.sf.freecol.common.model.Modifier;
import net.sf.freecol.common.model.Ownable;
import net.sf.freecol.common.model.PathNode;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Player.PlayerType;
import net.sf.freecol.common.model.Player.Stance;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StanceTradeItem;
import net.sf.freecol.common.model.Tension;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TradeItem;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.Unit.UnitState;
import net.sf.freecol.common.model.UnitTradeItem;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.common.model.pathfinding.CostDeciders;
import net.sf.freecol.common.model.pathfinding.GoalDecider;
import net.sf.freecol.common.networking.NetworkConstants;
import net.sf.freecol.common.util.Utils;
import net.sf.freecol.server.ai.mission.BuildColonyMission;
import net.sf.freecol.server.ai.mission.CashInTreasureTrainMission;
import net.sf.freecol.server.ai.mission.DefendSettlementMission;
import net.sf.freecol.server.ai.mission.IdleAtColonyMission;
import net.sf.freecol.server.ai.mission.Mission;
import net.sf.freecol.server.ai.mission.PioneeringMission;
import net.sf.freecol.server.ai.mission.PrivateerMission;
import net.sf.freecol.server.ai.mission.ScoutingMission;
import net.sf.freecol.server.ai.mission.TransportMission;
import net.sf.freecol.server.ai.mission.UnitSeekAndDestroyMission;
import net.sf.freecol.server.ai.mission.UnitWanderHostileMission;
import net.sf.freecol.server.ai.mission.WishRealizationMission;
import net.sf.freecol.server.ai.mission.WorkInsideColonyMission;
import net.sf.freecol.server.model.ServerPlayer;

import org.w3c.dom.Element;


/**
 * Objects of this class contains AI-information for a single {@link
 * Player} and is used for controlling this player.
 *
 * The method {@link #startWorking} gets called by the
 * {@link AIInGameInputHandler} when it is this player's turn.
 */
public class EuropeanAIPlayer extends AIPlayer {

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

    /**
     * Stores temporary information for sessions (trading with another player
     * etc).
     */
    private HashMap<String, Integer> sessionRegister = new HashMap<String, Integer>();

    /**
     * Creates a new <code>AIPlayer</code>.
     *
     * @param aiMain The main AI-class.
     * @param player The player that should be associated with this
     *            <code>AIPlayer</code>.
     */
    public EuropeanAIPlayer(AIMain aiMain, ServerPlayer player) {
        super(aiMain, player.getId());
        setPlayer(player);
    }

    /**
     * Creates a new <code>AIPlayer</code> and reads the information from the
     * given <code>Element</code>.
     *
     * @param aiMain The main AI-class.
     * @param element The XML-element containing information.
     */
    public EuropeanAIPlayer(AIMain aiMain, Element element) {
        super(aiMain, element.getAttribute(ID_ATTRIBUTE));
        readFromXMLElement(element);
    }

    /**
     * Creates a new <code>AIPlayer</code>.
     *
     * @param aiMain The main AI-object.
     * @param in The input stream containing the XML.
     * @throws XMLStreamException if a problem was encountered during parsing.
     */
    public EuropeanAIPlayer(AIMain aiMain, XMLStreamReader in) throws XMLStreamException {
        super(aiMain, in.getAttributeValue(null, ID_ATTRIBUTE));
        readFromXML(in);
    }


/* IMPLEMENTATION (AIPlayer interface) ****************************************/


    /**
     * Tells this <code>AIPlayer</code> to make decisions. The
     * <code>AIPlayer</code> is done doing work this turn when this method
     * returns.
     */
    public void startWorking() {
        final Player player = getPlayer();
        logger.finest("Entering method startWorking: "
                      + player + ", year " + getGame().getTurn());
        sessionRegister.clear();
        clearAIUnits();
        cheat();
        determineStances();
        rearrangeWorkersInColonies();
        abortInvalidAndOneTimeMissions();
        ensureColonyMissions();
        giveNormalMissions();
        bringGifts();
        demandTribute();
        createAIGoodsInColonies();
        createTransportLists();
        doMissions();
        rearrangeWorkersInColonies();
        abortInvalidMissions();
        // Some of the mission might have been invalidated by a another mission.
        giveNormalMissions();
        doMissions();
        rearrangeWorkersInColonies();
        abortInvalidMissions();
        ensureColonyMissions();
        clearAIUnits();
    }

    /**
     * Gets a list of all the player's tile improvement plans required by
     * the colonies.
     *
     * @return A list of tile improvements.
     * @see net.sf.freecol.common.model.TileImprovement
     */
    public List<TileImprovementPlan> getTileImprovementPlans() {
        List<TileImprovementPlan> tileImprovements
            = new ArrayList<TileImprovementPlan>();
        for (AIColony aic : getAIColonies()) {
            tileImprovements.addAll(aic.getTileImprovementPlans());
        }
        return tileImprovements;
    }
        
    /**
     * Remove a <code>TileImprovementPlan</code> from the relevant colony.
     */
    public void removeTileImprovementPlan(TileImprovementPlan plan) {
        for (AIColony aic : getAIColonies()) {
            if (aic.removeTileImprovementPlan(plan)) return;
        }
        logger.warning("Not found given TileImprovementPlan to remove");
    }

    /**
     * Asks the server to recruit a unit in Europe on behalf of the AIPlayer.
     *
     * TODO: Move this to a specialized Handler class (AIEurope?)
     * TODO: Give protected access?
     *
     * @param index The index of the unit to recruit in the recruitables list,
     *     (if not a valid index, recruit a random unit).
     * @return The new AIUnit created by this action or null on failure.
     */
    public AIUnit recruitAIUnitInEurope(int index) {
        AIUnit aiUnit = null;
        Europe europe = getPlayer().getEurope();
        int n = europe.getUnitCount();
        final String selectAbility = "model.ability.selectRecruit";
        int slot = (index >= 0 && index < Europe.RECRUIT_COUNT
            && getPlayer().hasAbility(selectAbility)) ? (index + 1) : 0;
        if (AIMessage.askEmigrate(getConnection(), slot)
            && europe.getUnitCount() == n+1) {
            aiUnit = getAIUnit(europe.getUnitList().get(n));
        }
        return aiUnit;
    }

    /**
     * Helper function for server communication - Ask the server
     * to train a unit in Europe on behalf of the AIGetPlayer().
     *
     * TODO: Move this to a specialized Handler class (AIEurope?)
     * TODO: Give protected access?
     *
     * @return the new AIUnit created by this action. May be null.
     */
    public AIUnit trainAIUnitInEurope(UnitType unitType) {
        if (unitType==null) {
            throw new IllegalArgumentException("Invalid UnitType.");
        }

        AIUnit aiUnit = null;
        Europe europe = getPlayer().getEurope();
        int n = europe.getUnitCount();

        if (AIMessage.askTrainUnitInEurope(getConnection(), unitType)
            && europe.getUnitCount() == n+1) {
            aiUnit = getAIUnit(europe.getUnitList().get(n));
        }
        return aiUnit;
    }

    /**
     * This is a temporary method which are used for forcing the
     * computer players into building more colonies. The method will
     * be removed after the proper code for deciding whether a colony
     * should be built or not has been implemented.
     *
     * @return <code>true</code> if the AI should build more colonies.
     */
    public boolean hasFewColonies() {
        if (!getPlayer().canBuildColonies()) return false;
        int numberOfColonies = 0;
        int numberOfWorkers = 0;
        for (Settlement settlement : getPlayer().getSettlements()) {
            numberOfColonies++;
            numberOfWorkers += settlement.getUnitCount();
        }

        boolean result = numberOfColonies <= 2
            || (numberOfColonies >= 3
                && numberOfWorkers / numberOfColonies > numberOfColonies - 2);
        logger.finest("hasFewColonies (" + numberOfColonies
            + " : " + numberOfWorkers + "): " + result);
        return result;
    }

    /**
     * Gets the wishes for all this player's colonies, sorted by the
     * {@link Wish#getValue value}.
     *
     * @return A list of wishes.
     */
    public List<Wish> getWishes() {
        List<Wish> wishes = new ArrayList<Wish>();
        for (AIColony aic : getAIColonies()) {
            wishes.addAll(aic.getWishes());
        }
        Collections.sort(wishes);
        return wishes;
    }

    /**
     * Selects the most useful founding father offered.
     *
     * @param foundingFathers The founding fathers on offer.
     * @return The founding father selected.
     */
    public FoundingFather selectFoundingFather(List<FoundingFather> foundingFathers) {
        // TODO: improve choice
        int age = getGame().getTurn().getAge();
        FoundingFather bestFather = null;
        int bestWeight = Integer.MIN_VALUE;
        for (FoundingFather father : foundingFathers) {
            if (father == null) continue;

            // For the moment, arbitrarily: always choose the one
            // offering custom houses.  Allowing the AI to build CH
            // early alleviates the complexity problem of handling all
            // TransportMissions correctly somewhat.
            if (father.hasAbility("model.ability.buildCustomHouse")) {
                bestFather = father;
                break;
            }

            int weight = father.getWeight(age);
            if (weight > bestWeight) {
                bestWeight = weight;
                bestFather = father;
            }
        }
        return bestFather;
    }

    /**
     * Decides whether to accept the monarch's tax raise or not.
     *
     * @param tax The new tax rate to be considered.
     * @return <code>true</code> if the tax raise should be accepted.
     */
    public boolean acceptTax(int tax) {
        Goods toBeDestroyed = getPlayer().getMostValuableGoods();
        if (toBeDestroyed == null) {
            return false;
        }

        GoodsType goodsType = toBeDestroyed.getType();
        if (goodsType.isFoodType() || goodsType.isBreedable()) {
            // we should be able to produce food and horses ourselves
            // TODO: check whether we already have horses!
            return false;
        } else if (goodsType.isMilitaryGoods() ||
                   goodsType.isTradeGoods() ||
                   goodsType.isBuildingMaterial()) {
            if (getGame().getTurn().getAge() == 3) {
                // by this time, we should be able to produce
                // enough ourselves
                // TODO: check whether we have an armory, at least
                return false;
            } else {
                return true;
            }
        } else {
            int averageIncome = 0;
            int numberOfGoods = 0;
            // TODO: consider the amount of goods produced. If we
            // depend on shipping huge amounts of cheap goods, we
            // don't want these goods to be boycotted.
            List<GoodsType> goodsTypes = getSpecification().getGoodsTypeList();
            for (GoodsType type : goodsTypes) {
                if (type.isStorable()) {
                    averageIncome += getPlayer().getIncomeAfterTaxes(type);
                    numberOfGoods++;
                }
            }
            averageIncome = averageIncome / numberOfGoods;
            if (getPlayer().getIncomeAfterTaxes(toBeDestroyed.getType()) > averageIncome) {
                // this is a more valuable type of goods
                return false;
            } else {
                return true;
            }
        }
    }

    /**
     * Decides whether to accept an Indian demand, or not.
     *
     * @param unit The <code>Unit</code> making demands.
     * @param colony The <code>Colony</code> where demands are being made.
     * @param goods The <code>Goods</code> demanded.
     * @param gold The amount of gold demanded.
     * @return True if this player accepts the demand.
     */
    public boolean indianDemand(Unit unit, Colony colony,
                                Goods goods, int gold) {
        // TODO: make a better choice, check whether the colony is
        // well defended
        return !"conquest".equals(getAIAdvantage());
    }

    /**
     * Decides to accept an offer of mercenaries or not.
     * TODO: make a better choice.
     *
     * @return True if the mercenaries are accepted.
     */
    public boolean acceptMercenaries() {
        return getPlayer().isAtWar() || "conquest".equals(getAIAdvantage());
    }


    public boolean acceptDiplomaticTrade(DiplomaticTrade agreement) {
        boolean validOffer = true;
        Stance stance = null;
        int value = 0;
        Iterator<TradeItem> itemIterator = agreement.iterator();
        while (itemIterator.hasNext()) {
            TradeItem item = itemIterator.next();
            if (item instanceof GoldTradeItem) {
                int gold = ((GoldTradeItem) item).getGold();
                if (item.getSource() == getPlayer()) {
                    value -= gold;
                } else {
                    value += gold;
                }
            } else if (item instanceof StanceTradeItem) {
                // TODO: evaluate whether we want this stance change
                stance = ((StanceTradeItem) item).getStance();
                switch (stance) {
                    case UNCONTACTED:
                        validOffer = false; //never accept invalid stance change
                        break;
                    case WAR: // always accept war without cost
                        break;
                    case CEASE_FIRE:
                        value -= 500;
                        break;
                    case PEACE:
                        if (!agreement.getSender().hasAbility("model.ability.alwaysOfferedPeace")) {
                            // TODO: introduce some kind of counter in order to avoid
                            // Benjamin Franklin exploit
                            value -= 1000;
                        }
                        break;
                    case ALLIANCE:
                        value -= 2000;
                        break;
                    }

            } else if (item instanceof ColonyTradeItem) {
                // TODO: evaluate whether we might wish to give up a colony
                if (item.getSource() == getPlayer()) {
                    validOffer = false;
                    break;
                } else {
                    value += 1000;
                }
            } else if (item instanceof UnitTradeItem) {
                // TODO: evaluate whether we might wish to give up a unit
                if (item.getSource() == getPlayer()) {
                    validOffer = false;
                    break;
                } else {
                    value += 100;
                }
            } else if (item instanceof GoodsTradeItem) {
                Goods goods = ((GoodsTradeItem) item).getGoods();
                if (item.getSource() == getPlayer()) {
                    value -= getPlayer().getMarket().getBidPrice(goods.getType(), goods.getAmount());
                } else {
                    value += getPlayer().getMarket().getSalePrice(goods.getType(), goods.getAmount());
                }
            }
        }
        if (validOffer) {
            logger.info("Trade value is " + value + ", accept if >=0");
        } else {
            logger.info("Trade offer is considered invalid!");
        }
        return (value>=0)&&validOffer;
    }


    /**
     * Called after another <code>Player</code> sends a <code>trade</code> message
     *
     *
     * @param goods The goods which we are going to offer
     */
    public void registerSellGoods(Goods goods) {
        String goldKey = "tradeGold#" + goods.getType().getId() + "#" + goods.getAmount()
            + "#" + goods.getLocation().getId();
        sessionRegister.put(goldKey, null);
    }

    /**
     * Called when another <code>Player</code> proposes to buy.
     *
     *
     * @param unit The foreign <code>Unit</code> trying to trade.
     * @param settlement The <code>Settlement</code> this player owns and
     *            which the given <code>Unit</code> is trading.
     * @param goods The goods the given <code>Unit</code> is trying to sell.
     * @param gold The suggested price.
     * @return The price this <code>AIPlayer</code> suggests or
     *         {@link NetworkConstants#NO_TRADE}.
     */
    // TODO: this obviously applies only to native players. Is there
    // an European equivalent?
    public int buyProposition(Unit unit, Settlement settlement, Goods goods, int gold) {
        logger.finest("Entering method buyProposition");
        Player buyer = unit.getOwner();
        String goldKey = "tradeGold#" + goods.getType().getId() + "#" + goods.getAmount()
            + "#" + settlement.getId();
        String hagglingKey = "tradeHaggling#" + unit.getId();
        Integer registered = sessionRegister.get(goldKey);
        if (registered == null) {
            int price = ((IndianSettlement) settlement).getPriceToSell(goods)
                + getPlayer().getTension(buyer).getValue();
            Unit missionary = ((IndianSettlement) settlement).getMissionary(buyer);
            if (missionary != null && getSpecification()
                .getBoolean("model.option.enhancedMissionaries")) {
                // 10% bonus for missionary, 20% if expert
                int bonus = (missionary.hasAbility(Ability.EXPERT_MISSIONARY)) ? 8
                    : 9;
                price = (price * bonus) / 10;
            }
            sessionRegister.put(goldKey, new Integer(price));
            return price;
        } else {
            int price = registered.intValue();
            if (price < 0 || price == gold) {
                return price;
            } else if (gold < (price * 9) / 10) {
                logger.warning("Cheating attempt: sending a offer too low");
                sessionRegister.put(goldKey, new Integer(-1));
                return NetworkConstants.NO_TRADE;
            } else {
                int haggling = 1;
                if (sessionRegister.containsKey(hagglingKey)) {
                    haggling = sessionRegister.get(hagglingKey).intValue();
                }
                if (Utils.randomInt(logger, "Buy gold", getAIRandom(),
                        3 + haggling) <= 3) {
                    sessionRegister.put(goldKey, new Integer(gold));
                    sessionRegister.put(hagglingKey, new Integer(haggling + 1));
                    return gold;
                } else {
                    sessionRegister.put(goldKey, new Integer(-1));
                    return NetworkConstants.NO_TRADE_HAGGLE;
                }
            }
        }
    }

    /**
     * Called when another <code>Player</code> proposes a sale.
     *
     * @param unit The foreign <code>Unit</code> trying to trade.
     * @param settlement The <code>Settlement</code> this player owns and
     *            which the given <code>Unit</code> if trying to sell goods.
     * @param goods The goods the given <code>Unit</code> is trying to sell.
     * @param gold The suggested price.
     * @return The price this <code>AIPlayer</code> suggests or
     *         {@link NetworkConstants#NO_TRADE}.
     */
    public int sellProposition(Unit unit, Settlement settlement, Goods goods, int gold) {
        logger.finest("Entering method sellProposition");
        Colony colony = (Colony) settlement;
        Player otherPlayer = unit.getOwner();
        // don't pay for more than fits in the warehouse
        int amount = colony.getWarehouseCapacity() - colony.getGoodsCount(goods.getType());
        amount = Math.min(amount, goods.getAmount());
        // get a good price
        Tension.Level tensionLevel = getPlayer().getTension(otherPlayer).getLevel();
        int percentage = (9 - tensionLevel.ordinal()) * 10;
        // what we could get for the goods in Europe (minus taxes)
        int netProfits = ((100 - getPlayer().getTax())
                          * getPlayer().getMarket().getSalePrice(goods.getType(), amount)) / 100;
        int price = (netProfits * percentage) / 100;
        return price;

    }

/* Internal methods ***********************************************************/


    /**
     * Cheats for the AI.  Please try to centralize cheats here.
     *
     * TODO: Remove when the AI is good enough.
     */
    private void cheat() {
        logger.finest("Entering method cheat");
        Specification spec = getSpecification();
        Market market = getPlayer().getMarket();
        for (GoodsType goodsType : spec.getGoodsTypeList()) {
            if (market.getArrears(goodsType) > 0
                && Utils.randomInt(logger, "Cheat boycott",
                    getAIRandom(), 5) == 0) {
                market.setArrears(goodsType, 0);
                // Just remove one goods party modifier (we can not
                // currently identify which modifier applies to which
                // goods type, but that is not worth fixing for the
                // benefit of `temporary' cheat code).  If we do not
                // do this, AI colonies accumulate heaps of party
                // modifiers because of the cheat boycott removal.
                findOne: for (Colony c : getPlayer().getColonies()) {
                    FeatureContainer fc = c.getFeatureContainer();
                    for (Modifier m : fc.getModifiers()) {
                        if ("model.modifier.colonyGoodsParty".equals(m.getSource())) {
                            fc.removeModifier(m);
                            break findOne;
                        }
                    }
                }
            }
        }

        //TODO: This seems to buy units the AIPlayer can't possibly use (see BR#2566180)
        if (getAIMain().getFreeColServer().isSingleplayer()
            && getPlayer().getPlayerType() == PlayerType.COLONIAL) {
            Europe europe = getPlayer().getEurope();
            List<UnitType> unitTypes = spec.getUnitTypeList();

            if (Utils.randomInt(logger, "Cheat buy unit",
                    getAIRandom(), 10) == 1) {
                WorkerWish bestWish = null;
                int bestValue = Integer.MIN_VALUE;
                for (AIColony aic : getAIColonies()) {
                    for (WorkerWish ww : aic.getWorkerWishes()) {
                        if (ww.getValue() > bestValue) {
                            bestValue = ww.getValue();
                            bestWish = ww;
                        }
                    }
                }
                UnitType unitType;
                int unitPrice;
                if (bestWish != null
                    && (unitType = bestWish.getUnitType()) != null
                    && (unitPrice = europe.getUnitPrice(unitType)) >= 0) {
                    // cheat add the necessary amount of money
                    getPlayer().modifyGold(unitPrice);
                    AIUnit aiUnit = trainAIUnitInEurope(unitType);
                    if (aiUnit != null) {
                        Unit unit = aiUnit.getUnit();
                        if (unit != null && unit.isColonist()) {
                            aiUnit.equipForRole(Unit.Role.DRAGOON, true);
                        }
                        aiUnit.setMission(new WishRealizationMission(getAIMain(), aiUnit, bestWish));
                    }
                }
            }
            // TODO: better heuristics to determine which ship to buy
            if (Utils.randomInt(logger, "Cheat buy ship",
                    getAIRandom(), 40) == 21) {
                int total = 0;
                ArrayList<UnitType> navalUnits = new ArrayList<UnitType>();
                for (UnitType unitType : unitTypes) {
                    if (unitType.hasAbility(Ability.NAVAL_UNIT) && unitType.hasPrice()) {
                        navalUnits.add(unitType);
                        total += europe.getUnitPrice(unitType);
                    }
                }

                UnitType unitToPurchase = null;
                int r = Utils.randomInt(logger, "Cheat which ship",
                    getAIRandom(), total);
                total = 0;
                for (UnitType unitType : navalUnits) {
                    total += unitType.getPrice();
                    if (r < total) {
                        unitToPurchase = unitType;
                        break;
                    }
                }
                getPlayer().modifyGold(europe.getUnitPrice(unitToPurchase));
                this.trainAIUnitInEurope(unitToPurchase);
            }
        }
    }

    /**
     * Ensures that all workers inside a colony gets a
     * {@link WorkInsideColonyMission}.
     */
    private void ensureColonyMissions() {
        logger.finest("Entering method ensureColonyMissions");

        for (AIUnit au : getAIUnits()) {
            if (au.hasMission()) continue;

            // TODO: Find out why this happens, or even if it still does.
            Unit u = au.getUnit();
            if (u.getLocation() instanceof WorkLocation) {
                AIColony ac = getAIColony(u.getColony());
                if (ac == null) {
                    logger.warning("No AIColony for unit: " + u
                                   + " at: " + u.getLocation());
                    continue;
                }
                au.setMission(new WorkInsideColonyMission(getAIMain(), au, ac));
            }
        }
    }

    /**
     * Calls {@link AIColony#rearrangeWorkers} for every colony this player
     * owns.
     */
    private void rearrangeWorkersInColonies() {
        logger.finest("Entering method rearrangeWorkersInColonies");
        for (AIColony aic : getAIColonies()) aic.rearrangeWorkers();
    }

    /**
     * Takes the necessary actions to secure a european colony
     */
    /*
    private void secureColony(Colony colony) {
        GoodsType musketType = getSpecification().getGoodsType("model.goods.muskets");
        GoodsType horsesType = getSpecification().getGoodsType("model.goods.horses");
        final EquipmentType muskets = getSpecification().getEquipmentType("model.equipment.muskets");
        final EquipmentType horses = getSpecification().getEquipmentType("model.equipment.horses");

        Map map = getPlayer().getGame().getMap();
        int olddefenders = 0;
        int defenders = 0;
        int threat = 0;
        int worstThreat = 0;
        Location bestTarget = null;
        Iterator<Unit> ui = colony.getTile().getUnitIterator();
        while (ui.hasNext()) {
            if ((ui.next()).isDefensiveUnit()) {
                defenders++;
            }
        }
        Iterator<Position> positionIterator = map.getCircleIterator(colony.getTile().getPosition(), true, 5);
        while (positionIterator.hasNext()) {
            Tile t = map.getTile(positionIterator.next());
            if (t.getFirstUnit() != null) {
                if (t.getFirstUnit().getOwner() == getPlayer()) {
                    Iterator<Unit> uit = t.getUnitIterator();
                    while (uit.hasNext()) {
                        if (uit.next().isOffensiveUnit()) {
                            defenders++;
                        }
                    }
                } else {
                    int thisThreat = 0;
                    if (getPlayer().getTension(t.getFirstUnit().getOwner()).getValue() >= Tension.TENSION_ADD_MAJOR) {
                        Iterator<Unit> uit = t.getUnitIterator();
                        while (uit.hasNext()) {
                            if (uit.next().isOffensiveUnit()) {
                                thisThreat += 2;
                            }
                        }
                    } else if (getPlayer().getTension(t.getFirstUnit().getOwner()).getValue() >= Tension.TENSION_ADD_MINOR) {
                        Iterator<Unit> uit = t.getUnitIterator();
                        while (uit.hasNext()) {
                            if (uit.next().isOffensiveUnit()) {
                                thisThreat++;
                            }
                        }
                    }
                    threat += thisThreat;
                    if (thisThreat > worstThreat) {
                        if (t.getSettlement() != null) {
                            bestTarget = t.getSettlement();
                        } else {
                            bestTarget = t.getFirstUnit();
                        }
                        worstThreat = thisThreat;
                    }
                }
            }
        }
        olddefenders = defenders;
        if (colony.hasStockade()) {
            defenders += (defenders * (colony.getStockade().getLevel()) / 2);
        }
        if (threat > defenders) {
            // We're under attack! Man the stockade!
            ArrayList<Unit> recruits = new ArrayList<Unit>();
            ArrayList<Unit> others = new ArrayList<Unit>();
            int inColonyCount = 0;
            // Let's make some more soldiers, if we can.
            // First, find some people we can recruit.
            ui = colony.getUnitIterator();
            while (ui.hasNext()) {
                Unit u = (ui.next());
                if (u.isOffensiveUnit()) {
                    continue; // don't bother dealing with current
                    // soldiers at the moment
                }
                if (u.getLocation() != colony.getTile()) {
                    // If we are not on the tile we are in the colony.
                    inColonyCount++;
                }
                if (u.hasAbility(Ability.EXPERT_SOLDIER)) {
                    recruits.add(u);
                } else if (u.hasAbility(Ability.CAN_BE_EQUIPPED)) {
                    others.add(u);
                }
            }
            // ATTENTION: skill may be Integer.MIN_VALUE!
            Collections.sort(others, Unit.getSkillLevelComparator());
            recruits.addAll(others);
            // Don't overdo it - leave at least one person behind.
            int recruitCount = threat - defenders;
            if (recruitCount > recruits.size() - 1) {
                recruitCount = recruits.size() - 1;
            }
            if (recruitCount > inColonyCount - 1) {
                recruitCount = inColonyCount - 1;
            }
            // Actually go through and arm our people.
            boolean needMuskets = false;
            boolean needHorses = false;
            ui = recruits.iterator();
            while (ui.hasNext() && recruitCount > 0) {
                Unit u = (ui.next());
                if (!u.isArmed() && u.canBeEquippedWith(muskets)) {
                    recruitCount--;
                    Element equipUnitElement = Message.createNewRootElement("equipUnit");
                    equipUnitElement.setAttribute("unit", u.getId());
                    equipUnitElement.setAttribute("type", muskets.getId());
                    equipUnitElement.setAttribute("amount", "1");
                    u.equipWith(muskets);
                    sendAndWaitSafely(equipUnitElement);
                    Element putOutsideColonyElement = Message.createNewRootElement("putOutsideColony");
                    putOutsideColonyElement.setAttribute("unit", u.getId());
                    u.putOutsideColony();
                    sendAndWaitSafely(putOutsideColonyElement);
                    // Check if the unit can fortify before sending the order
                    if (u.checkSetState(UnitState.FORTIFYING)) {
                        Element changeStateElement = Message.createNewRootElement("changeState");
                        changeStateElement.setAttribute("unit", u.getId());
                        changeStateElement.setAttribute("state", UnitState.FORTIFYING.toString());
                        sendAndWaitSafely(changeStateElement);
                    }
                    olddefenders++;
                    if (!u.isMounted() && u.canBeEquippedWith(horses)) {
                        equipUnitElement = Message.createNewRootElement("equipUnit");
                        equipUnitElement.setAttribute("unit", u.getId());
                        equipUnitElement.setAttribute("type", horses.getId());
                        equipUnitElement.setAttribute("amount", "1");
                        sendAndWaitSafely(equipUnitElement);
                    } else {
                        needHorses = true;
                    }
                } else {
                    needMuskets = true;
                    break;
                }
            }
            AIColony ac = null;
            if (needMuskets || needHorses) {
                Iterator<AIColony> aIterator = getAIColonyIterator();
                while (aIterator.hasNext()) {
                    AIColony temp = aIterator.next();
                    if (temp != null && temp.getColony() == colony) {
                        ac = temp;
                        break;
                    }
                }
            }
            if (needMuskets && ac != null) {
                // Check and see if we have already made a GoodsWish for
                // here.
                Iterator<Wish> wishes = ac.getWishIterator();
                boolean made = false;
                while (wishes.hasNext()) {
                    Wish w = wishes.next();
                    if (!(w instanceof GoodsWish)) {
                        continue;
                    }
                    GoodsWish gw = (GoodsWish) w;
                    if (gw.getGoodsType() == musketType) {
                        made = true;
                    }
                }
                if (made == false) {
                    // Add a new GoodsWish onto the stack.
                    ac
                            .addGoodsWish(new GoodsWish(getAIMain(), colony, (threat - olddefenders) * 50,
                                    musketType));
                }
            }
            if (needHorses && ac != null) {
                // Check and see if we have already made a GoodsWish for
                // here.
                Iterator<Wish> wishes = ac.getWishIterator();
                boolean made = false;
                while (wishes.hasNext()) {
                    Wish w = wishes.next();
                    if (!(w instanceof GoodsWish)) {
                        continue;
                    }
                    GoodsWish gw = (GoodsWish) w;
                    if (gw.getGoodsType() == horsesType) {
                        made = true;
                    }
                }
                if (made == false) {
                    // Add a new GoodsWish onto the stack.
                    ac.addGoodsWish(new GoodsWish(getAIMain(), colony, (threat - defenders) * 50, horsesType));
                }
            }
            defenders = olddefenders;
            if (colony.hasStockade()) {
                defenders += (defenders * (colony.getStockade().getLevel()) / 2);
            }
        }
        if (defenders > (threat * 2)) {
            // We're so big and tough, we can go wipe out this threat.
            // Pick someone to go make it happen.
            Unit u = null;
            Iterator<Unit> uit = colony.getUnitIterator();
            while (uit.hasNext()) {
                Unit candidate = uit.next();
                if (candidate.isOffensiveUnit() && candidate.getState() == UnitState.FORTIFIED) {
                    u = candidate;
                    break;
                }
            }
            if (u != null) {
                u.setState(UnitState.ACTIVE);
                u.setLocation(colony.getTile());
                AIUnit newDefenderAI = (AIUnit) getAIMain().getAIObject(u);
                if (bestTarget != null) {
                    newDefenderAI.setMission(new UnitSeekAndDestroyMission(getAIMain(), newDefenderAI, bestTarget));
                } else {
                    newDefenderAI.setMission(new UnitWanderHostileMission(getAIMain(), newDefenderAI));
                }
            }
        }
    }
    */

    private List<AIUnit>getPlayerPioneers() {
        List<AIUnit> pioneers = new ArrayList<AIUnit>();
        AIMain aiMain = getAIMain();
        for (Unit u : getPlayer().getUnits()) {
            AIUnit aiu = aiMain.getAIUnit(u);
            if (aiu == null) continue;
            if (aiu.getMission() instanceof PioneeringMission) {
                pioneers.add(aiu);
            }
        }
        return pioneers;
    }

    /**
     * Gives a mission to non-naval units.
     */
    protected void giveNormalMissions() {
        logger.finest("Entering method giveNormalMissions");

        // Create a datastructure for the worker wishes:
        java.util.Map<UnitType, ArrayList<Wish>> workerWishes =
            new HashMap<UnitType, ArrayList<Wish>>();
        for (UnitType unitType : getSpecification().getUnitTypeList()) {
            workerWishes.put(unitType, new ArrayList<Wish>());
        }
        for (Wish w : getWishes()) {
            if (w instanceof WorkerWish && w.getTransportable() == null) {
                workerWishes.get(((WorkerWish) w).getUnitType()).add(w);
            }
        }

        final boolean fewColonies = hasFewColonies();
        boolean isPioneerReq = getPlayerPioneers().size() == 0;
        Iterator<AIUnit> aiUnitsIterator = getAIUnitIterator();
        while (aiUnitsIterator.hasNext()) {
            AIUnit aiUnit = aiUnitsIterator.next();
            if (aiUnit.hasMission()) continue;

            Unit unit = aiUnit.getUnit();
            if (unit.isUninitialized()) {
                logger.warning("Unit is uninitialized: " + unit.getId());
                continue;
            }

            if (unit.isDisposed()) continue;

            if (unit.getState() == UnitState.IN_COLONY
                && unit.getSettlement().getUnitCount() <= 1) {
                // The unit has its hand full keeping the colony alive.
                continue;
            }

            if (PrivateerMission.isValid(aiUnit)) {
                aiUnit.setMission(new PrivateerMission(getAIMain(), aiUnit));
                continue;
            }
            /* TODO: we need a mission for frigates and similar
             * units to patrol enemy shipping lanes
             */

            if (unit.isCarrier()) {
                aiUnit.setMission(new TransportMission(getAIMain(), aiUnit));
                continue;
            }

            if (unit.canCarryTreasure()) {
                aiUnit.setMission(new CashInTreasureTrainMission(getAIMain(), aiUnit));
                continue;
            }

            if (ScoutingMission.isValid(aiUnit)) {
                aiUnit.setMission(new ScoutingMission(getAIMain(), aiUnit));
                continue;
            }

            if (unit.isOffensiveUnit() || unit.isDefensiveUnit()){
                Player owner = unit.getOwner();
                boolean isPastStart = getGame().getTurn().getNumber() > 5
                    && !owner.getSettlements().isEmpty();

                if (!unit.isColonist() || isPastStart) {
                    giveMilitaryMission(aiUnit);
                    continue;
                }
            }

            // Setup as a pioneer if unit:
            // - already has tools
            // - an expert pioneer
            // - a non-expert unit and there are no other units assigned as pioneers
            boolean isPioneer = unit.hasAbility("model.ability.improveTerrain")
                                || unit.hasAbility(Ability.EXPERT_PIONEER);
            boolean isExpert = unit.getSkillLevel() > 0;
            if (isPioneer || (isPioneerReq && !isExpert)) {
                if (PioneeringMission.isValid(aiUnit)) {
                    aiUnit.setMission(new PioneeringMission(getAIMain(), aiUnit));
                    isPioneerReq = false;
                    continue;
                }
            }

            if (unit.isColonist()) {
                giveColonistMission(aiUnit, fewColonies, workerWishes);
            }

            if (!aiUnit.hasMission()) {
                if (aiUnit.getUnit().isOffensiveUnit()) {
                    aiUnit.setMission(new UnitWanderHostileMission(getAIMain(), aiUnit));
                } else {
                    //non-offensive units should take shelter in a nearby colony,
                    //not try to be hostile
                    aiUnit.setMission(new IdleAtColonyMission(getAIMain(), aiUnit));
                }
            }
        }
    }

    private void giveColonistMission(AIUnit aiUnit, boolean fewColonies,
                                     java.util.Map<UnitType, ArrayList<Wish>> workerWishes) {
        final Unit unit = aiUnit.getUnit();
        /*
         * Motivated by (speed) performance: This map stores the
         * distance between the unit and the destination of a Wish:
         */
        HashMap<Location, Integer> distances = new HashMap<Location, Integer>(121);
        for (ArrayList<Wish> al : workerWishes.values()) {
            for (Wish w : al) {
                if (w.getDestination() != null
                    && !distances.containsKey(w.getDestination())) {
                    distances.put(w.getDestination(), 
                        ((w.getDestination() == unit.getLocation()) ? 0
                            : unit.getTurnsToReach(w.getDestination())));
                }
            }
        }

        // Check if this unit is needed as an expert (using:
        // "WorkerWish"):
        ArrayList<Wish> wishList = workerWishes.get(unit.getType());
        WorkerWish bestWish = null;
        int bestTurns = Integer.MAX_VALUE;
        for (int i = 0; i < wishList.size(); i++) {
            // TODO: is this necessary? If so, use Iterator?
            WorkerWish ww = (WorkerWish) wishList.get(i);
            if (ww.getTransportable() != null) {
                wishList.remove(i);
                i--;
                continue;
            }
            int turns = getScaledTurns(distances, ww.getDestination());
            if (bestWish == null
                || ww.getValue() - (turns * 2) > bestWish.getValue() - (bestTurns * 2)) {
                bestWish = ww;
                bestTurns = turns;
            }
        }
        if (bestWish != null) {
            bestWish.setTransportable(aiUnit);
            aiUnit.setMission(new WishRealizationMission(getAIMain(), aiUnit, bestWish));
            return;
        }
        // Find a site for a new colony:
        Tile colonyTile = null;
        if (getPlayer().canBuildColonies()
            && BuildColonyMission.isValid(aiUnit)) {
            colonyTile = BuildColonyMission.findTargetTile(aiUnit, false);
            if (colonyTile != null) {
                bestTurns = unit.getTurnsToReach(colonyTile);
            }
        }

        // Check if we can find a better site to work than a new colony:
        if (!fewColonies || colonyTile == null || bestTurns > 10) {
            for (List<Wish> wishes : workerWishes.values()) {
                for (int j = 0; j < wishes.size(); j++) {
                    WorkerWish ww = (WorkerWish) wishes.get(j);
                    if (ww.getTransportable() != null) {
                        wishes.remove(j);
                        j--;
                        continue;
                    }
                    int turns = getScaledTurns(distances, ww.getDestination());
                    // TODO: Choose to build colony if the value of the
                    // wish is low.
                    if (bestWish == null
                        || ww.getValue() - (turns * 2) > bestWish.getValue() - (bestTurns * 2)) {
                        bestWish = ww;
                        bestTurns = turns;
                    }
                }
            }
        }
        if (bestWish != null) {
            bestWish.setTransportable(aiUnit);
            aiUnit.setMission(new WishRealizationMission(getAIMain(), aiUnit, bestWish));
            return;
        }
        // Choose to build a new colony:
        if (colonyTile != null) {
            aiUnit.setMission(new BuildColonyMission(getAIMain(), aiUnit,
                                                     colonyTile));
            boolean isUnitOnCarrier = aiUnit.getUnit().isOnCarrier();
            if (isUnitOnCarrier) {
                // Verify carrier mission
                AIUnit carrier = getAIUnit((Unit) aiUnit.getUnit()
                    .getLocation());
                Mission carrierMission = carrier.getMission();
                if (carrierMission == null) {
                    carrierMission = new TransportMission(getAIMain(), carrier);
                    carrier.setMission(carrierMission);
                } else if (!(carrierMission instanceof TransportMission)) {
                    throw new IllegalStateException("Carrier carrying unit not on a transport mission");
                }
                // Transport unit to carrier destination (is this what
                // is truly wanted?)
                ((TransportMission) carrierMission).addToTransportList(aiUnit);
            }
            return;
        }
    }

    private int getScaledTurns(java.util.Map<Location, Integer> distances, Location destination) {
        int turns = distances.get(destination);
        // TODO: what do these calcuations mean?
        if (turns == Integer.MAX_VALUE) {
            return (destination.getTile() == null) ? 5 : 10;
        } else {
            return Math.min(5, turns);
        }
    }

    /**
     * Brings gifts to nice players with nearby colonies.
     *
     * TODO: European players can also bring gifts! However,
     * this might be folded into a trade mission, since
     * European gifts are just a special case of trading.
     */
    private void bringGifts() {
        return;
    }

    /**
     * Demands goods from players with nearby colonies.
     *
     * TODO: European players can also demand tribute!
     */
    private void demandTribute() {
        return;
    }

    /**
     * Calls {@link AIColony#createAIGoods()} for every colony this player owns.
     */
    private void createAIGoodsInColonies() {
        logger.finest("Entering method createAIGoodsInColonies");
        for (AIColony aic : getAIColonies()) aic.createAIGoods();
    }

    /**
     * Counts the number of defenders in a colony.
     *
     * @param colony The <code>Colony</code> to examine.
     * @return The number of defenders.
     */
    protected int getColonyDefenders(Colony colony) {
        int defenders = 0;
        for (AIUnit au : getAIUnits()) {
            Mission m = au.getMission();
            if (m != null && m instanceof DefendSettlementMission
                && ((DefendSettlementMission) m).getSettlement() == colony
                && au.getUnit().getColony() == colony) {
                defenders++;
            }
        }
        return defenders;
    }

    /**
     * Evaluate allocating a unit to the defence of a colony.
     * Temporary helper method for giveMilitaryMission.
     *
     * @param unit The <code>Unit</code> that is to defend.
     * @param colony The <code>Colony</code> to defend.
     * @param turns The turns for the unit to reach the colony.
     * @return A value for such a mission.
     */
    public int getDefendColonyMissionValue(Unit unit, Colony colony,
                                           int turns) {
        // Sanitation
        if (unit == null || colony == null) return Integer.MIN_VALUE;

        int value = 10025 - 10 * turns;
        int defenders = getColonyDefenders(colony);

        // Reduce value in proportion to the number of defenders.
        value -= 500 * defenders;

        // Reduce value if defenders are protected.
        if (colony.hasStockade()) {
            if (defenders > colony.getStockade().getLevel() + 1) {
                value = 20 - defenders;
            } else {
                value -= 1000 * colony.getStockade().getLevel();
            }
        }

        // Do not return negative except for error cases.
        return Math.max(0, value);
    }

    /**
     * Evaluate a potential seek and destroy mission for a given unit
     * to a given tile.
     * TODO: revisit and rebalance the mass of magic numbers.
     *
     * @param unit The <code>Unit</code> to do the mission.
     * @param newTile The <code>Tile</code> to go to.
     * @param turns How long to travel to the tile.
     * @return A score for the proposed mission.
     */
    public int getUnitSeekAndDestroyMissionValue(Unit unit, Tile newTile, int turns) {
        if (!isTargetValidForSeekAndDestroy(unit, newTile)) {
            return Integer.MIN_VALUE;
        }
        Settlement settlement = newTile.getSettlement();
        Unit defender = newTile.getDefendingUnit(unit);
        // Take distance to target into account
        int value = 10020 - turns * 100;

        if (settlement != null) {
            // Do not cheat and look inside the settlement.
            // Just use visible facts about it.
            // TODO: if we are the REF and there is a significant Tory
            // population inside, assume traitors have briefed us.
            if (settlement instanceof Colony) {
                // Favour high population and weak fortifications.
                Colony colony = (Colony) settlement;
                value += 50 * colony.getUnitCount();
                if (colony.hasStockade()) {
                    value -= 1000 * colony.getStockade().getLevel();
                }
            } else if (settlement instanceof IndianSettlement) {
                // Favour the most hostile settlements
                IndianSettlement is = (IndianSettlement) settlement;
                Tension tension = is.getAlarm(unit.getOwner());
                if (tension != null) value += tension.getValue();
            }
        } else if (defender != null) {
            CombatModel combatModel = unit.getGame().getCombatModel();
            float off = combatModel.getOffencePower(unit, defender);
            float def = combatModel.getDefencePower(unit, defender);

            Unit train = getBestTreasureTrain(newTile);
            if (train != null) {
                value += Math.min(train.getTreasureAmount() / 10, 50);
            }

            if (defender.getType().getOffence() > 0) {
                value += 200 - def * 2 - turns * 50;
            }

            value += combatModel.getOffencePower(defender, unit)
                - combatModel.getDefencePower(defender, unit);

            if (!defender.isNaval()
                && defender.hasAbility(Ability.EXPERT_SOLDIER)
                && !defender.isArmed()) {
                value += 10 - def * 2 - turns * 25;
            }
            if (value < 0) value = 0;
        }
        logger.finest("getUnitSeekAndDestroyMissionValue " + unit.getId()
                      + " v " + ((settlement != null) ? settlement.getId()
                                 : (defender != null) ? defender.getId()
                                 : "none")
                      + " = " + value);
        return value;
    }

    /**
     * Chooses the best target for an AI unit to attack.
     *
     * @param aiUnit The <code>AIUnit</code> that will attack.
     * @return The best target for a military mission.
     */
    public Ownable chooseMilitaryTarget(AIUnit aiUnit) {
        final AIMain aiMain = getAIMain();

        final Unit unit = aiUnit.getUnit();
        final Unit carrier = (!unit.isOnCarrier()) ? null
            : (Unit) unit.getLocation();
        Ownable bestTarget = null;         // The best target for a mission.
        int bestValue = Integer.MIN_VALUE; // The value of the target above.

        // Determine starting tile.
        Tile startTile = ((carrier != null) ? carrier : unit).getTile();
        if (startTile == null) {
            startTile = ((carrier != null) ? carrier : unit)
                .getFullEntryLocation();
        }
        if (startTile == null) {
            logger.warning("chooseMilitaryTarget failed, no tile for: "
                           + unit);
            return null;
        }

        // Check if the unit is located on a Tile with a
        // Settlement which requires defenders.
        if (unit.getColony() != null) {
            bestTarget = unit.getColony();
            bestValue = getDefendColonyMissionValue(unit,
                                                    (Colony) bestTarget, 0);
        }

        // Checks if a nearby colony requires additional defence:
        GoalDecider gd = new GoalDecider() {
                private PathNode best = null;

                private int bestValue = 0;

                public PathNode getGoal() {
                    return best;
                }

                public boolean hasSubGoals() {
                    return true;
                }

                private int getValue(PathNode pathNode) {
                    // Note: check *unit*, not *u*, which could be
                    // the carrier unit is on.
                    Colony colony = pathNode.getTile().getColony();
                    return (colony == null
                            || colony.getOwner() != unit.getOwner())
                        ? Integer.MIN_VALUE
                        : getDefendColonyMissionValue(unit, colony,
                                                      pathNode.getTurns());
                }

                public boolean check(Unit u, PathNode pathNode) {
                    int value = getValue(pathNode);
                    if (value > bestValue) {
                        bestValue = value;
                        best = pathNode;
                        return true;
                    }
                    return false;
                }
            };
        final int MAXIMUM_TURNS_TO_SETTLEMENT = 10;
        PathNode bestPath = unit.search(startTile, gd,
                                        CostDeciders.avoidIllegal(),
                                        MAXIMUM_TURNS_TO_SETTLEMENT,
                                        carrier);
        if (bestPath != null) {
            PathNode last = bestPath.getLastNode();
            Colony colony = last.getTile().getColony();
            int value = getDefendColonyMissionValue(unit, colony,
                                                    last.getTurns());
            if (value > bestValue) {
                bestTarget = colony;
                bestValue = value;
            }
        }

        // Search for the closest target of a pre-existing
        // UnitSeekAndDestroyMission.
        Location bestExistingTarget = null;
        int bestDistance = Integer.MAX_VALUE;
        for (AIUnit au : getAIUnits()) {
            Unit u = au.getUnit();
            if (u.getTile() == null
                || !(au.getMission() instanceof UnitSeekAndDestroyMission))
                continue;
            UnitSeekAndDestroyMission usdm
                = (UnitSeekAndDestroyMission) au.getMission();
            int distance = unit.getTurnsToReach(startTile,
                                                usdm.getTarget().getTile());
            if (distance < bestDistance) {
                bestExistingTarget = usdm.getTarget();
                bestDistance = distance;
            }
        }
        if (bestExistingTarget != null) {
            int value = getUnitSeekAndDestroyMissionValue(unit,
                                                          bestExistingTarget.getTile(), bestDistance);
            if (value > bestValue) {
                bestValue = value;
                bestTarget = (Ownable) bestExistingTarget;
            }
        }

        // General check for available targets for seek-and-destroy.
        GoalDecider targetDecider = new GoalDecider() {
                private PathNode bestTarget = null;

                private int bestNewTargetValue = Integer.MIN_VALUE;

                public PathNode getGoal() {
                    return bestTarget;
                }

                public boolean hasSubGoals() {
                    return true;
                }

                private int getValue(PathNode pathNode) {
                    // Note: check *unit*, not *u*, which could be
                    // the carrier unit is on.
                    Tile tile = pathNode.getTile();
                    return (isTargetValidForSeekAndDestroy(unit, tile))
                        ? getUnitSeekAndDestroyMissionValue(unit, tile,
                                                            pathNode.getTurns())
                        : Integer.MIN_VALUE;
                }

                public boolean check(Unit u, PathNode pathNode) {
                    int value = getValue(pathNode);
                    if (value > bestNewTargetValue) {
                        bestTarget = pathNode;
                        bestNewTargetValue = value;
                        return true;
                    }
                    return false;
                }
            };
        PathNode newTarget = unit.search(startTile, targetDecider,
                                         CostDeciders.avoidIllegal(),
                                         INFINITY, carrier);
        if (newTarget != null) {
            Tile targetTile = newTarget.getLastNode().getTile();
            int value = getUnitSeekAndDestroyMissionValue(unit,
                                                          targetTile, newTarget.getTotalTurns());
            if (value > bestValue) {
                bestValue = value;
                if (targetTile.getSettlement() != null) {
                    bestTarget = targetTile.getSettlement();
                } else if (getBestTreasureTrain(targetTile) != null) {
                    bestTarget = getBestTreasureTrain(targetTile);
                } else {
                    bestTarget = targetTile.getDefendingUnit(unit);
                }
            }
        }
        return (bestValue > 0) ? bestTarget : null;
    }

    /**
     * Gives a military mission to the given unit.
     *
     * Old comment: Temporary method for giving a military mission.
     * This method will be removed when "MilitaryStrategy" and the
     * "Tactic"-classes has been implemented.
     *
     * @param aiUnit The <code>AIUnit</code> to give a mission to.
     */
    public void giveMilitaryMission(AIUnit aiUnit) {
        logger.finest("Entering giveMilitaryMission for: " + aiUnit.getUnit());

        final AIMain aiMain = getAIMain();
        final Player player = getPlayer();
        Mission mission;
        Ownable bestTarget = chooseMilitaryTarget(aiUnit);
        if (bestTarget == null) {
            // Just wander around if there is nothing better to do.
            mission = new UnitWanderHostileMission(aiMain, aiUnit);
        } else if (bestTarget instanceof Settlement
            && ((Settlement) bestTarget).getOwner() == player) {
            Settlement settlement = (Settlement) bestTarget;
            mission = new DefendSettlementMission(aiMain, aiUnit, settlement);
        } else {
            mission = new UnitSeekAndDestroyMission(aiMain, aiUnit,
                (Location) bestTarget);
        }

        aiUnit.setMission(mission);
        logger.finest("giveMilitaryMission found: " + mission
                      + " for unit: " + aiUnit);
    }

    private int getTurns(Transportable t, TransportMission tm) {
        if (t.getTransportDestination() != null
            && t.getTransportDestination().getTile()
            == tm.getUnit().getTile()) {
            return 0;
        }
        PathNode path = tm.getTransportPath(t);
        return (path == null) ? -1 : path.getTotalTurns();
    }

    /**
     * Assign transportable units and goods to available carriers
     * transport lists.
     */
    private void createTransportLists() {
        logger.finest("Entering method createTransportLists");
        if (!getPlayer().isEuropean()) return;
        List<Transportable> transportables = new ArrayList<Transportable>();

        // Collect non-naval units needing transport.
        for (AIUnit au : getAIUnits()) {
            if (!au.getUnit().isNaval()
                && au.getTransport() == null
                && au.getTransportDestination() != null) {
                transportables.add(au);
            }
        }

        // Add goods to transport
        for (AIColony aic : getAIColonies()) {
            for (AIGoods aig : aic.getAIGoods()) {
                if (aig.getTransportDestination() != null
                    && aig.getTransport() == null) {
                    transportables.add(aig);
                }
            }
        }

        // Update the priority.
        for (Transportable t : transportables) {
            t.increaseTransportPriority();
        }

        // Order the transportables by priority.
        Collections.sort(transportables, new Comparator<Transportable>() {
            public int compare(Transportable o1, Transportable o2) {
                if (o1 == o2) return 0;
                int result = o2.getTransportPriority() - o1.getTransportPriority();
                return (result == 0) ? o1.getId().compareTo(o2.getId())
                    : result;
            }
        });

        // Collect current transport missions with space available.
        ArrayList<TransportMission> availableMissions
            = new ArrayList<TransportMission>();
        for (AIUnit au : getAIUnits()) {
            if (au.hasMission() && au.getMission() instanceof TransportMission
                && au.getUnit().getSpaceLeft() > 0) {
                availableMissions.add((TransportMission) au.getMission());
            }
        }

        // If a transportable is already on a carrier, just add it to
        // its transport list.  Note however that it may not be
        // possible to complete such transport, in which case, the
        // carrier should dump the transportable in the nearest
        // colony.
        for (Transportable t : new ArrayList<Transportable>(transportables)) {
            Location transportableLoc = t.getTransportLocatable().getLocation();
            boolean onCarrier = transportableLoc instanceof Unit;
            if (onCarrier) {
                AIUnit carrierAI = getAIUnit((Unit) transportableLoc);
                Mission m = carrierAI.getMission();
                if (m instanceof TransportMission) {
                    ((TransportMission) m).addToTransportList(t);
                }
                transportables.remove(t);
            }
        }

        // For all remaining transportables, find the best carrier.
        // That is the one with space available that is closest.
        // TODO: this concentrates on packing, but ignores destinations
        // which is definitely going to be inefficient
        // TODO: be smarter about removing bestTransport from
        // availableMissions when it is full.
        while (!transportables.isEmpty() && !availableMissions.isEmpty()) {
            Transportable t = transportables.remove(0);
            TransportMission bestTransport = null;
            int bestTransportSpace = 0;
            int bestTransportTurns = Integer.MAX_VALUE;
            for (TransportMission tm : availableMissions) {
                int transportSpace = tm.getAvailableSpace(t);
                if (transportSpace <= 0) continue;
                if (t instanceof AIUnit) {
                    if (!tm.getUnit().canCarryUnits()) continue;
                } else if (t instanceof AIGoods) {
                    if (!tm.getUnit().canCarryGoods()) continue;
                }                    
                if (t.getTransportSource() != null
                    && (t.getTransportSource().getTile()
                        == tm.getUnit().getTile())) {
                    if (bestTransportTurns > 0
                        || (bestTransportTurns == 0
                            && transportSpace > bestTransportSpace)) {
                        bestTransport = tm;
                        bestTransportSpace = transportSpace;
                        bestTransportTurns = 0;
                    }
                    continue;
                }
                int totalTurns = getTurns(t, tm);
                if (totalTurns <= 0) continue;
                if (totalTurns < bestTransportTurns
                    || (totalTurns == bestTransportTurns
                        && transportSpace > bestTransportSpace)) {
                    bestTransport = tm;
                    bestTransportSpace = transportSpace;
                    bestTransportTurns = totalTurns;
                }
            }
            if (bestTransport == null) {
                logger.finest("Transport failed for: " + t);
                continue;
            }
            bestTransport.addToTransportList(t);
            logger.finest("Transport found for: " + t
                          + " using: " + bestTransport
                          + "  unit: " + bestTransport.getUnit());

            // See if any other transportables are present at the same
            // location and can fit.
            for (int i = 0; i < transportables.size(); i++) {
                Transportable t2 = transportables.get(i);
                if (t2.getTransportLocatable().getLocation()
                    == t.getTransportLocatable().getLocation()
                    && bestTransport.getAvailableSpace(t2) > 0) {
                    transportables.remove(t2);
                    bestTransport.addToTransportList(t2);
                    logger.finest("Transport piggyback for: " + t2
                                  + " using: " + t);
                }
            }
        }
    }

    /**
     * Finds the treasure train carrying the largest treasure located
     * on the given <code>Tile</code>.
     *
     * @param tile The <code>Tile</code> to check.
     * @return The best treasure train found or null if none present.
     */
    private Unit getBestTreasureTrain(Tile tile) {
        Unit best = null;
        for (Unit unit : tile.getUnitList()) {
            if (unit.canCarryTreasure()
                && (best == null
                    || best.getTreasureAmount() < unit.getTreasureAmount())) {
                best = unit;
            }
        }
        return best;
    }

}
