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

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.freecol.FreeCol;
import net.sf.freecol.client.gui.i18n.Messages;
import net.sf.freecol.common.model.Ability;
import net.sf.freecol.common.model.AbstractGoods;
import net.sf.freecol.common.model.AbstractUnit;
import net.sf.freecol.common.model.BuildableType;
import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.ColonyTile;
import net.sf.freecol.common.model.CombatModel.CombatResult;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.DiplomaticTrade.TradeStatus;
import net.sf.freecol.common.model.EquipmentType;
import net.sf.freecol.common.model.Europe;
import net.sf.freecol.common.model.Europe.MigrationType;
import net.sf.freecol.common.model.ExportData;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.FreeColGameObject;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.GameOptions;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.GoodsContainer;
import net.sf.freecol.common.model.GoodsType;
import net.sf.freecol.common.model.HighScore;
import net.sf.freecol.common.model.HighSeas;
import net.sf.freecol.common.model.HistoryEvent;
import net.sf.freecol.common.model.IndianNationType;
import net.sf.freecol.common.model.IndianSettlement;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Market;
import net.sf.freecol.common.model.Market.Access;
import net.sf.freecol.common.model.ModelMessage;
import net.sf.freecol.common.model.Monarch;
import net.sf.freecol.common.model.Monarch.MonarchAction;
import net.sf.freecol.common.model.Nameable;
import net.sf.freecol.common.model.Nation;
import net.sf.freecol.common.model.NationSummary;
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.RandomRange;
import net.sf.freecol.common.model.Region;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Specification;
import net.sf.freecol.common.model.StringTemplate;
import net.sf.freecol.common.model.Tension;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.TileImprovement;
import net.sf.freecol.common.model.TileImprovementType;
import net.sf.freecol.common.model.TradeItem;
import net.sf.freecol.common.model.TradeRoute;
import net.sf.freecol.common.model.TradeRoute.Stop;
import net.sf.freecol.common.model.Turn;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.Unit.Role;
import net.sf.freecol.common.model.Unit.UnitState;
import net.sf.freecol.common.model.UnitType;
import net.sf.freecol.common.model.UnitTypeChange;
import net.sf.freecol.common.model.UnitTypeChange.ChangeType;
import net.sf.freecol.common.model.WorkLocation;
import net.sf.freecol.common.networking.ChatMessage;
import net.sf.freecol.common.networking.ChooseFoundingFatherMessage;
import net.sf.freecol.common.networking.Connection;
import net.sf.freecol.common.networking.DOMMessage;
import net.sf.freecol.common.networking.DiplomacyMessage;
import net.sf.freecol.common.networking.GoodsForSaleMessage;
import net.sf.freecol.common.networking.IndianDemandMessage;
import net.sf.freecol.common.networking.LootCargoMessage;
import net.sf.freecol.common.networking.MonarchActionMessage;
import net.sf.freecol.common.util.Introspector;
import net.sf.freecol.common.util.RandomChoice;
import net.sf.freecol.common.util.Utils;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.ai.AIPlayer;
import net.sf.freecol.server.ai.REFAIPlayer;
import net.sf.freecol.server.control.ChangeSet.ChangePriority;
import net.sf.freecol.server.control.ChangeSet.See;
import net.sf.freecol.server.model.DiplomacySession;
import net.sf.freecol.server.model.LootSession;
import net.sf.freecol.server.model.ServerColony;
import net.sf.freecol.server.model.ServerEurope;
import net.sf.freecol.server.model.ServerGame;
import net.sf.freecol.server.model.ServerIndianSettlement;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.model.ServerUnit;
import net.sf.freecol.server.model.TradeSession;
import net.sf.freecol.server.model.TransactionSession;

import org.w3c.dom.Element;


/**
 * The main server controller.
 */
public final class InGameController extends Controller {

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

    // TODO: options, spec?
    // Alarm adjustments.
    public static final int ALARM_NEW_MISSIONARY = -100;

    // Score bonus on declaration of independence.
    public static final int SCORE_INDEPENDENCE_DECLARED = 100;

    // Score bonus on achieving independence.
    public static final int SCORE_INDEPENDENCE_GRANTED = 1000;

    // The server random number source.
    private final Random random;

    // Debug helpers
    private int debugOnlyAITurns = 0;
    private MonarchAction debugMonarchAction = null;
    private ServerPlayer debugMonarchPlayer = null;

 
    /**
     * The constructor to use.
     * 
     * @param freeColServer The main server object.
     * @param random The pseudo-random number source to use.
     */
    public InGameController(FreeColServer freeColServer, Random random) {
        super(freeColServer);

        this.random = random;
    }

    /**
     * Gets the number of AI turns to skip through.
     *
     * @return The number of terms to skip.
     */
    public int getSkippedTurns() {
        return (FreeCol.isInDebugMode()) ? debugOnlyAITurns : -1;
    }

    /**
     * Sets the number of AI turns to skip through as a debug helper.
     *
     * @param turns The number of turns to skip through.
     */
    public void setSkippedTurns(int turns) {
        if (FreeCol.isInDebugMode()) {
            debugOnlyAITurns = turns;
        }
    }

    /**
     * Sets a monarch action to debug/test.
     *
     * @param player The <code>Player</code> whose monarch should act.
     * @param action The <code>MonarchAction</code> to be taken.
     */
    public void setMonarchAction(Player player, MonarchAction action) {
        if (FreeCol.isInDebugMode()) {
            debugMonarchPlayer = (ServerPlayer) player;
            debugMonarchAction = action;
        }
    }

    /**
     * Debug convenience to step the random number generator.
     *
     * @return The next random number in series, in the range 0-99.
     */
    public int stepRandom() {
        return Utils.randomInt(logger, "step random", random, 100);
    }

    /**
     * Public version of the yearly goods adjust (public so it can be
     * use in the Market test code).  Sends the market and change
     * messages to the player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> whose market
     *            is to be updated.
     */
    public void yearlyGoodsAdjust(ServerPlayer serverPlayer) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csYearlyGoodsAdjust(random, cs);
        sendElement(serverPlayer, cs);
    }

    /**
     * Public version of csAddFoundingFather so it can be used in the
     * test code and DebugMenu.
     *
     * @param serverPlayer The <code>ServerPlayer</code> who gains a father.
     * @param father The <code>FoundingFather</code> to add.
     */
    public void addFoundingFather(ServerPlayer serverPlayer,
                                  FoundingFather father) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csAddFoundingFather(father, random, cs);
        sendElement(serverPlayer, cs);
    }

    /**
     * Public change stance and inform all routine.  Mostly used in the
     * test suite, but the AIs also call it.
     *
     * @param player The originating <code>Player</code>.
     * @param stance The new <code>Stance</code>.
     * @param otherPlayer The <code>Player</code> wrt which the stance changes.
     * @param symmetric If true, change the otherPlayer stance as well.
     */
    public void changeStance(Player player, Stance stance,
                             Player otherPlayer, boolean symmetric) {
        ChangeSet cs = new ChangeSet();
        ServerPlayer serverPlayer = (ServerPlayer) player;
        if (serverPlayer.csChangeStance(stance, otherPlayer, symmetric, cs)) {
            sendToAll(cs);
        }
    }

    /**
     * Gets a nation summary.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is querying.
     * @param player The <code>Player</code> to summarize.
     * @return An <code>Element</code> encapsulating this action.
     */
    public NationSummary getNationSummary(ServerPlayer serverPlayer,
                                          Player player) {
        return new NationSummary(player, serverPlayer);
    }

    /**
     * Move goods from current location to another.
     *
     * @param goods The <code>Goods</code> to move.
     * @param loc The new <code>Location</code>.
     */
    public void moveGoods(Goods goods, Location loc)
        throws IllegalStateException {
        Location oldLoc = goods.getLocation();
        if (oldLoc == null) {
            throw new IllegalStateException("Goods in null location.");
        } else if (loc == null) {
            ; // Dumping is allowed
        } else if (loc instanceof Unit) {
            if (((Unit) loc).isInEurope()) {
                if (!(oldLoc instanceof Unit && ((Unit) oldLoc).isInEurope())) {
                    throw new IllegalStateException("Goods and carrier not both in Europe.");
                }
            } else if (loc.getTile() == null) {
                throw new IllegalStateException("Carrier not on the map.");
            } else if (oldLoc instanceof Settlement) {
                // Can not be co-located when buying from natives, or
                // when natives are demanding goods from a colony.
            } else if (loc.getTile() != oldLoc.getTile()) {
                throw new IllegalStateException("Goods and carrier not co-located.");
            }
        } else if (loc instanceof IndianSettlement) {
            // Can not be co-located when selling to natives.
        } else if (loc instanceof Colony) {
            if (oldLoc instanceof Unit
                && ((Unit) oldLoc).getOwner() != ((Colony) loc).getOwner()) {
                // Gift delivery
            } else if (loc.getTile() != oldLoc.getTile()) {
                throw new IllegalStateException("Goods and carrier not both in Colony.");
            }
        } else if (loc.getGoodsContainer() == null) {
            throw new IllegalStateException("New location with null GoodsContainer.");
        }

        // Save state of the goods container/s, allowing simpler updates.
        oldLoc.getGoodsContainer().saveState();
        if (loc != null) loc.getGoodsContainer().saveState();

        oldLoc.remove(goods);
        goods.setLocation(null);

        if (loc != null) {
            loc.add(goods);
            goods.setLocation(loc);
        }
    }

    /**
     * Create the Royal Expeditionary Force player corresponding to
     * a given player that is about to rebel.
     * Public for the test suite.
     *
     * @param serverPlayer The <code>ServerPlayer</code> about to rebel.
     * @return The REF player.
     */
    public ServerPlayer createREFPlayer(ServerPlayer serverPlayer) {
        Nation refNation = serverPlayer.getNation().getRefNation();
        Monarch monarch = serverPlayer.getMonarch();
        ServerPlayer refPlayer = getFreeColServer().addAIPlayer(refNation);
        refPlayer.setEntryLocation(null); // Trigger initial placement routine
        Player.makeContact(serverPlayer, refPlayer); // Will change, setup only

        // Instantiate the REF in Europe
        List<Unit> landUnits
            = refPlayer.createUnits(monarch.getREFLandUnits());
        List<Unit> navalUnits
            = refPlayer.createUnits(monarch.getREFNavalUnits());
        List<Unit> unitsList = new ArrayList<Unit>();
        unitsList.addAll(navalUnits);
        unitsList.addAll(landUnits);

        // Embark the land units.  For all land units, find a naval
        // unit to carry it.  Fill greedily, so as if there is excess
        // naval capacity then the naval units at the end of the list
        // will tend to be empty or very lightly filled, allowing them
        // to defend the whole fleet at full strength against the
        // rebel navy.
        Collections.shuffle(navalUnits, random);
        Collections.shuffle(landUnits, random);
        for (Unit unit : landUnits) {
            for (Unit carrier : navalUnits) {
                if (unit.getSpaceTaken() <= carrier.getSpaceLeft()) {
                    unit.setLocation(carrier);
                    continue;
                }
            }
        }
        // Send the navy on its way
        for (Unit u : navalUnits) {
            u.setWorkLeft(1);
            u.setDestination(getGame().getMap());
            u.setLocation(u.getOwner().getHighSeas());
        }

        return refPlayer;
    }


    // Client-server communication utilities

    // A handler interface to pass to askFuture().
    // This will change from DOMMessage to Message when DOM goes away.
    private interface DOMMessageHandler {
        public DOMMessage handle(DOMMessage message);
    };

    private class DOMMessageCallable implements Callable<DOMMessage> {

        private Connection connection;
        private Game game;
        private DOMMessage message;
        private DOMMessageHandler handler;
        

        public DOMMessageCallable(Connection connection, Game game,
                                  DOMMessage message,
                                  DOMMessageHandler handler) {
            this.connection = connection;
            this.game = game;
            this.message = message;
            this.handler = handler;
        }
        
        public DOMMessage call() {
            Element reply;
            try {
                reply = connection.askDumping(message.toXMLElement());
            } catch (IOException e) {
                return null;
            }
            if (reply == null) return null;
            String tag = reply.getTagName();
            tag = "net.sf.freecol.common.networking." 
                + tag.substring(0, 1).toUpperCase() + tag.substring(1)
                + "Message";
            Class[] types = new Class[] { Game.class, Element.class };
            Object[] params = new Object[] { game, reply };
            DOMMessage message;
            try {
                message = (DOMMessage)Introspector.instantiate(tag, types,
                                                               params);
            } catch (IllegalArgumentException e) {
                logger.log(Level.WARNING, "Instantiation fail", e);
                message = null;
            }
            return (message == null) ? null : handler.handle(message);
        }
    };

    // A service to run the futures.
    private final ExecutorService executor = Executors.newCachedThreadPool();

    /**
     * Asks a question of a player in a Future.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to ask.
     * @param question The <code>DOMMessage</code> question.
     * @param handler The <code>DOMMessageHandler</code> handler to process
     *     the reply with.
     * @return A future encapsulating the result.
     */
    private Future<DOMMessage> askFuture(ServerPlayer serverPlayer,
                                         DOMMessage question,
                                         DOMMessageHandler handler) {
        Callable<DOMMessage> callable
            = new DOMMessageCallable(serverPlayer.getConnection(), getGame(),
                                     question, handler);
        return executor.submit(callable);
    }

    // A place to stash queries that need to be resolved at some point.
    private static final List<FutureQuery> outstandingQueries
        = new ArrayList<FutureQuery>();

    // Trivial way to associate a future with a runnable to resolve it.
    private class FutureQuery {

        public Future<DOMMessage> future;
        public Runnable runnable;

        public FutureQuery(Future<DOMMessage> future, Runnable runnable) {
            this.future = future;
            this.runnable = runnable;
            outstandingQueries.add(this);
        }
    };

    /**
     * Resolves and clears any outstanding queries.
     */
    public void resolveOutstandingQueries() {
        FutureQuery fq;
        while (!outstandingQueries.isEmpty()) {
            fq = outstandingQueries.remove(0);
            if (!fq.future.isDone()) {
                if (fq.runnable != null) fq.runnable.run();
                fq.future.cancel(true);
            }
        }
    }

    /**
     * Asks a question that must be answered this turn.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to ask.
     * @param question The <code>DOMMessage</code> question.
     * @param handler The <code>DOMMessageHandler</code> handler to process
     *     the reply with.
     * @param runnable An optional <code>Runnable</code> to run if the
     *     question was not answered.
     */
    private void askThisTurn(ServerPlayer serverPlayer, DOMMessage message,
                             DOMMessageHandler handler, Runnable runnable) {
        new FutureQuery(askFuture(serverPlayer, message, handler), runnable);
    }

    /**
     * Asks a question of a player with a timeout.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to ask.
     * @param question The <code>DOMMessage</code> question.
     * @return The response to the question, or null if none.
     */
    private DOMMessage askTimeout(ServerPlayer serverPlayer,
                                  DOMMessage request) {
        Future<DOMMessage> future = askFuture(serverPlayer, request,
            new DOMMessageHandler() {
                public DOMMessage handle(DOMMessage message) {
                    return message;
                }
            });
        DOMMessage reply;
        try {
            boolean single = getFreeColServer().isSingleplayer();
            reply = future.get(FreeCol.getFreeColTimeout(single),
                               TimeUnit.SECONDS);
        } catch (TimeoutException te) {
            sendElement(serverPlayer,
                new ChangeSet().addTrivial(See.only(serverPlayer),
                    "closeMenus", ChangePriority.CHANGE_NORMAL));
            reply = null;
        } catch (Exception e) {
            reply = null;
            logger.log(Level.WARNING, "Exception completing future", e);
        }
        return reply;
    }
        
        
    /**
     * Get a list of all server players, optionally excluding supplied ones.
     *
     * @param serverPlayers The <code>ServerPlayer</code>s to exclude.
     * @return A list of all connected server players, with exclusions.
     */
    private List<ServerPlayer> getOtherPlayers(ServerPlayer... serverPlayers) {
        List<ServerPlayer> result = new ArrayList<ServerPlayer>();
        outer: for (Player otherPlayer : getGame().getPlayers()) {
            ServerPlayer enemyPlayer = (ServerPlayer) otherPlayer;
            if (!enemyPlayer.isConnected()) continue;
            for (ServerPlayer exclude : serverPlayers) {
                if (enemyPlayer == exclude) continue outer;
            }
            result.add(enemyPlayer);
        }
        return result;
    }

    /**
     * Send a set of changes to all players.
     *
     * @param cs The <code>ChangeSet</code> to send.
     */
    private void sendToAll(ChangeSet cs) {
        sendToList(getOtherPlayers(), cs);
    }


    /**
     * Send an update to all players except one.
     *
     * @param serverPlayer A <code>ServerPlayer</code> to exclude.
     * @param cs The <code>ChangeSet</code> encapsulating the update.
     */
    private void sendToOthers(ServerPlayer serverPlayer, ChangeSet cs) {
        sendToList(getOtherPlayers(serverPlayer), cs);
    }

    /**
     * Send an element to all players except one.
     * Deprecated, please avoid if possible.
     *
     * @param serverPlayer A <code>ServerPlayer</code> to exclude.
     * @param element An <code>Element</code> to send.
     */
    private void sendToOthers(ServerPlayer serverPlayer, Element element) {
        sendToList(getOtherPlayers(serverPlayer), element);
    }

    /**
     * Send an update to a list of players.
     *
     * @param serverPlayers The <code>ServerPlayer</code>s to send to.
     * @param cs The <code>ChangeSet</code> encapsulating the update.
     */
    private void sendToList(List<ServerPlayer> serverPlayers, ChangeSet cs) {
        for (ServerPlayer s : serverPlayers) sendElement(s, cs);
    }

    /**
     * Send an element to a list of players.
     * Deprecated, please avoid if possible.
     *
     * @param serverPlayers The <code>ServerPlayer</code>s to send to.
     * @param element An <code>Element</code> to send.
     */
    private void sendToList(List<ServerPlayer> serverPlayers, Element element) {
        if (element != null) {
            for (ServerPlayer s : serverPlayers) {
                askElement(s, element);
            }
        }
    }

    /**
     * Send an element to a specific player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to update.
     * @param cs A <code>ChangeSet</code> to build an <code>Element</code> with.
     */
    private void sendElement(ServerPlayer serverPlayer, ChangeSet cs) {
        askElement(serverPlayer, cs.build(serverPlayer));
    }

    /**
     * Send an element to a specific player.
     * Deprecated, please avoid if possible.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to update.
     * @param request An <code>Element</code> containing the update.
     */
    private Element askElement(ServerPlayer serverPlayer, Element request) {
        Connection connection = serverPlayer.getConnection();
        if (request == null || connection == null) return null;

        Element reply;
        try {
            reply = connection.askDumping(request);
        } catch (IOException e) {
            logger.log(Level.WARNING, "Could not send \""
                + request.getTagName() + "\"-message.", e);
            reply = null;
        }
        return reply;
    }

    /**
     * Speaks to a chief in a native settlement, but only if it is as
     * a result of a scout actually asking to speak to the chief, or
     * for other settlement-contacting events such as missionary
     * actions, demanding tribute, learning skills and trading if the
     * settlementActionsContactChief game option is enabled.
     * It is still unclear what Col1 did here.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is contacting
     *     the settlement.
     * @param is The <code>IndianSettlement</code> to contact.
     * @param scout True if this contact is due to a scout asking to
     *     speak to the chief.
     * @param cs A <code>ChangeSet</code> to update.
     */
    private void csSpeakToChief(ServerPlayer serverPlayer, IndianSettlement is,
                                boolean scout, ChangeSet cs) {
        serverPlayer.csContact((ServerPlayer) is.getOwner(), null, cs);
        is.makeContactSettlement(serverPlayer);
        if (scout || getGame().getSpecification()
            .getBooleanOption("model.option.settlementActionsContactChief")
            .getValue()) {
            is.setSpokenToChief(serverPlayer);
        }
    }

    // Routines that follow implement the controller response to
    // messages.
    // The convention is to return an element to be passed back to the
    // client by the invoking message handler.

    /**
     * Ends the turn of the given player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to end the turn of.
     * @return Null.
     */
    public Element endTurn(ServerPlayer serverPlayer) {
        FreeColServer freeColServer = getFreeColServer();
        ServerGame game = getGame();
        ServerPlayer player = (ServerPlayer) game.getCurrentPlayer();

        if (serverPlayer != player) {
            throw new IllegalArgumentException("It is not "
                + serverPlayer.getName() + "'s turn, it is "
                + ((player == null) ? "noone" : player.getName()) + "'s!");
        }

        for (;;) {
            player.clearModelMessages();

            // Has anyone won?
            // Do not end single player games where an AI has won,
            // that would stop revenge mode.
            Player winner = game.checkForWinner();
            if (winner != null
                && !(freeColServer.isSingleplayer() && winner.isAI())) {
                ChangeSet cs = new ChangeSet();
                cs.addTrivial(See.all(), "gameEnded",
                              ChangePriority.CHANGE_NORMAL,
                              "winner", winner.getId());
                sendToOthers(serverPlayer, cs);
                return cs.build(serverPlayer);
            }

            // Are there humans left?
            // TODO: see if this can be relaxed so we can run large
            // AI-only simulations.
            boolean human = false;
            for (Player p : game.getPlayers()) {
                if (!p.isDead() && !p.isAI()
                    && ((ServerPlayer) p).isConnected()) {
                    human = true;
                    break;
                }
            }
            if (!human) {
                game.setCurrentPlayer(null);
                return null;
            }

            // ATM AI player colony-rearrangements do not use the standard
            // c-s model but exploit the fact that the AIs and the server
            // are still in the same memory space.  This means that
            // the colony sizes will be wrongly reported unless we take
            // the following explicit action.
            ChangeSet cs = new ChangeSet();
            if (player.isAI() && player.isEuropean()) {
                for (Colony c : player.getColonies()) {
                    cs.add(See.perhaps().except(player), c);
                }
            }

            // Clean up futures from the current player.
            resolveOutstandingQueries();

            // Check for new turn
            if (game.isNextPlayerInNewTurn()) {
                game.csNewTurn(random, cs);
                if (debugOnlyAITurns > 0) {
                    if (--debugOnlyAITurns <= 0) {
                        // If this was a debug run, complete it.  This will
                        // possibly signal the client to save and quit.
                        if (FreeCol.getDebugRunTurns() > 0) {
                            FreeCol.completeDebugRun();
                        }
                    }
                }
            }

            if ((player = (ServerPlayer) game.getNextPlayer()) == null) {
                // "can not happen"
                return DOMMessage.clientError("Can not get next player");
            }
            if (player.checkForDeath()) { // Remove dead players and retry
                player.csWithdraw(cs);
                sendToAll(cs);
                logger.info(player.getNation() + " is dead.");
                continue;
            } else if (player.isREF() && player.checkForREFDefeat()) {
                for (Player p : player.getRebels()) {
                    csGiveIndependence(player, (ServerPlayer) p, cs);
                }
                player.csWithdraw(cs);
                sendToAll(cs);
                logger.info(player.getNation() + " is defeated.");
                continue;
            }

            // Do "new turn"-like actions that need to wait until right
            // before the player is about to move.
            game.setCurrentPlayer(player);
            if (player.isREF() && player.getEntryLocation() == null) {
                // Initialize this newly created REF, determining its
                // entry location.
                // If the teleportREF option is enabled, teleport it in.
                REFAIPlayer refAIPlayer = (REFAIPlayer) freeColServer
                    .getAIPlayer(player);
                boolean teleport = getGame().getSpecification()
                    .getBoolean(GameOptions.TELEPORT_REF);
                Tile entry = refAIPlayer.initialize(teleport);
                if (entry == null) {
                    for (Player p : player.getRebels()) {
                        entry = p.getEntryLocation().getTile();
                        break;
                    }
                }
                player.setEntryLocation(entry);
                logger.info(player.getName() + " will appear at " + entry);
                if (teleport) {
                    for (Unit u : player.getUnits()) {
                        if (u.isNaval()) {
                            u.setLocation(entry);
                            u.setWorkLeft(-1);
                            u.setState(Unit.UnitState.ACTIVE);
                        }
                    }
                    cs.add(See.perhaps(), entry);
                }
            }
            player.csStartTurn(random, cs);
            nextFoundingFather(player);

            cs.addTrivial(See.all(), "setCurrentPlayer",
                          ChangePriority.CHANGE_LATE,
                          "player", player.getId());
            Monarch monarch = player.getMonarch();
            if (monarch != null) {
                MonarchAction action = null;
                if (debugMonarchAction != null
                    && player == debugMonarchPlayer) {
                    action = debugMonarchAction;
                    debugMonarchAction = null;
                    debugMonarchPlayer = null;
                    logger.finest("Debug monarch action: " + action);
                } else {
                    action = RandomChoice.getWeightedRandom(logger,
                            "Choose monarch action", random,
                            monarch.getActionChoices());
                }
                if (action != null) {
                    if (monarch.actionIsValid(action)) {
                        logger.finest("Monarch action: " + action);
                        csMonarchAction(player, action, cs);
                    } else {
                        logger.finest("Skipping invalid monarch action: "
                            + action);
                    }
                }
            }

            // First, flush accumulated changes to other players.
            // Then, if this is an AI or normal connected player in
            // non-debug mode then return the accumulated changes directly,
            // otherwise flush them out and retry.
            sendToOthers(serverPlayer, cs);
            if (player.isAI()
                || (player.isConnected() && debugOnlyAITurns <= 0)) {
                return cs.build(serverPlayer);
            } else {
                sendElement(serverPlayer, cs);
            }
        }
    }

    /**
     * Queries a player to choose their next founding father in a future.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to ask.
     * @return A <code>Future</code> to encapsulate the query.
     */
    private void nextFoundingFather(final ServerPlayer serverPlayer) {
        if (!serverPlayer.canRecruitFoundingFather()) return;
        if (serverPlayer.getOfferedFathers().isEmpty()) {
            serverPlayer.setOfferedFathers(serverPlayer
                .getRandomFoundingFathers(random));
        }
        final List<FoundingFather> ffs = serverPlayer.getOfferedFathers();
        if (ffs.isEmpty()) return;
        askThisTurn(serverPlayer, new ChooseFoundingFatherMessage(ffs),
            new DOMMessageHandler() {
                public DOMMessage handle(DOMMessage request) {
                    ChooseFoundingFatherMessage message
                        = (ChooseFoundingFatherMessage)request;
                    FoundingFather ff = message.getResult();
                    if (ff == null) {
                        logger.warning("No founding father selected");
                    } else if (!ffs.contains(ff)) {
                        logger.warning("Invalid founding father: "
                            + ff.getId());
                    } else {
                        serverPlayer.setCurrentFather(ff);
                        serverPlayer.clearOfferedFathers();
                        logger.info("Selected founding father: " + ff);
                    }
                    return null;
                }
            }, null);
    }

    /**
     * Give independence.  Note that the REF player is granting, but
     * most of the changes happen to the newly independent player.
     * hence the special handling.
     *
     * @param serverPlayer The REF <code>ServerPlayer</code> that is granting.
     * @param independent The newly independent <code>ServerPlayer</code>.
     * @param cs A <code>ChangeSet</code> to update.
     */
    private void csGiveIndependence(ServerPlayer serverPlayer,
                                    ServerPlayer independent, ChangeSet cs) {
        serverPlayer.csChangeStance(Stance.PEACE, independent, true, cs);
        independent.setPlayerType(PlayerType.INDEPENDENT);
        Game game = getGame();
        Turn turn = game.getTurn();
        independent.modifyScore(SCORE_INDEPENDENCE_GRANTED - turn.getNumber());
        independent.setTax(0);
        independent.reinitialiseMarket();
        cs.addGlobalHistory(game,
            new HistoryEvent(turn, HistoryEvent.EventType.INDEPENDENCE));
        cs.addMessage(See.only(independent),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                "model.player.independence", independent)
            .addStringTemplate("%ref%", serverPlayer.getNationName()));

        // Who surrenders?
        List<Unit> surrenderUnits = new ArrayList<Unit>();
        for (Unit u : serverPlayer.getUnits()) {
            if (!u.isNaval()) surrenderUnits.add(u);
        }
        if (surrenderUnits.size() > 0) {
            for (Unit u : surrenderUnits) {
                UnitType downgrade = u.getTypeChange(ChangeType.CAPTURE,
                                                     independent);
                if (downgrade != null) u.setType(downgrade);
                u.setOwner(independent);
                // Make sure the former owner is notified!
                cs.add(See.perhaps().always(serverPlayer), u);
            }
            cs.addMessage(See.only(independent),
                new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                    "model.player.independence.unitsAcquired", independent)
                .addStringTemplate("%units%",
                    unitTemplate(", ", surrenderUnits)));
        }

        // Update player type.  Again, a pity to have to do a whole
        // player update, but a partial update works for other players.
        cs.addPartial(See.all().except(independent), independent, "playerType");
        cs.addMessage(See.all().except(independent),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                "model.player.independence.announce", independent)
                .addStringTemplate("%nation%", independent.getNationName())
                .addStringTemplate("%ref%", serverPlayer.getNationName()));
        cs.add(See.only(independent), independent);
    }

    private StringTemplate unitTemplate(String base, List<Unit> units) {
        StringTemplate template = StringTemplate.label(base);
        for (Unit u : units) {
            template.addStringTemplate(u.getLabel());
        }
        return template;
    }

    private StringTemplate abstractUnitTemplate(String base,
                                                List<AbstractUnit> units) {
        StringTemplate template = StringTemplate.label(base);
        Specification spec = getGame().getSpecification();
        for (AbstractUnit au : units) {
            template.addStringTemplate(au.getLabel(spec));
        }
        return template;
    }

    private String getNonPlayerNation() {
        int nations = Nation.EUROPEAN_NATIONS.length;
        int start = Utils.randomInt(logger, "Random nation", random, nations);
        for (int index = 0; index < nations; index++) {
            String nationId = "model.nation."
                + Nation.EUROPEAN_NATIONS[(start + index) % nations];
            if (getGame().getPlayer(nationId) == null) {
                return nationId + ".name";
            }
        }
        // this should never happen
        return "";
    }

    /**
     * Resolves a tax raise.
     *
     * @param serverPlayer The <code>ServerPlayer</code> whose tax is rising.
     * @param taxRaise The amount of tax raise.
     * @param goods The <code>Goods</code> for a goods party.
     * @param result Whether the tax was accepted or not.
     */
    private void raiseTax(ServerPlayer serverPlayer, int taxRaise, Goods goods,
                          boolean result) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csRaiseTax(taxRaise, goods, result, cs);
        sendElement(serverPlayer, cs);
    }

    /**
     * Performs a monarch action.
     *
     * Note that CHANGE_LATE is used so that these actions follow
     * setting the current player, so that it is the players turn when
     * they respond to a monarch action.
     *
     * @param serverPlayer The <code>ServerPlayer</code> being acted upon.
     * @param action The monarch action.
     * @param cs A <code>ChangeSet</code> to update.
     */
    private void csMonarchAction(final ServerPlayer serverPlayer,
                                 MonarchAction action, ChangeSet cs) {
        final Monarch monarch = serverPlayer.getMonarch();
        boolean valid = monarch.actionIsValid(action);
        if (!valid) return;
        String messageId = "model.monarch.action." + action.toString();
        StringTemplate template;
        MonarchActionMessage message;

        switch (action) {
        case NO_ACTION:
            break;
        case RAISE_TAX_WAR: case RAISE_TAX_ACT:
            final int taxRaise = monarch.raiseTax(random);
            final Goods goods = serverPlayer.getMostValuableGoods();
            if (goods == null) {
                logger.finest("Ignoring tax raise, no goods to boycott.");
                break;
            }
            template = StringTemplate.template("model.monarch.action."
                + action.toString())
                .addStringTemplate("%goods%", goods.getType().getLabel(true))
                .addAmount("%amount%", taxRaise);
            if (action == MonarchAction.RAISE_TAX_WAR) {
                template = template.add("%nation%", getNonPlayerNation());
            } else if (action == MonarchAction.RAISE_TAX_ACT) {
                template = template.addAmount("%number%",
                    Utils.randomInt(logger, "Tax act goods", random, 6))
                    .addName("%newWorld%", serverPlayer.getNewLandName());
            }
            message = new MonarchActionMessage(action, template);
            message.setTax(taxRaise);
            askThisTurn(serverPlayer, message,
                new DOMMessageHandler() {
                    public DOMMessage handle(DOMMessage message) {
                        boolean result
                            = (message instanceof MonarchActionMessage)
                                ? ((MonarchActionMessage)message).getResult()
                                : false;
                        raiseTax(serverPlayer, taxRaise, goods, result);
                        return null;
                    }
                },
                new Runnable() {
                    public void run() {
                        raiseTax(serverPlayer, taxRaise, goods, false);
                    }
                });
            break;
        case LOWER_TAX_WAR: case LOWER_TAX_OTHER:
            int oldTax = serverPlayer.getTax();
            int taxLower = monarch.lowerTax(random);
            serverPlayer.csSetTax(taxLower, cs);
            template = StringTemplate.template(messageId)
                .addAmount("%difference%", oldTax - taxLower)
                .addAmount("%newTax%", taxLower);
            if (action == MonarchAction.LOWER_TAX_WAR) {
                template = template.add("%nation%", getNonPlayerNation());
            } else {
                template = template.addAmount("%number%",
                    Utils.randomInt(logger, "Lower tax reason", random, 5));
            }
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                new MonarchActionMessage(action, template));
            break;
        case WAIVE_TAX:
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_NORMAL,
                new MonarchActionMessage(action,
                    StringTemplate.template(messageId)));
            break;
        case ADD_TO_REF:
            AbstractUnit refAdditions = monarch.chooseForREF(random);
            if (refAdditions == null) break;
            monarch.addToREF(refAdditions);
            template = StringTemplate.template(messageId)
                .addAmount("%number%", refAdditions.getNumber())
                .add("%unit%", refAdditions.getUnitType(getGame().getSpecification())
                     .getNameKey());
            cs.add(See.only(serverPlayer), monarch);
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                   new MonarchActionMessage(action, template));
            break;
        case DECLARE_WAR:
            List<Player> enemies = monarch.collectPotentialEnemies();
            if (enemies.isEmpty()) break;
            Player enemy = Utils.getRandomMember(logger, "Choose enemy",
                enemies, random);
            serverPlayer.csChangeStance(Stance.WAR, enemy, true, cs);
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                new MonarchActionMessage(action,
                    StringTemplate.template(messageId)
                        .addStringTemplate("%nation%", enemy.getNationName())));
            break;
        case SUPPORT_LAND: case SUPPORT_SEA:
            boolean sea = action == MonarchAction.SUPPORT_SEA;
            List<AbstractUnit> support = monarch.getSupport(random, sea);
            if (support.isEmpty()) break;
            serverPlayer.createUnits(support);
            cs.add(See.only(serverPlayer), serverPlayer.getEurope());
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                new MonarchActionMessage(action,
                    StringTemplate.template(messageId)
                    .addStringTemplate("%addition%",
                        abstractUnitTemplate(", ", support))));
            break;
        case OFFER_MERCENARIES:
            final List<AbstractUnit> mercenaries
                = monarch.getMercenaries(random);
            if (mercenaries.isEmpty()) break;
            final int mercPrice = serverPlayer.priceMercenaries(mercenaries);
            message = new MonarchActionMessage(MonarchAction.OFFER_MERCENARIES,
                StringTemplate.template("model.monarch.action.OFFER_MERCENARIES")
                    .addAmount("%gold%", mercPrice)
                    .addStringTemplate("%mercenaries%",
                        abstractUnitTemplate(", ", mercenaries)));
            askThisTurn(serverPlayer, message,
                new DOMMessageHandler() {
                    public DOMMessage handle(DOMMessage message) {
                        boolean result
                            = (message instanceof MonarchActionMessage)
                                ? ((MonarchActionMessage)message).getResult()
                                : false;
                        if (result) {
                            ChangeSet cs = new ChangeSet();
                            serverPlayer.csAddMercenaries(mercenaries,
                                                          mercPrice, cs);
                            sendElement(serverPlayer, cs);
                        }
                        return null;
                    }
                }, null);
            break;
        case DISPLEASURE: default:
            logger.warning("Bogus action: " + action);
            break;
        }
    }

    /**
     * Check the high scores.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is retiring.
     * @return An element indicating the players high score state.
     */
    public Element checkHighScore(ServerPlayer serverPlayer) {
        FreeColServer freeColServer = getFreeColServer();
        boolean highScore = freeColServer.newHighScore(serverPlayer);
        if (highScore) {
            try {
                freeColServer.saveHighScores();
            } catch (Exception e) {
                logger.log(Level.WARNING, "Failed to save high scores", e);
                highScore = false;
            }
        }

        ChangeSet cs = new ChangeSet();
        cs.addAttribute(See.only(serverPlayer),
            "highScore", Boolean.toString(highScore));
        return cs.build(serverPlayer);
    }

    /**
     * Handle a player retiring.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is retiring.
     * @return An element cleaning up the player.
     */
    public Element retire(ServerPlayer serverPlayer) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csWithdraw(cs); // Clean up the player.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Continue playing after winning.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that plays on.
     * @return Null.
     */
    public Element continuePlaying(ServerPlayer serverPlayer) {
        ServerGame game = (ServerGame) getGame();
        Element reply = null;
        if (!getFreeColServer().isSingleplayer()) {
            logger.warning("Can not continue playing in multiplayer!");
        } else if (serverPlayer != game.checkForWinner()) {
            logger.warning("Can not continue playing, as "
                           + serverPlayer.getName()
                           + " has not won the game!");
        } else {
            Specification spec = game.getSpecification();
            spec.getBooleanOption(GameOptions.VICTORY_DEFEAT_REF)
                .setValue(false);
            spec.getBooleanOption(GameOptions.VICTORY_DEFEAT_EUROPEANS)
                .setValue(false);
            spec.getBooleanOption(GameOptions.VICTORY_DEFEAT_HUMANS)
                .setValue(false);
            // The victory panel is shown after end turn, end turn again
            // to start turn of next player.
            reply = endTurn((ServerPlayer) game.getCurrentPlayer());
        }
        return reply;
    }


    /**
     * Cash in a treasure train.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is cashing in.
     * @param unit The treasure train <code>Unit</code> to cash in.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element cashInTreasureTrain(ServerPlayer serverPlayer, Unit unit) {
        ChangeSet cs = new ChangeSet();

        // Work out the cash in amount and the message to send.
        int fullAmount = unit.getTreasureAmount();
        int cashInAmount;
        String messageId;
        if (serverPlayer.getPlayerType() == PlayerType.COLONIAL) {
            // Charge transport fee and apply tax
            cashInAmount = (fullAmount - unit.getTransportFee())
                * (100 - serverPlayer.getTax()) / 100;
            messageId = "model.unit.cashInTreasureTrain.colonial";
        } else {
            // No fee possible, no tax applies.
            cashInAmount = fullAmount;
            messageId = "model.unit.cashInTreasureTrain.independent";
        }
            
        serverPlayer.modifyGold(cashInAmount);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold", "score");
        cs.addMessage(See.only(serverPlayer),
            new ModelMessage(messageId, serverPlayer, unit)
                .addAmount("%amount%", fullAmount)
                .addAmount("%cashInAmount%", cashInAmount));
        messageId = (serverPlayer.getPlayerType() == PlayerType.REBEL
                     || serverPlayer.getPlayerType() == PlayerType.INDEPENDENT)
            ? "model.unit.cashInTreasureTrain.other.independent"
            : "model.unit.cashInTreasureTrain.other.colonial";
        cs.addMessage(See.all().except(serverPlayer),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                             messageId, serverPlayer)
                .addAmount("%amount%", fullAmount)
                .addStringTemplate("%nation%", serverPlayer.getNationName()));

        // Dispose of the unit, only visible to the owner.
        cs.add(See.only(serverPlayer), (FreeColGameObject) unit.getLocation());
        cs.addDispose(See.only(serverPlayer), null, unit);

        // Others can see the cash in message.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Declare independence.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is naming.
     * @param nationName The new name for the independent nation.
     * @param countryName The new name for its residents.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element declareIndependence(ServerPlayer serverPlayer,
                                       String nationName, String countryName) {
        ChangeSet cs = new ChangeSet();

        // Cross the Rubicon
        StringTemplate oldNation = serverPlayer.getNationName();
        serverPlayer.setIndependentNationName(nationName);
        serverPlayer.setNewLandName(countryName);
        serverPlayer.setPlayerType(PlayerType.REBEL);
        serverPlayer.getFeatureContainer().addAbility(new Ability("model.ability.independenceDeclared"));
        serverPlayer.modifyScore(SCORE_INDEPENDENCE_DECLARED);

        // Do not add history event to cs as we are going to update the
        // entire player.  Likewise clear model messages.
        Turn turn = getGame().getTurn();
        cs.addGlobalHistory(getGame(), new HistoryEvent(turn,
                HistoryEvent.EventType.DECLARE_INDEPENDENCE));
        serverPlayer.clearModelMessages();
        cs.addMessage(See.only(serverPlayer),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                "warOfIndependence.independenceDeclared", serverPlayer));

        // Dispose of units in Europe.
        Europe europe = serverPlayer.getEurope();
        StringTemplate seized = StringTemplate.label(", ");
        for (Unit u : europe.getUnitList()) {
            seized.addStringTemplate(u.getLabel());
        }
        if (!seized.getReplacements().isEmpty()) {
            cs.addMessage(See.only(serverPlayer),
                new ModelMessage(ModelMessage.MessageType.UNIT_LOST,
                                 "model.player.independence.unitsSeized",
                                 serverPlayer)
                    .addStringTemplate("%units%", seized));
        }

        // Generalized continental army muster
        java.util.Map<UnitType, UnitType> upgrades
            = new HashMap<UnitType, UnitType>();
        Specification spec = getGame().getSpecification();
        for (UnitType unitType : spec.getUnitTypeList()) {
            UnitType upgrade = unitType.getTargetType(ChangeType.INDEPENDENCE,
                                                      serverPlayer);
            if (upgrade != null) {
                upgrades.put(unitType, upgrade);
            }
        }
        for (Colony colony : serverPlayer.getColonies()) {
            int sol = colony.getSoL();
            if (sol > 50) {
                java.util.Map<UnitType, List<Unit>> unitMap = new HashMap<UnitType, List<Unit>>();
                List<Unit> allUnits = colony.getTile().getUnitList();
                allUnits.addAll(colony.getUnitList());
                for (Unit unit : allUnits) {
                    if (upgrades.containsKey(unit.getType())) {
                        List<Unit> unitList = unitMap.get(unit.getType());
                        if (unitList == null) {
                            unitList = new ArrayList<Unit>();
                            unitMap.put(unit.getType(), unitList);
                        }
                        unitList.add(unit);
                    }
                }
                for (Entry<UnitType, List<Unit>> entry : unitMap.entrySet()) {
                    int limit = (entry.getValue().size() + 2) * (sol - 50) / 100;
                    if (limit > 0) {
                        for (int index = 0; index < limit; index++) {
                            Unit unit = entry.getValue().get(index);
                            if (unit == null) break;
                            unit.setType(upgrades.get(entry.getKey()));
                            cs.add(See.only(serverPlayer), unit);
                        }
                        cs.addMessage(See.only(serverPlayer),
                            new ModelMessage(ModelMessage.MessageType.UNIT_IMPROVED,
                                             "model.player.continentalArmyMuster",
                                             serverPlayer, colony)
                                .addName("%colony%", colony.getName())
                                .addAmount("%number%", limit)
                                .add("%oldUnit%", entry.getKey().getNameKey())
                                .add("%unit%", upgrades.get(entry.getKey()).getNameKey()));
                    }
                }
            }
        }

        // Create the REF.
        ServerPlayer refPlayer = createREFPlayer(serverPlayer);

        // Now the REF is ready, we can dispose of the European connection.
        serverPlayer.getHighSeas().removeDestination(europe);
        cs.addDispose(See.only(serverPlayer), null, europe);
        serverPlayer.setEurope(null);
        serverPlayer.setMonarch(null);

        // Pity to have to update such a heavy object as the player,
        // but we do this, at most, once per player.  Other players
        // only need a partial player update and the stance change.
        // Put the stance change after the name change so that the
        // other players see the new nation name declaring war.  The
        // REF is hardwired to declare war on rebels so there is no
        // need to adjust its stance or tension.
        cs.addPartial(See.all().except(serverPlayer), serverPlayer,
                      "playerType", "independentNationName", "newLandName");
        cs.addMessage(See.all().except(serverPlayer),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                             "declareIndependence.announce",
                             serverPlayer)
                .addStringTemplate("%oldNation%", oldNation)
                .addStringTemplate("%newNation%", serverPlayer.getNationName())
                .add("%ruler%", serverPlayer.getRulerNameKey()));
        cs.add(See.only(serverPlayer), serverPlayer);
        serverPlayer.csChangeStance(Stance.WAR, refPlayer, true, cs);

        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Rename an object.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is naming.
     * @param object The <code>Nameable</code> to rename.
     * @param newName The new name.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element renameObject(ServerPlayer serverPlayer, Nameable object,
                                String newName) {
        ChangeSet cs = new ChangeSet();

        object.setName(newName);
        FreeColGameObject fcgo = (FreeColGameObject) object;
        cs.addPartial(See.all(), fcgo, "name");

        // Others may be able to see the name change.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Gets a settlement transaction session, either existing or
     * newly opened.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is trading.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> that is trading.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getTransaction(ServerPlayer serverPlayer, Unit unit,
                                  Settlement settlement) {
        ChangeSet cs = new ChangeSet();
        TradeSession session = TransactionSession.lookup(TradeSession.class,
            unit, settlement);
        if (session == null) {
            if (unit.getMovesLeft() <= 0) {
                return DOMMessage.clientError("Unit " + unit.getId()
                    + " has no moves left.");
            }
            session = new TradeSession(unit, settlement);
            // Sets unit moves to zero to avoid cheating.  If no
            // action is taken, the moves will be restored when
            // closing the session
            unit.setMovesLeft(0);
            cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        }

        // Add just the attributes the client needs.
        cs.addAttribute(See.only(serverPlayer), "canBuy",
            Boolean.toString(session.getBuy()));
        cs.addAttribute(See.only(serverPlayer), "canSell",
            Boolean.toString(session.getSell()));
        cs.addAttribute(See.only(serverPlayer), "canGift",
            Boolean.toString(session.getGift()));

        // Others can not see transactions.
        return cs.build(serverPlayer);
    }


    /**
     * Close a transaction.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is trading.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> that is trading.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element closeTransaction(ServerPlayer serverPlayer, Unit unit,
                                    Settlement settlement) {
        TradeSession session
            = TradeSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("No such transaction session.");
        }

        ChangeSet cs = new ChangeSet();
        // Restore unit movement if no action taken.
        if (!session.getActionTaken()) {
            unit.setMovesLeft(session.getMovesLeft());
            cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        }
        session.complete(cs);

        // Others can not see end of transaction.
        return cs.build(serverPlayer);
    }


    /**
     * Get the goods for sale in a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is enquiring.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> that is trading.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getGoodsForSale(ServerPlayer serverPlayer, Unit unit,
                                   Settlement settlement) {
        List<Goods> sellGoods = null;

        if (settlement instanceof IndianSettlement) {
            IndianSettlement indianSettlement = (IndianSettlement) settlement;
            AIPlayer aiPlayer = getFreeColServer()
                .getAIPlayer(indianSettlement.getOwner());
            sellGoods = indianSettlement.getSellGoods(3, unit);
            for (Goods goods : sellGoods) {
                aiPlayer.registerSellGoods(goods);
            }
        } else { // Colony might be supported one day?
            return DOMMessage.clientError("Bogus settlement");
        }
        return new GoodsForSaleMessage(unit, settlement, sellGoods)
            .toXMLElement();
    }


    /**
     * Price some goods for sale from a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is buying.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> that is trading.
     * @param goods The <code>Goods</code> to buy.
     * @param price The buyers proposed price for the goods.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element buyProposition(ServerPlayer serverPlayer,
                                  Unit unit, Settlement settlement,
                                  Goods goods, int price) {
        TradeSession session
            = TradeSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("Proposing to buy without opening a transaction session?!");
        }
        if (!session.getBuy()) {
            return DOMMessage.clientError("Proposing to buy in a session where buying is not allowed.");
        }
        ChangeSet cs = new ChangeSet();
        if (settlement instanceof IndianSettlement) {
            csSpeakToChief(serverPlayer, (IndianSettlement)settlement,
                           false, cs);
        }

        // AI considers the proposition, return with a gold value
        AIPlayer ai = getFreeColServer().getAIPlayer(settlement.getOwner());
        int gold = ai.buyProposition(unit, settlement, goods, price);

        // Others can not see proposals.
        cs.addAttribute(See.only(serverPlayer),
                        "gold", Integer.toString(gold));
        return cs.build(serverPlayer);
    }

    /**
     * Price some goods for sale to a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is buying.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> that is trading.
     * @param goods The <code>Goods</code> to sell.
     * @param price The sellers proposed price for the goods.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element sellProposition(ServerPlayer serverPlayer,
                                   Unit unit, Settlement settlement,
                                   Goods goods, int price) {
        TradeSession session
            = TradeSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("Proposing to sell without opening a transaction session");
        }
        if (!session.getSell()) {
            return DOMMessage.clientError("Proposing to sell in a session where selling is not allowed.");
        }
        ChangeSet cs = new ChangeSet();
        if (settlement instanceof IndianSettlement) {
            csSpeakToChief(serverPlayer, (IndianSettlement)settlement,
                           false, cs);
        }

        // AI considers the proposition, return with a gold value
        AIPlayer ai = getFreeColServer().getAIPlayer(settlement.getOwner());
        int gold = ai.sellProposition(unit, settlement, goods, price);

        // Others can not see proposals.
        cs.addAttribute(See.only(serverPlayer),
                        "gold", Integer.toString(gold));
        return cs.build(serverPlayer);
    }

    /**
     * Buy goods in Europe.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is buying.
     * @param unit The <code>Unit</code> to carry the goods.
     * @param type The <code>GoodsType</code> to buy.
     * @param amount The amount of goods to buy.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element buyGoods(ServerPlayer serverPlayer, Unit unit,
                            GoodsType type, int amount) {
        if (!serverPlayer.canTrade(type, Access.EUROPE)) {
            return DOMMessage.clientError("Can not trade boycotted goods");
        }
        ChangeSet cs = new ChangeSet();
        GoodsContainer container = unit.getGoodsContainer();
        container.saveState();
        serverPlayer.buy(container, type, amount, random);
        serverPlayer.csFlushMarket(type, cs);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        cs.add(See.only(serverPlayer), container);
        // Action occurs in Europe, nothing is visible to other players.
        return cs.build(serverPlayer);
    }

    /**
     * Sell goods in Europe.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is selling.
     * @param unit The <code>Unit</code> carrying the goods.
     * @param type The <code>GoodsType</code> to sell.
     * @param amount The amount of goods to sell.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element sellGoods(ServerPlayer serverPlayer, Unit unit,
                             GoodsType type, int amount) {
        if (!serverPlayer.canTrade(type, Access.EUROPE)) {
            return DOMMessage.clientError("Can not trade boycotted goods");
        }
        ChangeSet cs = new ChangeSet();
        GoodsContainer container = unit.getGoodsContainer();
        container.saveState();
        serverPlayer.sell(container, type, amount, random);
        serverPlayer.csFlushMarket(type, cs);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        cs.add(See.only(serverPlayer), container);
        // Action occurs in Europe, nothing is visible to other players.
        return cs.build(serverPlayer);
    }


    /**
     * A unit migrates from Europe.
     *
     * @param serverPlayer The <code>ServerPlayer</code> whose unit it will be.
     * @param slot The slot within <code>Europe</code> to select the unit from.
     * @param type The type of migration occurring.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element emigrate(ServerPlayer serverPlayer, int slot,
                            MigrationType type) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csEmigrate(slot, type, random, cs);

        // Do not update others, emigration is private.
        return cs.build(serverPlayer);
    }


    /**
     * Move a unit.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is moving.
     * @param unit The <code>Unit</code> to move.
     * @param newTile The <code>Tile</code> to move to.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element move(ServerPlayer serverPlayer, Unit unit, Tile newTile) {
        ChangeSet cs = new ChangeSet();
        ((ServerUnit) unit).csMove(newTile, random, cs);
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Decline to investigate strange mounds.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param tile The <code>Tile</code> where the mounds are.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element declineMounds(ServerPlayer serverPlayer, Tile tile) {
        tile.removeLostCityRumour();

        // Others might see rumour disappear
        ChangeSet cs = new ChangeSet();
        cs.add(See.perhaps(), tile);
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Set land name.
     *
     * @param serverPlayer The <code>ServerPlayer</code> who landed.
     * @param unit The <code>Unit</code> that has come ashore.
     * @param name The new land name.
     * @param welcomer An optional <code>ServerPlayer</code> that has offered
     *            a treaty.
     * @param camps An optional number of camps for the welcome message.
     * @param accept True if the treaty has been accepted.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setNewLandName(ServerPlayer serverPlayer, Unit unit,
                                  String name, ServerPlayer welcomer, int camps,
                                  boolean accept) {
        ChangeSet cs = new ChangeSet();

        // Special case of a welcome from an adjacent native unit,
        // offering the land the landing unit is on if a peace treaty
        // is accepted.
        serverPlayer.setNewLandName(name);
        if (welcomer != null) {
            if (accept) { // Claim land
                Tile tile = unit.getTile();
                tile.changeOwnership(serverPlayer, null);
                cs.add(See.perhaps(), tile);
            } else { // Consider not accepting the treaty to be an insult.
                cs.add(See.only(null).perhaps(serverPlayer),
                    welcomer.modifyTension(serverPlayer,
                        Tension.TENSION_ADD_MAJOR));
            }
        }

        // Update the name and note the history.
        cs.addPartial(See.only(serverPlayer), serverPlayer, "newLandName");
        Turn turn = serverPlayer.getGame().getTurn();
        cs.addHistory(serverPlayer,
            new HistoryEvent(turn, HistoryEvent.EventType.DISCOVER_NEW_WORLD)
                .addName("%name%", name));

        // Only the tile change is not private.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Set region name.
     *
     * @param serverPlayer The <code>ServerPlayer</code> discovering.
     * @param region The <code>Region</code> to discover.
     * @param name The new region name.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setNewRegionName(ServerPlayer serverPlayer,
                                    Region region, String name) {
        ChangeSet cs = new ChangeSet();
        cs.addRegion(serverPlayer, region, name);

        // Others do find out about region name changes.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Move a unit across the high seas.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to move.
     * @param destination The <code>Location</code> to move to.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element moveTo(ServerPlayer serverPlayer, Unit unit,
                          Location destination) {
        ChangeSet cs = new ChangeSet();
        HighSeas highSeas = serverPlayer.getHighSeas();
        Location current = unit.getDestination();
        boolean others = false; // Notify others?
        boolean invalid = false; // Not a highSeas move?

        if (destination instanceof Europe) {
            if (!highSeas.getDestinations().contains(destination)) {
                return DOMMessage.clientError("HighSeas does not connect to: "
                    + ((FreeColGameObject) destination).getId());
            } else if (unit.getLocation() == highSeas) {
                if (!(current instanceof Europe)) {
                    // Changed direction
                    unit.setWorkLeft(unit.getSailTurns()
                        - unit.getWorkLeft() + 1);
                }
                unit.setDestination(destination);
                cs.add(See.only(serverPlayer), unit, highSeas);
            } else if (unit.getTile() != null) {
                Tile tile = unit.getTile();
                unit.setEntryLocation(tile);
                unit.setWorkLeft(unit.getSailTurns());
                unit.setDestination(destination);
                unit.setMovesLeft(0);
                unit.setLocation(highSeas);
                cs.addDisappear(serverPlayer, tile, unit);
                cs.add(See.only(serverPlayer), tile, highSeas);
                others = true;
            } else {
                invalid = true;
            }
        } else if (destination instanceof Map) {
            if (!highSeas.getDestinations().contains(destination)) {
                return DOMMessage.clientError("HighSeas does not connect to: "
                    + ((FreeColGameObject) destination).getId());
            } else if (unit.getLocation() == highSeas) {
                if (current != destination && (current == null
                        || current.getTile() == null
                        || current.getTile().getMap() != destination)) {
                    // Changed direction
                    unit.setWorkLeft(unit.getSailTurns()
                        - unit.getWorkLeft() + 1);
                }
                unit.setDestination(destination);
                cs.add(See.only(serverPlayer), highSeas);
            } else if (unit.getLocation() instanceof Europe) {
                Europe europe = (Europe) unit.getLocation();
                unit.setWorkLeft(unit.getSailTurns());
                unit.setDestination(destination);
                unit.setMovesLeft(0);
                unit.setLocation(highSeas);
                cs.add(See.only(serverPlayer), europe, highSeas);
            } else {
                invalid = true;
            }
        } else if (destination instanceof Settlement) {
            Tile tile = destination.getTile();
            if (!highSeas.getDestinations().contains(tile.getMap())) {
                return DOMMessage.clientError("HighSeas does not connect to: "
                    + ((FreeColGameObject) destination).getId());
            } else if (unit.getLocation() == highSeas) {
                // Direction is somewhat moot, so just reset.
                unit.setWorkLeft(unit.getSailTurns());
                unit.setDestination(destination);
                cs.add(See.only(serverPlayer), highSeas);
            } else if (unit.getLocation() instanceof Europe) {
                Europe europe = (Europe) unit.getLocation();
                unit.setWorkLeft(unit.getSailTurns());
                unit.setDestination(destination);
                unit.setMovesLeft(0);
                unit.setLocation(highSeas);
                cs.add(See.only(serverPlayer), europe, highSeas);
            } else {
                invalid = true;
            }
        } else {
            return DOMMessage.clientError("Bogus moveTo destination: "
                + ((FreeColGameObject) destination).getId());
        }
        if (invalid) {
            return DOMMessage.clientError("Invalid moveTo: unit=" + unit.getId()
                + " from=" + ((FreeColGameObject) unit.getLocation()).getId()
                + " to=" + ((FreeColGameObject) destination).getId());
        }

        if (others) sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Embark a unit onto a carrier.
     * Checking that the locations are appropriate is not done here.
     *
     * @param serverPlayer The <code>ServerPlayer</code> embarking.
     * @param unit The <code>Unit</code> that is embarking.
     * @param carrier The <code>Unit</code> to embark onto.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element embarkUnit(ServerPlayer serverPlayer, Unit unit,
                              Unit carrier) {
        if (unit.isNaval()) {
            return DOMMessage.clientError("Naval unit " + unit.getId()
                + " can not embark.");
        }
        if (carrier.getSpaceLeft() < unit.getSpaceTaken()) {
            return DOMMessage.clientError("No space available for unit "
                + unit.getId() + " to embark.");
        }

        ChangeSet cs = new ChangeSet();

        Location oldLocation = unit.getLocation();
        unit.setLocation(carrier);
        unit.setMovesLeft(0);
        cs.add(See.only(serverPlayer), (FreeColGameObject) oldLocation);
        if (carrier.getLocation() != oldLocation) {
            cs.add(See.only(serverPlayer), carrier);
        }
        if (oldLocation instanceof Tile) {
            cs.addMove(See.only(serverPlayer), unit, (Tile) oldLocation,
                carrier.getTile());
            cs.addDisappear(serverPlayer, (Tile) oldLocation, unit);
        }

        // Others might see the unit disappear, or the carrier capacity.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Disembark unit from a carrier.
     *
     * @param serverPlayer The <code>ServerPlayer</code> whose unit is
     *                     embarking.
     * @param unit The <code>Unit</code> that is disembarking.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element disembarkUnit(ServerPlayer serverPlayer, Unit unit) {
        if (unit.isNaval()) {
            return DOMMessage.clientError("Naval unit " + unit.getId()
                + " can not disembark.");
        }
        if (!(unit.getLocation() instanceof Unit)) {
            return DOMMessage.clientError("Unit " + unit.getId()
                + " is not embarked.");
        }

        ChangeSet cs = new ChangeSet();

        Unit carrier = (Unit) unit.getLocation();
        Location newLocation = carrier.getLocation();
        List<Tile> newTiles = (newLocation.getTile() == null) ? null
            : ((ServerUnit) unit).collectNewTiles(newLocation.getTile());
        unit.setLocation(newLocation);
        unit.setMovesLeft(0); // In Col1 disembark consumes whole move.
        cs.add(See.perhaps(), (FreeColGameObject) newLocation);
        if (newTiles != null) {
            serverPlayer.csSeeNewTiles(newTiles, cs);
        }

        // Others can (potentially) see the location.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Combat.
     *
     * @param attackerPlayer The <code>ServerPlayer</code> who is attacking.
     * @param attacker The <code>FreeColGameObject</code> that is attacking.
     * @param defender The <code>FreeColGameObject</code> that is defending.
     * @param crs A list of <code>CombatResult</code>s defining the result.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element combat(ServerPlayer attackerPlayer,
                          FreeColGameObject attacker,
                          FreeColGameObject defender,
                          List<CombatResult> crs) {
        ChangeSet cs = new ChangeSet();
        try {
            attackerPlayer.csCombat(attacker, defender, crs, random, cs);
        } catch (Exception e) {
            logger.log(Level.WARNING, "Combat FAIL", e);
            return DOMMessage.clientError(e.getMessage());
        }
        sendToOthers(attackerPlayer, cs);
        return cs.build(attackerPlayer);
    }


    /**
     * Ask about learning a skill at an IndianSettlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is learning.
     * @param unit The <code>Unit</code> that is learning.
     * @param settlement The <code>Settlement</code> to learn from.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element askLearnSkill(ServerPlayer serverPlayer, Unit unit,
                                 IndianSettlement settlement) {
        ChangeSet cs = new ChangeSet();

        csSpeakToChief(serverPlayer, settlement, false, cs);
        Tile tile = settlement.getTile();
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), tile);
        unit.setMovesLeft(0);
        cs.addPartial(See.only(serverPlayer), unit, "movesLeft");

        // Do not update others, nothing to see yet.
        return cs.build(serverPlayer);
    }

    /**
     * Learn a skill at an IndianSettlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is learning.
     * @param unit The <code>Unit</code> that is learning.
     * @param settlement The <code>Settlement</code> to learn from.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element learnFromIndianSettlement(ServerPlayer serverPlayer,
                                             Unit unit,
                                             IndianSettlement settlement) {
        // Sanity checks.
        UnitType skill = settlement.getLearnableSkill();
        if (skill == null) {
            return DOMMessage.clientError("No skill to learn at "
                + settlement.getName());
        }
        if (!unit.getType().canBeUpgraded(skill, ChangeType.NATIVES)) {
            return DOMMessage.clientError("Unit " + unit.toString()
                + " can not learn skill " + skill
                + " at " + settlement.getName());
        }

        // Try to learn
        ChangeSet cs = new ChangeSet();
        unit.setMovesLeft(0);
        csSpeakToChief(serverPlayer, settlement, false, cs);
        switch (settlement.getAlarm(serverPlayer).getLevel()) {
        case HATEFUL: // Killed, might be visible to other players.
            cs.add(See.perhaps().always(serverPlayer),
                   (FreeColGameObject) unit.getLocation());
            cs.addDispose(See.perhaps().always(serverPlayer),
                unit.getLocation(), unit);
            break;
        case ANGRY: // Learn nothing, not even a pet update
            cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
            break;
        default:
            // Teach the unit, and expend the skill if necessary.
            // Do a full information update as the unit is in the settlement.
            unit.setType(skill);
            if (!settlement.isCapital()
                && !(settlement.getMissionary(serverPlayer) != null
                    && getGame().getSpecification()
                    .getBoolean("model.option.enhancedMissionaries"))) {
                settlement.setLearnableSkill(null);
            }
            break;
        }
        Tile tile = settlement.getTile();
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), unit, tile);

        // Others always see the unit, it may have died or been taught.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Demand a tribute from a native settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> demanding the tribute.
     * @param unit The <code>Unit</code> that is demanding the tribute.
     * @param settlement The <code>IndianSettlement</code> demanded of.
     * @return An <code>Element</code> encapsulating this action.
     * TODO: Move TURNS_PER_TRIBUTE magic number to the spec.
     */
    public Element demandTribute(ServerPlayer serverPlayer, Unit unit,
                                 IndianSettlement settlement) {
        ChangeSet cs = new ChangeSet();
        final int TURNS_PER_TRIBUTE = 5;

        csSpeakToChief(serverPlayer, settlement, false, cs);

        Player indianPlayer = settlement.getOwner();
        int gold = 0;
        int year = getGame().getTurn().getNumber();
        RandomRange gifts = settlement.getType().getGifts(unit);
        if (settlement.getLastTribute() + TURNS_PER_TRIBUTE < year
            && gifts != null) {
            switch (indianPlayer.getTension(serverPlayer).getLevel()) {
            case HAPPY: case CONTENT:
                gold = Math.min(gifts.getAmount("Tribute", random, true) / 10,
                                100);
                break;
            case DISPLEASED:
                gold = Math.min(gifts.getAmount("Tribute", random, true) / 20,
                                100);
                break;
            case ANGRY: case HATEFUL:
            default:
                gold = 0; // No tribute for you.
                break;
            }
        }

        // Increase tension whether we paid or not.  Apply tension
        // directly to the settlement and let propagation work.
        cs.add(See.only(serverPlayer),
            ((ServerIndianSettlement)settlement).modifyAlarm(serverPlayer,
                Tension.TENSION_ADD_NORMAL));
        settlement.setLastTribute(year);
        ModelMessage m;
        if (gold > 0) {
            indianPlayer.modifyGold(-gold);
            serverPlayer.modifyGold(gold);
            cs.addPartial(See.only(serverPlayer), serverPlayer, "gold", "score");
            m = new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                                 "scoutSettlement.tributeAgree",
                                 unit, settlement)
                .addAmount("%amount%", gold);
        } else {
            m = new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                                 "scoutSettlement.tributeDisagree",
                                 unit, settlement);
        }
        cs.addMessage(See.only(serverPlayer), m);
        Tile tile = settlement.getTile();
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), tile);
        unit.setMovesLeft(0);
        cs.addPartial(See.only(serverPlayer), unit, "movesLeft");

        // Do not update others, this is all private.
        return cs.build(serverPlayer);
    }


    /**
     * Scout a native settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is scouting.
     * @param unit The scout <code>Unit</code>.
     * @param settlement The <code>IndianSettlement</code> to scout.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element scoutIndianSettlement(ServerPlayer serverPlayer,
                                         Unit unit,
                                         IndianSettlement settlement) {
        ChangeSet cs = new ChangeSet();
        Tile tile = settlement.getTile();
        boolean tileDirty = settlement.makeContactSettlement(serverPlayer);
        String result;

        // Hateful natives kill the scout right away.
        Tension tension = settlement.getAlarm(serverPlayer);
        if (tension.getLevel() == Tension.Level.HATEFUL) {
            cs.add(See.perhaps().always(serverPlayer),
                   (FreeColGameObject) unit.getLocation());
            cs.addDispose(See.perhaps().always(serverPlayer),
                unit.getLocation(), unit);
            result = "die";
        } else {
            // Otherwise player gets to visit, and learn about the settlement.
            int radius = unit.getLineOfSight();
            UnitType skill = settlement.getLearnableSkill();
            if (settlement.hasSpokenToChief()) {
                // Do nothing if already spoken to.
                result = "nothing";
            } else if (skill != null
                       && skill.hasAbility(Ability.EXPERT_SCOUT)
                       && unit.getType().canBeUpgraded(skill,
                                                       ChangeType.NATIVES)) {
                // If the scout can be taught to be an expert it will be.
                // TODO: in the old code the settlement retains the
                // teaching ability.  WWC1D?
                unit.setType(settlement.getLearnableSkill());
                result = "expert";
            } else {
                // Choose tales 1/3 of the time, or if there are no beads.
                RandomRange gifts = settlement.getType().getGifts(unit);
                int gold = (gifts == null) ? 0
                    : gifts.getAmount("Base beads amount", random, true);
                if (gold <= 0
                    || Utils.randomInt(logger, "Tales", random, 3) == 0) {
                    radius = Math.max(radius, IndianSettlement.TALES_RADIUS);
                    result = "tales";
                } else {
                    if (unit.hasAbility(Ability.EXPERT_SCOUT)) {
                        gold = (gold * 11) / 10; // TODO: magic number
                    }
                    serverPlayer.modifyGold(gold);
                    settlement.getOwner().modifyGold(-gold);
                    result = "beads";
                }
            }

            // Have now spoken to the chief.
            csSpeakToChief(serverPlayer, settlement, true, cs);
            tileDirty = true;

            // Update settlement tile with new information, and any
            // newly visible tiles, possibly with enhanced radius.
            for (Tile t : tile.getSurroundingTiles(radius)) {
                if (!serverPlayer.canSee(t) && (t.isLand() || t.isCoast())) {
                    serverPlayer.setExplored(t);
                    cs.add(See.only(serverPlayer), t);
                }
            }

            // If the unit was promoted, update it completely, otherwise just
            // update moves and possibly gold+score.
            unit.setMovesLeft(0);
            if ("expert".equals(result)) {
                cs.add(See.perhaps(), unit);
            } else {
                cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
                if ("beads".equals(result)) {
                    cs.addPartial(See.only(serverPlayer), serverPlayer,
                                  "gold", "score");
                }
            }
        }
        if (tileDirty) {
            tile.updatePlayerExploredTile(serverPlayer, true);
            cs.add(See.only(serverPlayer), tile);
        }

        // Always add result.
        cs.addAttribute(See.only(serverPlayer), "result", result);

        // Other players may be able to see unit disappearing, or
        // learning.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Denounce an existing mission.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is denouncing.
     * @param unit The <code>Unit</code> denouncing.
     * @param settlement The <code>IndianSettlement</code> containing the
     *                   mission to denounce.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element denounceMission(ServerPlayer serverPlayer, Unit unit,
                                   IndianSettlement settlement) {
        ChangeSet cs = new ChangeSet();
        csSpeakToChief(serverPlayer, settlement, false, cs);

        // Determine result
        Unit missionary = settlement.getMissionary();
        if (missionary == null) {
            return DOMMessage.clientError("Denouncing null missionary");
        }
        ServerPlayer enemy = (ServerPlayer) missionary.getOwner();
        double denounce = Utils.randomDouble(logger, "Denounce base", random)
            * enemy.getImmigration() / (serverPlayer.getImmigration() + 1);
        if (missionary.hasAbility("model.ability.expertMissionary")) {
            denounce += 0.2;
        }
        if (unit.hasAbility("model.ability.expertMissionary")) {
            denounce -= 0.2;
        }

        if (denounce < 0.5) { // Success, remove old mission and establish ours
            return establishMission(serverPlayer, unit, settlement);
        }

        // Denounce failed
        Player owner = settlement.getOwner();
        cs.add(See.only(serverPlayer), settlement);
        cs.addMessage(See.only(serverPlayer),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                             "indianSettlement.mission.noDenounce",
                             serverPlayer, unit)
                .addStringTemplate("%nation%", owner.getNationName()));
        cs.addMessage(See.only(enemy),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                             "indianSettlement.mission.enemyDenounce",
                             enemy, settlement)
                .addStringTemplate("%enemy%", serverPlayer.getNationName())
                .addName("%settlement%", settlement.getNameFor(enemy))
                .addStringTemplate("%nation%", owner.getNationName()));
        cs.add(See.perhaps().always(serverPlayer),
               (FreeColGameObject) unit.getLocation());
        cs.addDispose(See.perhaps().always(serverPlayer),
            unit.getLocation(), unit);

        // Others can see missionary disappear
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Establish a new mission.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is establishing.
     * @param unit The missionary <code>Unit</code>.
     * @param settlement The <code>IndianSettlement</code> to establish at.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element establishMission(ServerPlayer serverPlayer, Unit unit,
                                    IndianSettlement settlement) {
        ChangeSet cs = new ChangeSet();
        csSpeakToChief(serverPlayer, settlement, false, cs);

        Unit missionary = settlement.getMissionary();
        if (missionary != null) {
            ServerPlayer enemy = (ServerPlayer) missionary.getOwner();
            enemy.csKillMissionary(settlement, cs);
        }

        // Result depends on tension wrt this settlement.
        // Establish if at least not angry.
        switch (settlement.getAlarm(serverPlayer).getLevel()) {
        case HATEFUL: case ANGRY:
            cs.add(See.perhaps().always(serverPlayer),
                (FreeColGameObject) unit.getLocation());
            cs.addDispose(See.perhaps().always(serverPlayer),
                unit.getLocation(), unit);
            break;
        case HAPPY: case CONTENT: case DISPLEASED:
            cs.add(See.perhaps().always(serverPlayer), unit.getTile());
            unit.setLocation(null);
            unit.setMovesLeft(0);
            cs.add(See.only(serverPlayer), unit);
            settlement.changeMissionary(unit);
            settlement.setConvertProgress(0);
            List<FreeColGameObject> modifiedSettlements
                = ((ServerIndianSettlement)settlement).modifyAlarm(serverPlayer,
                    ALARM_NEW_MISSIONARY);
            modifiedSettlements.remove(settlement);
            if (!modifiedSettlements.isEmpty()) {
                cs.add(See.only(serverPlayer), modifiedSettlements);
            }
            break;
        }
        Tile tile = settlement.getTile();
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.perhaps().always(serverPlayer), tile);
        String messageId = "indianSettlement.mission."
            + settlement.getAlarm(serverPlayer).getKey();
        cs.addMessage(See.only(serverPlayer),
            new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                             messageId, serverPlayer, unit)
                .addStringTemplate("%nation%", settlement.getOwner().getNationName()));

        // Others can see missionary disappear and settlement acquire
        // mission.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Incite a settlement against an enemy.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is inciting.
     * @param unit The missionary <code>Unit</code> inciting.
     * @param settlement The <code>IndianSettlement</code> to incite.
     * @param enemy The <code>Player</code> to be incited against.
     * @param gold The amount of gold in the bribe.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element incite(ServerPlayer serverPlayer, Unit unit,
                          IndianSettlement settlement,
                          Player enemy, int gold) {
        ChangeSet cs = new ChangeSet();

        Tile tile = settlement.getTile();
        csSpeakToChief(serverPlayer, settlement, false, cs);
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), tile);

        // How much gold will be needed?
        ServerPlayer enemyPlayer = (ServerPlayer) enemy;
        Player nativePlayer = settlement.getOwner();
        int payingValue = nativePlayer.getTension(serverPlayer).getValue();
        int targetValue = nativePlayer.getTension(enemyPlayer).getValue();
        int goldToPay = (payingValue > targetValue) ? 10000 : 5000;
        goldToPay += 20 * (payingValue - targetValue);
        goldToPay = Math.max(goldToPay, 650);

        // Try to incite?
        if (gold < 0) { // Initial enquiry.
            cs.addAttribute(See.only(serverPlayer),
                            "gold", Integer.toString(goldToPay));
        } else if (gold < goldToPay || !serverPlayer.checkGold(gold)) {
            cs.addMessage(See.only(serverPlayer),
                new ModelMessage(ModelMessage.MessageType.FOREIGN_DIPLOMACY,
                                 "indianSettlement.inciteGoldFail",
                                 serverPlayer, settlement)
                    .addStringTemplate("%player%", enemyPlayer.getNationName())
                    .addAmount("%amount%", goldToPay));
            cs.addAttribute(See.only(serverPlayer), "gold", "0");
            unit.setMovesLeft(0);
            cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        } else {
            // Success.  Raise the tension for the native player with respect
            // to the European player.  Let resulting stance changes happen
            // naturally in the AI player turn/s.
            cs.add(See.only(null).perhaps(enemyPlayer),
                   nativePlayer.modifyTension(enemyPlayer,
                                              Tension.WAR_MODIFIER));
            cs.add(See.only(null).perhaps(serverPlayer),
                   enemyPlayer.modifyTension(serverPlayer,
                                             Tension.TENSION_ADD_WAR_INCITER));
            cs.addAttribute(See.only(serverPlayer),
                            "gold", Integer.toString(gold));
            serverPlayer.modifyGold(-gold);
            nativePlayer.modifyGold(gold);
            cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
            unit.setMovesLeft(0);
            cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        }

        // Others might include enemy.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Set a unit destination.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to set the destination for.
     * @param destination The <code>Location</code> to set as destination.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setDestination(ServerPlayer serverPlayer, Unit unit,
                                  Location destination) {
        if (unit.getTradeRoute() != null) unit.setTradeRoute(null);
        unit.setDestination(destination);

        // Others can not see a destination change.
        return new ChangeSet().add(See.only(serverPlayer), unit)
            .build(serverPlayer);
    }


    /**
     * Set current stop of a unit to the next valid stop if any.
     *
     * @param serverPlayer The <code>ServerPlayer</code> the unit belongs to.
     * @param unit The <code>Unit</code> to update.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element updateCurrentStop(ServerPlayer serverPlayer, Unit unit) {
        // Check if there is a valid current stop?
        int current = unit.validateCurrentStop();
        if (current < 0) { // No valid stop
            unit.setTradeRoute(null);
            return new ChangeSet().add(See.only(serverPlayer), unit)
                .build(serverPlayer);
        }

        List<Stop> stops = unit.getTradeRoute().getStops();
        int next = current;
        for (;;) {
            if (++next >= stops.size()) next = 0;
            if (next == current) return null; // No work at any stop, stay put.
            Stop nextStop = stops.get(next);
            if (((ServerUnit) unit).hasWorkAtStop(nextStop)) break;
            logger.finest("Unit " + unit
                + " in trade route " + unit.getTradeRoute().getName()
                + " found no work at stop: " + (FreeColGameObject) nextStop.getLocation());
        }

        // Next is the updated stop.
        // Could do just a partial update of currentStop if we did not
        // also need to set the unit destination.
        unit.setCurrentStop(next);

        // Others can not see a stop change.
        return new ChangeSet().add(See.only(serverPlayer), unit)
            .build(serverPlayer);
    }


    /**
     * Buy from a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is buying.
     * @param unit The <code>Unit</code> that will carry the goods.
     * @param settlement The <code>IndianSettlement</code> to buy from.
     * @param goods The <code>Goods</code> to buy.
     * @param amount How much gold to pay.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element buyFromSettlement(ServerPlayer serverPlayer, Unit unit,
                                     IndianSettlement settlement,
                                     Goods goods, int amount) {
        ChangeSet cs = new ChangeSet();
        csSpeakToChief(serverPlayer, settlement, false, cs);

        TradeSession session
            = TradeSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("Trying to buy without opening a transaction session");
        }
        if (!session.getBuy()) {
            return DOMMessage.clientError("Trying to buy in a session where buying is not allowed.");
        }
        if (unit.getSpaceLeft() <= 0) {
            return DOMMessage.clientError("Unit is full, unable to buy.");
        }

        // Check that this is the agreement that was made
        AIPlayer ai = getFreeColServer().getAIPlayer(settlement.getOwner());
        int returnGold = ai.buyProposition(unit, settlement, goods, amount);
        if (returnGold != amount) {
            return DOMMessage.clientError("This was not the price we agreed upon! Cheater?");
        }
        if (!serverPlayer.checkGold(amount)) { // Check this is funded.
            return DOMMessage.clientError("Insufficient gold to buy.");
        }

        // Valid, make the trade.
        moveGoods(goods, unit);
        cs.add(See.perhaps(), unit);

        Player settlementPlayer = settlement.getOwner();
        Tile tile = settlement.getTile();
        settlement.updateWantedGoods();
        settlementPlayer.modifyGold(amount);
        serverPlayer.modifyGold(-amount);
        cs.add(See.only(serverPlayer),
            ((ServerIndianSettlement)settlement).modifyAlarm(serverPlayer,
                -amount / 50));
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), tile);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        session.setBuy();
        logger.finest(serverPlayer.getName() + " " + unit + " buys " + goods
                      + " at " + settlement.getName() + " for " + amount);

        // Others can see the unit capacity.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Sell to a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is selling.
     * @param unit The <code>Unit</code> carrying the goods.
     * @param settlement The <code>IndianSettlement</code> to sell to.
     * @param goods The <code>Goods</code> to sell.
     * @param amount How much gold to expect.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element sellToSettlement(ServerPlayer serverPlayer, Unit unit,
                                    IndianSettlement settlement,
                                    Goods goods, int amount) {
        ChangeSet cs = new ChangeSet();
        csSpeakToChief(serverPlayer, settlement, false, cs);

        TradeSession session
            = TransactionSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("Trying to sell without opening a transaction session");
        }
        if (!session.getSell()) {
            return DOMMessage.clientError("Trying to sell in a session where selling is not allowed.");
        }

        // Check that the gold is the agreed amount
        AIPlayer ai = getFreeColServer().getAIPlayer(settlement.getOwner());
        int returnGold = ai.sellProposition(unit, settlement, goods, amount);
        if (returnGold != amount) {
            return DOMMessage.clientError("This was not the price we agreed upon! Cheater?");
        }

        // Valid, make the trade.
        moveGoods(goods, settlement);
        cs.add(See.perhaps(), unit);

        Player settlementPlayer = settlement.getOwner();
        settlementPlayer.modifyGold(-amount);
        serverPlayer.modifyGold(amount);
        cs.add(See.only(serverPlayer),
            ((ServerIndianSettlement)settlement).modifyAlarm(serverPlayer,
                -amount / 500));
        Tile tile = settlement.getTile();
        settlement.updateWantedGoods();
        tile.updatePlayerExploredTile(serverPlayer, true);
        cs.add(See.only(serverPlayer), tile);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        session.setSell();
        cs.addSale(serverPlayer, settlement, goods.getType(),
                   (int) Math.round((float) amount / goods.getAmount()));
        logger.finest(serverPlayer.getName() + " " + unit + " sells " + goods
                      + " at " + settlement.getName() + " for " + amount);

        // Others can see the unit capacity.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Deliver gift to settlement.
     * Note that this includes both European and native gifts.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is delivering.
     * @param unit The <code>Unit</code> that is delivering.
     * @param goods The <code>Goods</code> to deliver.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element deliverGiftToSettlement(ServerPlayer serverPlayer,
                                           Unit unit, Settlement settlement,
                                           Goods goods) {
        TradeSession session
            = TransactionSession.lookup(TradeSession.class, unit, settlement);
        if (session == null) {
            return DOMMessage.clientError("Trying to deliver gift without opening a session");
        }
        if (!session.getGift()) {
            return DOMMessage.clientError("Trying to deliver gift in a session where gift giving is not allowed: " + unit + " " + settlement + " " + session);
        }

        ChangeSet cs = new ChangeSet();
        Tile tile = settlement.getTile();
        moveGoods(goods, settlement);
        cs.add(See.perhaps(), unit);
        if (settlement instanceof IndianSettlement) {
            IndianSettlement is = (IndianSettlement) settlement;
            csSpeakToChief(serverPlayer, is, false, cs);
            cs.add(See.only(serverPlayer),
                ((ServerIndianSettlement)is).modifyAlarm(serverPlayer,
                    -is.getPriceToBuy(goods) / 50));
            is.updateWantedGoods();
            tile.updatePlayerExploredTile(serverPlayer, true);
            cs.add(See.only(serverPlayer), tile);
        }
        session.setGift();

        // Inform the receiver of the gift.
        ModelMessage m = new ModelMessage(ModelMessage.MessageType.GIFT_GOODS,
                                          "model.unit.gift",
                                          settlement, goods.getType())
            .addStringTemplate("%player%", serverPlayer.getNationName())
            .add("%type%", goods.getNameKey())
            .addAmount("%amount%", goods.getAmount())
            .addName("%settlement%", settlement.getName());
        cs.addMessage(See.only(serverPlayer), m);
        ServerPlayer receiver = (ServerPlayer) settlement.getOwner();
        if (receiver.isConnected() && settlement instanceof Colony) {
            cs.add(See.only(receiver), unit);
            cs.add(See.only(receiver), settlement);
            cs.addMessage(See.only(receiver), m);
        }
        logger.info("Gift delivered by unit: " + unit.getId()
                    + " to settlement: " + settlement.getName());

        // Others can see unit capacity, receiver gets it own items.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Load cargo.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is loading.
     * @param unit The <code>Unit</code> to load.
     * @param goods The <code>Goods</code> to load.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element loadCargo(ServerPlayer serverPlayer, Unit unit,
                             Goods goods) {
        ChangeSet cs = new ChangeSet();
        Location oldLocation = goods.getLocation();

        goods.adjustAmount();
        moveGoods(goods, unit);
        boolean moved = false;
        if (unit.getInitialMovesLeft() != unit.getMovesLeft()) {
            unit.setMovesLeft(0);
            moved = true;
        }
        Unit oldUnit = null;
        if (oldLocation instanceof Unit) {
            oldUnit = (Unit) oldLocation;
            if (oldUnit.getInitialMovesLeft() != oldUnit.getMovesLeft()) {
                oldUnit.setMovesLeft(0);
            } else {
                oldUnit = null;
            }
        }

        // Update the goodsContainers only if within a settlement,
        // otherwise update the shared location so that others can
        // see capacity changes.
        if (unit.getSettlement() != null) {
            cs.add(See.only(serverPlayer), oldLocation.getGoodsContainer());
            cs.add(See.only(serverPlayer), unit.getGoodsContainer());
            if (moved) {
                cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
            }
            if (oldUnit != null) {
                cs.addPartial(See.only(serverPlayer), oldUnit, "movesLeft");
            }
        } else {
            cs.add(See.perhaps(), (FreeColGameObject) unit.getLocation());
        }

        // Others might see capacity change.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Unload cargo.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is unloading.
     * @param unit The <code>Unit</code> to unload.
     * @param goods The <code>Goods</code> to unload.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element unloadCargo(ServerPlayer serverPlayer, Unit unit,
                               Goods goods) {
        ChangeSet cs = new ChangeSet();

        Location loc;
        Settlement settlement = null;
        if (unit.isInEurope()) { // Must be a dump of boycotted goods
            loc = null;
        } else if (unit.getTile() == null) {
            return DOMMessage.clientError("Unit not on the map.");
        } else if (unit.getSettlement() != null) {
            settlement = unit.getTile().getSettlement();
            loc = settlement;
        } else { // Dump of goods onto a tile
            loc = null;
        }
        goods.adjustAmount();
        moveGoods(goods, loc);
        boolean moved = false;
        if (unit.getInitialMovesLeft() != unit.getMovesLeft()) {
            unit.setMovesLeft(0);
            moved = true;
        }

        if (settlement != null) {
            cs.add(See.only(serverPlayer), settlement.getGoodsContainer());
            cs.add(See.only(serverPlayer), unit.getGoodsContainer());
            if (moved) cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        } else {
            cs.add(See.perhaps(), (FreeColGameObject) unit.getLocation());
        }

        // Others might see a capacity change.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Clear the specialty of a unit.
     *
     * @param serverPlayer The owner of the unit.
     * @param unit The <code>Unit</code> to clear the speciality of.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element clearSpeciality(ServerPlayer serverPlayer, Unit unit) {
        UnitType newType = unit.getTypeChange(ChangeType.CLEAR_SKILL,
                                              serverPlayer);
        if (newType == null) {
            return DOMMessage.clientError("Can not clear unit speciality: "
                + unit.getId());
        }

        // There can be some restrictions that may prevent the
        // clearing of the speciality.  AFAICT the only ATM is that a
        // teacher can not lose its speciality, but this will need to
        // be revisited if we invent a work location that requires a
        // particular unit type.
        if (unit.getStudent() != null) {
            return DOMMessage.clientError("Can not clear speciality of a teacher.");
        }

        // Valid, change type.
        unit.setType(newType);

        // Update just the unit, others can not see it as this only happens
        // in-colony.  TODO: why not clear speciality in the open,
        // you can disband...
        return new ChangeSet().add(See.only(serverPlayer), unit)
            .build(serverPlayer);
    }


    /**
     * Disband a unit.
     * 
     * @param serverPlayer The owner of the unit.
     * @param unit The <code>Unit</code> to disband.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element disbandUnit(ServerPlayer serverPlayer, Unit unit) {
        ChangeSet cs = new ChangeSet();

        // Dispose of the unit.
        cs.add(See.perhaps().always(serverPlayer),
               (FreeColGameObject) unit.getLocation());
        cs.addDispose(See.perhaps().always(serverPlayer),
            unit.getLocation(), unit);

        // Others can see the unit removal and the space it leaves.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Build a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is building.
     * @param unit The <code>Unit</code> that is building.
     * @param name The new settlement name.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element buildSettlement(ServerPlayer serverPlayer, Unit unit,
                                   String name) {
        ChangeSet cs = new ChangeSet();
        Game game = serverPlayer.getGame();

        // Build settlement
        Tile tile = unit.getTile();
        Settlement settlement;
        if (Player.ASSIGN_SETTLEMENT_NAME.equals(name)) {
            name = serverPlayer.getSettlementName();
            if (Player.ASSIGN_SETTLEMENT_NAME.equals(name)) {
                // Load settlement names on demand.
                serverPlayer.installSettlementNames(Messages
                        .getSettlementNames(serverPlayer), random);
                name = serverPlayer.getSettlementName();
            }
        }
        if (serverPlayer.isEuropean()) {
            settlement = new ServerColony(game, serverPlayer, name, tile);
            serverPlayer.addSettlement(settlement);
            settlement.placeSettlement(serverPlayer.isAI());
        } else {
            IndianNationType nationType
                = (IndianNationType) serverPlayer.getNationType();
            UnitType skill = RandomChoice.getWeightedRandom(logger,
                    "Choose skill", random,
                    nationType.generateSkillsForTile(tile));
            if (skill == null) { // Seasoned Scout
                List<UnitType> scouts = getGame().getSpecification()
                    .getUnitTypesWithAbility(Ability.EXPERT_SCOUT);
                skill = Utils.getRandomMember(logger, "Choose scout",
                                              scouts, random);
            }
            settlement = new ServerIndianSettlement(game, serverPlayer, name,
                                                    tile, false, skill,
                                                    new HashSet<Player>(),
                                                    null);
            serverPlayer.addSettlement(settlement);
            settlement.placeSettlement(true);
            // TODO: its lame that the settlement starts with no contacts
        }

        // Join.  Remove equipment first in case role confuses placement.
        ((ServerUnit)unit).csRemoveEquipment(settlement,
            new HashSet<EquipmentType>(unit.getEquipment().keySet()),
            0, random, cs);
        unit.setLocation(settlement);
        unit.setMovesLeft(0);

        // Update with settlement tile, and newly owned tiles.
        List<FreeColGameObject> tiles = new ArrayList<FreeColGameObject>();
        tiles.addAll(settlement.getOwnedTiles());
        cs.add(See.perhaps(), tiles);

        cs.addHistory(serverPlayer, new HistoryEvent(game.getTurn(),
                HistoryEvent.EventType.FOUND_COLONY)
            .addName("%colony%", settlement.getName()));

        // Also send any tiles that can now be seen because the colony
        // can perhaps see further than the founding unit.
        if (settlement.getLineOfSight() > unit.getLineOfSight()) {
            tiles.clear();
            for (Tile t : tile.getSurroundingTiles(unit.getLineOfSight()+1,
                                                  settlement.getLineOfSight())) {
                if (!tiles.contains(t)) tiles.add(t);
            }
            cs.add(See.only(serverPlayer), tiles);
        }

        // Others can see tile changes.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }

    /**
     * Join a colony.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> that is joining.
     * @param colony The <code>Colony</code> to join.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element joinColony(ServerPlayer serverPlayer, Unit unit,
                              Colony colony) {
        ChangeSet cs = new ChangeSet();
        List<Tile> ownedTiles = colony.getOwnedTiles();
        Tile tile = colony.getTile();

        // Join.
        unit.setLocation(colony);
        unit.setMovesLeft(0);
        ((ServerUnit)unit).csRemoveEquipment(colony,
            new HashSet<EquipmentType>(unit.getEquipment().keySet()),
            0, random, cs);

        // Update with colony tile, and tiles now owned.
        cs.add(See.only(serverPlayer), tile);
        for (Tile t : tile.getSurroundingTiles(colony.getRadius())) {
            if (t.getOwningSettlement() == colony && !ownedTiles.contains(t)) {
                cs.add(See.perhaps(), t);
            }
        }

        // Others might see a tile ownership change.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Abandon a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is abandoning.
     * @param settlement The <code>Settlement</code> to abandon.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element abandonSettlement(ServerPlayer serverPlayer,
                                     Settlement settlement) {
        ChangeSet cs = new ChangeSet();

        // Collect the tiles the settlement owns before disposing,
        // except the center tile as that is in the dispose below.
        for (Tile t : settlement.getOwnedTiles()) {
            if (t != settlement.getTile()) cs.add(See.perhaps(), t);
        }

        // Create history event before disposing.
        if (settlement instanceof Colony) {
            cs.addHistory(serverPlayer,
                new HistoryEvent(getGame().getTurn(),
                                 HistoryEvent.EventType.ABANDON_COLONY)
                    .addName("%colony%", settlement.getName()));
        }

        // Now do the dispose.
        cs.add(See.perhaps().always(serverPlayer), settlement.getTile());
        cs.addDispose(See.perhaps().always(serverPlayer),
            settlement.getTile(), settlement);

        // TODO: Player.settlements is still being fixed on the client side.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Claim land.
     *
     * @param serverPlayer The <code>ServerPlayer</code> claiming.
     * @param tile The <code>Tile</code> to claim.
     * @param settlement The <code>Settlement</code> to claim for.
     * @param price The price to pay for the land, which must agree
     *     with the owner valuation, unless negative which denotes stealing.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element claimLand(ServerPlayer serverPlayer, Tile tile,
                             Settlement settlement, int price) {
        ChangeSet cs = new ChangeSet();
        serverPlayer.csClaimLand(tile, settlement, price, cs);

        // Others can see the tile.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Accept a diplomatic trade.  Handles the transfers of TradeItems.
     *
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> to trade with.
     * @param agreement The <code>DiplomacyTrade</code> agreement.
     * @param cs A <code>ChangeSet</code> to update.
     */
    private void csAcceptTrade(Unit unit, Settlement settlement,
                               DiplomaticTrade agreement, ChangeSet cs) {
        ServerPlayer srcPlayer = (ServerPlayer) agreement.getSender();
        ServerPlayer dstPlayer = (ServerPlayer) agreement.getRecipient();

        for (TradeItem tradeItem : agreement.getTradeItems()) {
            // Check trade carefully before committing.
            if (!tradeItem.isValid()) {
                logger.warning("Trade with invalid tradeItem: "
                               + tradeItem.toString());
                continue;
            }
            ServerPlayer source = (ServerPlayer) tradeItem.getSource();
            if (source != srcPlayer && source != dstPlayer) {
                logger.warning("Trade with invalid source: "
                               + ((source == null) ? "null" : source.getId()));
                continue;
            }
            ServerPlayer dest = (ServerPlayer) tradeItem.getDestination();
            if (dest != srcPlayer && dest != dstPlayer) {
                logger.warning("Trade with invalid destination: "
                               + ((dest == null) ? "null" : dest.getId()));
                continue;
            }

            // Collect changes for updating.  Not very OO but
            // TradeItem should not know about server internals.
            // Take care to show items that change hands to the *old*
            // owner too.
            Stance stance = tradeItem.getStance();
            if (stance != null
                && !source.csChangeStance(stance, dest, true, cs)) {
                logger.warning("Stance trade failure");
            }
            Colony colony = tradeItem.getColony();
            if (colony != null) {
                ServerPlayer former = (ServerPlayer) colony.getOwner();
                colony.changeOwner(dest);
                List<FreeColGameObject> tiles
                    = new ArrayList<FreeColGameObject>();
                tiles.addAll(colony.getOwnedTiles());
                cs.add(See.perhaps().always(former), tiles);
            }
            int gold = tradeItem.getGold();
            if (gold > 0) {
                source.modifyGold(-gold);
                dest.modifyGold(gold);
                cs.addPartial(See.only(source), source, "gold", "score");
                cs.addPartial(See.only(dest), dest, "gold", "score");
            }
            Goods goods = tradeItem.getGoods();
            if (goods != null) {
                moveGoods(goods, settlement);
                cs.add(See.only(source), unit);
                cs.add(See.only(dest), settlement.getGoodsContainer());
            }
            Unit newUnit = tradeItem.getUnit();
            if (newUnit != null) {
                ServerPlayer former = (ServerPlayer) newUnit.getOwner();
                unit.setOwner(dest);
                cs.add(See.perhaps().always(former), newUnit);
            }
        }
    }

    /**
     * Diplomatic trades.
     * Note that when an agreement is accepted we always process the
     * agreement that was sent, not what was returned, cutting off a
     * possibility for cheating.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is trading.
     * @param unit The <code>Unit</code> that is trading.
     * @param settlement The <code>Settlement</code> to trade with.
     * @param agreement The <code>DiplomaticTrade</code> to consider.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element diplomaticTrade(ServerPlayer serverPlayer, Unit unit,
                                   Settlement settlement,
                                   DiplomaticTrade agreement) {
        ChangeSet cs = new ChangeSet();
        ServerPlayer otherPlayer = (ServerPlayer) settlement.getOwner();
        DiplomacySession session
            = TransactionSession.lookup(DiplomacySession.class,
                unit, settlement);
        TradeStatus status;
        Player.makeContact(serverPlayer, otherPlayer);

        switch (status = agreement.getStatus()) {
        case PROPOSE_TRADE:
            if (session == null) {
                Unit.MoveType type = unit.getMoveType(settlement.getTile());
                if (type != Unit.MoveType.ENTER_FOREIGN_COLONY_WITH_SCOUT) {
                        return DOMMessage.clientError("Unable to enter "
                            + settlement.getId() + ": " + type.whyIllegal());
                }
                session = new DiplomacySession(unit, settlement);
                unit.setMovesLeft(0);
                cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
            }
            session.setAgreement(agreement);
            break;
        case ACCEPT_TRADE:
            if (session == null) {
                return DOMMessage.clientError("Accept-trade with no session");
            }
            agreement = session.getAgreement();
            agreement.setStatus(status);
            csAcceptTrade(unit, settlement, agreement, cs);
            break;
        case REJECT_TRADE:
            if (session == null) {
                return DOMMessage.clientError("Reject-trade with no session");
            }
            agreement = session.getAgreement();
            agreement.setStatus(status);
            break;
        default:
            return DOMMessage.clientError("Bogus trade status: " + status);
        }
            
        DOMMessage reply = askTimeout(otherPlayer,
            new DiplomacyMessage(unit, settlement, agreement));
        DiplomaticTrade theirAgreement = (reply instanceof DiplomacyMessage)
            ? ((DiplomacyMessage)reply).getAgreement()
            : null;
        if (status != TradeStatus.PROPOSE_TRADE) {
            session.complete(cs);
            sendToOthers(serverPlayer, cs);
            return cs.build(serverPlayer);
        }
        status = (theirAgreement == null) ? TradeStatus.REJECT_TRADE
            : theirAgreement.getStatus();
        switch (status) {
        case PROPOSE_TRADE:
            session.setAgreement(agreement = theirAgreement);
            break;
        case ACCEPT_TRADE:
            agreement.setStatus(status);
            csAcceptTrade(unit, settlement, agreement, cs);
            session.complete(cs);
            break;
        case REJECT_TRADE:
            agreement.setStatus(status);
            session.complete(cs);
            break;
        default:
            logger.warning("Bogus trade status: " + status);
            break;
        }
        // Update *everyone* with the result, *and* return a
        // DiplomacyMessage to the originating player because that is
        // what ServerAPI.askDiplomacy is expecting.
        sendToAll(cs);

        return new ChangeSet()
            .add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                new DiplomacyMessage(unit, settlement, agreement))
            .build(serverPlayer);
    }

    /**
     * Spy on a settlement.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is spying.
     * @param unit The <code>Unit</code> that is spying.
     * @param settlement The <code>Settlement</code> to spy on.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element spySettlement(ServerPlayer serverPlayer, Unit unit,
                                 Settlement settlement) {
        ChangeSet cs = new ChangeSet();

        cs.addSpy(See.only(serverPlayer), settlement);
        unit.setMovesLeft(0);
        cs.addPartial(See.only(serverPlayer), unit, "movesLeft");
        return cs.build(serverPlayer);
    }


    /**
     * Change work location.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to change the work location of.
     * @param workLocation The <code>WorkLocation</code> to change to.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element work(ServerPlayer serverPlayer, Unit unit,
                        WorkLocation workLocation) {
        ChangeSet cs = new ChangeSet();
        Colony colony = workLocation.getColony();
        colony.getGoodsContainer().saveState();

        if (workLocation instanceof ColonyTile) {
            Tile tile = ((ColonyTile) workLocation).getWorkTile();
            if (tile.getOwningSettlement() != colony) {
                // Claim known free land (because canAdd() succeeded).
                serverPlayer.csClaimLand(tile, colony, 0, cs);
            }
        }

        // Remove any unit equipment
        if (!unit.getEquipment().isEmpty()) {
            ((ServerUnit)unit).csRemoveEquipment(colony,
                new HashSet<EquipmentType>(unit.getEquipment().keySet()),
                0, random, cs);
          
        }

        // Check for upgrade.
        UnitType oldType = unit.getType();
        UnitTypeChange change = oldType
            .getUnitTypeChange(ChangeType.ENTER_COLONY, unit.getOwner());
        if (change != null) {
            unit.setType(change.getNewUnitType());
        }

        // Change the location.
        // We could avoid updating the whole tile if we knew that this
        // was definitely a move between locations and no student/teacher
        // interaction occurred.
        unit.setLocation(workLocation);
        cs.add(See.perhaps(), colony.getTile());
        // Others can see colony change size
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Loot cargo.
     *
     * Note loser is passed by id, as by the time we get here the unit
     * may have been sunk.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the winner.
     * @param winner The <code>Unit</code> that looting.
     * @param loserId The id of the <code>Unit</code> that is looted.
     * @param loot The <code>Goods</code> to loot.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element lootCargo(ServerPlayer serverPlayer, Unit winner,
                             String loserId, List<Goods> loot) {
        LootSession session = TransactionSession.lookup(LootSession.class,
            winner.getId(), loserId);
        if (session == null) {
            return DOMMessage.clientError("Bogus looting!");
        }
        if (winner.getSpaceLeft() == 0) {
            return DOMMessage.clientError("No space to loot to: "
                + winner.getId());
        }

        ChangeSet cs = new ChangeSet();
        List<Goods> available = session.getCapture();
        if (loot == null) { // Initial inquiry
            cs.add(See.only(serverPlayer), ChangePriority.CHANGE_LATE,
                   new LootCargoMessage(winner, loserId, available));
        } else {
            for (Goods g : loot) {
                if (!available.contains(g)) {
                    return DOMMessage.clientError("Invalid loot: "
                        + g.toString());
                }
                available.remove(g);
                if (!winner.canAdd(g)) {
                    return DOMMessage.clientError("Loot failed: "
                        + g.toString());
                }
                winner.add(g);
            }

            // Others can see cargo capacity change.
            session.complete(cs);
            cs.add(See.perhaps(), winner);
            sendToOthers(serverPlayer, cs);
        }
        return cs.build(serverPlayer);
    }


    /**
     * Pay arrears.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param type The <code>GoodsType</code> to pay the arrears for.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element payArrears(ServerPlayer serverPlayer, GoodsType type) {
        int arrears = serverPlayer.getArrears(type);
        if (arrears <= 0) {
            return DOMMessage.clientError("No arrears for pay for: "
                + type.getId());
        } else if (!serverPlayer.checkGold(arrears)) {
            return DOMMessage.clientError("Not enough gold to pay arrears for: "
                + type.getId());
        }

        ChangeSet cs = new ChangeSet();
        Market market = serverPlayer.getMarket();
        serverPlayer.modifyGold(-arrears);
        market.setArrears(type, 0);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        cs.add(See.only(serverPlayer), market.getMarketData(type));
        // Arrears payment is private.
        return cs.build(serverPlayer);
    }


    /**
     * Equip a unit.
     * Currently the unit is either in Europe or in a settlement.
     * Might one day allow the unit to be on a tile co-located with
     * an equipment-bearing wagon.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to equip.
     * @param type The <code>EquipmentType</code> to equip with.
     * @param amount The change in the amount of equipment.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element equipUnit(ServerPlayer serverPlayer, Unit unit,
                             EquipmentType type, int amount) {
        Settlement settlement = (unit.getTile() == null) ? null
            : unit.getSettlement();
        GoodsContainer container = null;
        boolean tileDirty = false;
        if (unit.isInEurope()) {
            // Refuse to trade in boycotted goods
            for (AbstractGoods goods : type.getGoodsRequired()) {
                GoodsType goodsType = goods.getType();
                if (!serverPlayer.canTrade(goodsType)) {
                    return DOMMessage.clientError("No equip of " + type.getId()
                        + " due to boycott of " + goodsType.getId());
                }
            }
            // Will need a fake container to contain the goods to buy
            // in Europe.  Units may not necessarily have one.
            container = new GoodsContainer(getGame(), serverPlayer.getEurope());
        } else if (settlement != null) {
            // Equipping a unit at work in a colony should remove the unit
            // from the work location.
            if (unit.getLocation() instanceof WorkLocation) {
                unit.setLocation(settlement.getTile());
                tileDirty = true;
            }
            settlement.getGoodsContainer().saveState();
        }

        ChangeSet cs = new ChangeSet();
        List<EquipmentType> remove = null;
        // Process adding equipment first, so as to settle what has to
        // be removed.
        if (amount > 0) {
            for (AbstractGoods goods : type.getGoodsRequired()) {
                GoodsType goodsType = goods.getType();
                int n = amount * goods.getAmount();
                if (unit.isInEurope()) {
                    try {
                        serverPlayer.buy(container, goodsType, n, random);
                        serverPlayer.csFlushMarket(goodsType, cs);
                    } catch (IllegalStateException e) {
                        return DOMMessage.clientError(e.getMessage());
                    }
                } else if (settlement != null) {
                    if (settlement.getGoodsCount(goodsType) < n) {
                        return DOMMessage.clientError("Failed to equip: "
                            + unit.getId() + " not enough " + goodsType
                            + " in settlement " + settlement.getId());
                    }
                    settlement.removeGoods(goodsType, n);
                }
            }
            remove = unit.changeEquipment(type, amount);
            amount = 0; // 0 => all, now
        } else if (amount < 0) {
            remove = new ArrayList<EquipmentType>();
            remove.add(type);
            amount = -amount;
        } else {
            return null; // Nothing to do.
        }

        // Now do removal of equipment.
        ((ServerUnit)unit).csRemoveEquipment(settlement, remove, amount,
                                             random, cs);

        // Nothing for others to see except if the settlement population
        // changes.
        // If in Europe, we can get away with just updating the unit
        // as sell() will have added sales changes.  In a settlement,
        // the goods container will always be dirty, but the whole tile
        // will only need to be updated if the unit moved into it.
        if (unit.getInitialMovesLeft() != unit.getMovesLeft()) {
            unit.setMovesLeft(0);
        }
        if (unit.isInEurope()) {
            cs.add(See.only(serverPlayer), unit);
            cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        } else if (settlement != null) {
            if (tileDirty) {
                cs.add(See.perhaps(), settlement.getTile());
            } else {
                cs.add(See.only(serverPlayer), unit,
                       settlement.getGoodsContainer());
            }
        }
        return cs.build(serverPlayer);
    }


    /**
     * Pay for a building.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the colony.
     * @param colony The <code>Colony</code> that is building.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element payForBuilding(ServerPlayer serverPlayer, Colony colony) {
        BuildableType build = colony.getCurrentlyBuilding();
        if (build == null) {
            return DOMMessage.clientError("Colony " + colony.getId()
                + " is not building anything!");
        }
        HashMap<GoodsType, Integer> required
            = colony.getGoodsForBuilding(build);
        int price = colony.priceGoodsForBuilding(required);
        if (!serverPlayer.checkGold(price)) {
            return DOMMessage.clientError("Insufficient funds to pay for build.");
        }

        // Save the correct final gold for the player, as we are going to
        // use buy() below, but it deducts the normal uninflated price for
        // the goods being bought.  We restore this correct amount later.
        int savedGold = serverPlayer.modifyGold(-price);
        serverPlayer.modifyGold(price);

        ChangeSet cs = new ChangeSet();
        GoodsContainer container = colony.getGoodsContainer();
        container.saveState();
        for (GoodsType type : required.keySet()) {
            int amount = required.get(type);
            if (type.isStorable()) {
                // TODO: should also check canTrade(type, Access.?)
                serverPlayer.buy(container, type, amount, random);
                serverPlayer.csFlushMarket(type, cs);
            } else {
                container.addGoods(type, amount);
            }
        }
        colony.invalidateCache();

        // Nothing to see for others, colony internal.
        serverPlayer.setGold(savedGold);
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        cs.add(See.only(serverPlayer), container);
        return cs.build(serverPlayer);
    }


    /**
     * Indians making demands of a colony.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is demanding.
     * @param unit The <code>Unit</code> making the demands.
     * @param colony The <code>Colony</code> that is demanded of.
     * @param goods The <code>Goods</code> being demanded.
     * @param gold The amount of gold being demanded.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element indianDemand(final ServerPlayer serverPlayer, Unit unit,
                                Colony colony, Goods goods, int gold) {
        ServerPlayer victim = (ServerPlayer) colony.getOwner();
        int difficulty = getGame().getSpecification()
            .getIntegerOption("model.option.nativeDemands").getValue();
        ChangeSet cs = new ChangeSet();

        DOMMessage reply = askTimeout(victim,
            new IndianDemandMessage(unit, colony, goods, gold));
        boolean result = (reply instanceof IndianDemandMessage)
            ? ((IndianDemandMessage)reply).getResult()
            : false;
        logger.info(serverPlayer.getName() + " unit " + unit
            + " demands " + goods + " goods and " + gold + " gold "
            + " from " + colony.getName() + " accepted: " + result);

        IndianDemandMessage message = new IndianDemandMessage(unit, colony,
                                                              goods, gold);
        message.setResult(result);
        cs.add(See.only(serverPlayer), ChangePriority.CHANGE_NORMAL, message);
        if (result) {
            if (goods != null) {
                GoodsContainer colonyContainer = colony.getGoodsContainer();
                colonyContainer.saveState();
                GoodsContainer unitContainer = unit.getGoodsContainer();
                unitContainer.saveState();
                moveGoods(goods, unit);
                cs.add(See.only(victim), colonyContainer);
                //cs.add(See.only(serverPlayer), unitContainer);
            }
            if (gold > 0) {
                victim.modifyGold(-gold);
                serverPlayer.modifyGold(gold);
                cs.addPartial(See.only(victim), victim, "gold");
                //cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
            }
            cs.add(See.only(null).perhaps(victim),
                serverPlayer.modifyTension(victim, -(5 - difficulty) * 50));
        }

        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Train a unit in Europe.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is demanding.
     * @param type The <code>UnitType</code> to train.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element trainUnitInEurope(ServerPlayer serverPlayer, UnitType type) {
        Europe europe = serverPlayer.getEurope();
        if (europe == null) {
            return DOMMessage.clientError("No Europe to train in.");
        }
        int price = europe.getUnitPrice(type);
        if (price <= 0) {
            return DOMMessage.clientError("Bogus price: " + price);
        } else if (!serverPlayer.checkGold(price)) {
            return DOMMessage.clientError("Not enough gold to train " + type);
        }

        new ServerUnit(getGame(), europe, serverPlayer, type);
        serverPlayer.modifyGold(-price);
        ((ServerEurope) europe).increasePrice(type, price);

        // Only visible in Europe
        ChangeSet cs = new ChangeSet();
        cs.addPartial(See.only(serverPlayer), serverPlayer, "gold");
        cs.add(See.only(serverPlayer), europe);
        return cs.build(serverPlayer);
    }


    /**
     * Set build queue.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the colony.
     * @param colony The <code>Colony</code> to set the queue of.
     * @param queue The new build queue.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setBuildQueue(ServerPlayer serverPlayer, Colony colony,
                                 List<BuildableType> queue) {
        colony.setBuildQueue(queue);

        // Only visible to player.
        ChangeSet cs = new ChangeSet();
        cs.add(See.only(serverPlayer), colony);
        return cs.build(serverPlayer);
    }


    /**
     * Set goods levels.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the colony.
     * @param colony The <code>Colony</code> to set the goods levels in.
     * @param exportData The new <code>ExportData</code>.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setGoodsLevels(ServerPlayer serverPlayer, Colony colony,
                                  ExportData exportData) {
        colony.setExportData(exportData);
        return new ChangeSet().add(See.only(serverPlayer), colony)
            .build(serverPlayer);
    }


    /**
     * Put outside colony.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to be put out.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element putOutsideColony(ServerPlayer serverPlayer, Unit unit) {
        Tile tile = unit.getTile();
        Colony colony = unit.getColony();
        unit.setLocation(tile);

        // Full tile update for the player, the rest get their limited
        // view of the colony so that population changes.
        ChangeSet cs = new ChangeSet();
        cs.add(See.only(serverPlayer), tile);
        cs.add(See.perhaps().except(serverPlayer), colony);
        return cs.build(serverPlayer);
    }


    /**
     * Change work type.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to change the work type of.
     * @param type The new <code>GoodsType</code> to produce.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element changeWorkType(ServerPlayer serverPlayer, Unit unit,
                                  GoodsType type) {
        if (unit.getWorkType() != type) {
            unit.setExperience(0);
            unit.setWorkType(type);
        }

        // Private update of the unit.
        return new ChangeSet().add(See.only(serverPlayer), unit)
            .build(serverPlayer);
    }


    /**
     * Change improvement work type.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to change the work type of.
     * @param type The new <code>TileImprovementType</code> to produce.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element changeWorkImprovementType(ServerPlayer serverPlayer,
                                             Unit unit,
                                             TileImprovementType type) {
        Tile tile = unit.getTile();
        TileImprovement improvement = tile.findTileImprovementType(type);
        if (improvement == null) { // Create the new improvement.
            improvement = new TileImprovement(getGame(), tile, type);
            tile.add(improvement);
        }

        unit.setWorkImprovement(improvement);
        unit.setState(UnitState.IMPROVING);

        // Private update of the tile.
        return new ChangeSet().add(See.only(serverPlayer), tile)
            .build(serverPlayer);
    }


    /**
     * Change a units state.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The <code>Unit</code> to change the state of.
     * @param state The new <code>UnitState</code>.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element changeState(ServerPlayer serverPlayer, Unit unit,
                               UnitState state) {
        ChangeSet cs = new ChangeSet();

        if (state == UnitState.FORTIFYING) {
            Tile tile = unit.getTile();
            ServerColony colony
                = (tile.getOwningSettlement() instanceof Colony)
                ? (ServerColony) tile.getOwningSettlement()
                : null;
            if (colony != null
                && colony.getOwner() != unit.getOwner()
                && colony.isTileInUse(tile)) {
                colony.csEvictUser(unit, cs);
            }
        }

        unit.setState(state);
        cs.add(See.perhaps(), unit.getTile());

        // Others might be able to see the unit.
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }


    /**
     * Assign a student to a teacher.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param student The student <code>Unit</code>.
     * @param teacher The teacher <code>Unit</code>.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element assignTeacher(ServerPlayer serverPlayer, Unit student,
                                 Unit teacher) {
        Unit oldStudent = teacher.getStudent();
        Unit oldTeacher = student.getTeacher();

        // Only update units that changed their teaching situation.
        ChangeSet cs = new ChangeSet();
        if (oldTeacher != null) {
            oldTeacher.setStudent(null);
            cs.add(See.only(serverPlayer), oldTeacher);
        }
        if (oldStudent != null) {
            oldStudent.setTeacher(null);
            cs.add(See.only(serverPlayer), oldStudent);
        }
        teacher.setStudent(student);
        teacher.setWorkType(null);
        student.setTeacher(teacher);
        cs.add(See.only(serverPlayer), student, teacher);
        return cs.build(serverPlayer);
    }


    /**
     * Assign a trade route to a unit.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that owns the unit.
     * @param unit The unit <code>Unit</code> to assign to.
     * @param tradeRoute The <code>TradeRoute</code> to assign.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element assignTradeRoute(ServerPlayer serverPlayer, Unit unit,
                                    TradeRoute tradeRoute) {
        unit.setTradeRoute(tradeRoute);
        unit.setDestination(null);
        if (tradeRoute != null) {
            List<Stop> stops = tradeRoute.getStops();
            int found = -1;
            for (int i = 0; i < stops.size(); i++) {
                if (unit.getLocation() == stops.get(i).getLocation()) {
                    found = i;
                    break;
                }
            }
            if (found < 0) found = 0;
            unit.setCurrentStop(found);
        }

        // Only visible to the player
        return new ChangeSet().add(See.only(serverPlayer), unit)
            .build(serverPlayer);
    }

    /**
     * Set trade routes for a player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to set trade
     *    routes for.
     * @param routes The new list of <code>TradeRoute</code>s.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element setTradeRoutes(ServerPlayer serverPlayer,
                                  List<TradeRoute> routes) {
        serverPlayer.setTradeRoutes(routes);
        // Have to update the whole player alas.
        return new ChangeSet().add(See.only(serverPlayer), serverPlayer)
            .build(serverPlayer);
    }

    /**
     * Get a new trade route for a player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to get a trade
     *    route for.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getNewTradeRoute(ServerPlayer serverPlayer) {
        List<TradeRoute> routes
            = new ArrayList<TradeRoute>(serverPlayer.getTradeRoutes());
        TradeRoute route = new TradeRoute(getGame(), "", serverPlayer);
        routes.add(route);
        serverPlayer.setTradeRoutes(routes);
        return new ChangeSet().addTradeRoute(serverPlayer, route)
            .build(serverPlayer);
    }


    /**
     * Get a list of abstract REF units for a player.
     *
     * @param serverPlayer The <code>ServerPlayer</code> to query the REF of.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getREFUnits(ServerPlayer serverPlayer) {
        Game game = getGame();
        Specification spec = game.getSpecification();
        List<AbstractUnit> units = new ArrayList<AbstractUnit>();
        final UnitType defaultType = spec.getDefaultUnitType();

        if (serverPlayer.getMonarch() == null) {
            ServerPlayer REFPlayer = (ServerPlayer) serverPlayer.getREFPlayer();
            java.util.Map<UnitType, EnumMap<Role, Integer>> unitHash
                = new HashMap<UnitType, EnumMap<Role, Integer>>();
            for (Unit unit : REFPlayer.getUnits()) {
                if (unit.isOffensiveUnit()) {
                    UnitType unitType = defaultType;
                    if (unit.getType().getOffence() > 0
                        || unit.hasAbility(Ability.EXPERT_SOLDIER)) {
                        unitType = unit.getType();
                    }
                    EnumMap<Role, Integer> roleMap = unitHash.get(unitType);
                    if (roleMap == null) {
                        roleMap = new EnumMap<Role, Integer>(Role.class);
                    }
                    Role role = unit.getRole();
                    Integer count = roleMap.get(role);
                    if (count == null) {
                        roleMap.put(role, new Integer(1));
                    } else {
                        roleMap.put(role, new Integer(count.intValue() + 1));
                    }
                    unitHash.put(unitType, roleMap);
                }
            }
            for (java.util.Map.Entry<UnitType, EnumMap<Role, Integer>> typeEntry : unitHash.entrySet()) {
                for (java.util.Map.Entry<Role, Integer> roleEntry : typeEntry.getValue().entrySet()) {
                    units.add(new AbstractUnit(typeEntry.getKey(), roleEntry.getKey(), roleEntry.getValue()));
                }
            }
        } else {
            units = serverPlayer.getMonarch().getREF();
        }

        ChangeSet cs = new ChangeSet();
        cs.addTrivial(See.only(serverPlayer), "getREFUnits",
                      ChangePriority.CHANGE_NORMAL);
        Element reply = cs.build(serverPlayer);
        // TODO: eliminate explicit Element hackery
        for (AbstractUnit unit : units) {
            reply.appendChild(unit.toXMLElement(serverPlayer,
                                                reply.getOwnerDocument()));
        }
        return reply;
    }


    /**
     * Gets the list of high scores.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is querying.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getHighScores(ServerPlayer serverPlayer) {
        ChangeSet cs = new ChangeSet();
        cs.addTrivial(See.only(serverPlayer), "getHighScores",
                      ChangePriority.CHANGE_NORMAL);
        Element reply = cs.build(serverPlayer);
        for (HighScore score : getFreeColServer().getHighScores()) {
            reply.appendChild(score.toXMLElement(serverPlayer,
                                                 reply.getOwnerDocument()));
        }
        return reply;
    }


    /**
     * Chat.
     *
     * @param serverPlayer The <code>ServerPlayer</code> that is chatting.
     * @param message The chat message.
     * @param pri A privacy setting, currently a noop.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element chat(ServerPlayer serverPlayer, String message,
                        boolean pri) {
        sendToOthers(serverPlayer,
                     new ChatMessage(serverPlayer, message, false)
                     .toXMLElement());
        return null;
    }

    /**
     * Get the current game statistics.
     *
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element getStatistics(ServerPlayer serverPlayer) {
        // Convert statistics map to a list.
        java.util.Map<String, String> stats = getGame()
            .getStatistics();
        

        stats.putAll(getFreeColServer().getAIMain().getAIStatistics());

        
        List<String> all = new ArrayList<String>();
        List<String> keys = new ArrayList<String>(stats.keySet());
        Collections.sort(keys);
        for (String k : keys) {
            all.add(k);
            all.add(stats.get(k));
        }

        // Return as statistics element.
        ChangeSet cs = new ChangeSet();
        cs.addTrivial(See.only(serverPlayer), "statistics",
                      ChangePriority.CHANGE_NORMAL,
                      all.toArray(new String[0]));
        return cs.build(serverPlayer);
    }

    /**
     * Enters revenge mode against those evil AIs.
     *
     * @param serverPlayer The <code>ServerPlayer</code> entering revenge mode.
     * @return An <code>Element</code> encapsulating this action.
     */
    public Element enterRevengeMode(ServerPlayer serverPlayer) {
        if (!getFreeColServer().isSingleplayer()) {
            return DOMMessage.clientError("Can not enter revenge mode,"
                + " as this is not a single player game.");
        }
        Game game = getGame();
        List<UnitType> undeads = game.getSpecification()
            .getUnitTypesWithAbility("model.ability.undead");
        List<UnitType> navalUnits = new ArrayList<UnitType>();
        List<UnitType> landUnits = new ArrayList<UnitType>();
        for (UnitType undead : undeads) {
            if (undead.hasAbility(Ability.NAVAL_UNIT)) {
                navalUnits.add(undead);
            } else if (undead.hasAbility("model.ability.multipleAttacks")) {
                landUnits.add(undead);
            }
        }
        if (navalUnits.size() == 0 || landUnits.size() == 0) {
            return DOMMessage.clientError("Can not enter revenge mode,"
                + " because we can not find the undead units.");
        }

        ChangeSet cs = new ChangeSet();
        UnitType navalType = navalUnits.get(Utils.randomInt(logger,
                "Choose undead navy", random, navalUnits.size()));
        Tile start = ((Tile) serverPlayer.getEntryLocation())
            .getSafeTile(serverPlayer, random);
        Unit theFlyingDutchman
            = new ServerUnit(game, start, serverPlayer, navalType);
        UnitType landType = landUnits.get(Utils.randomInt(logger,
                "Choose undead army", random, landUnits.size()));
        new ServerUnit(game, theFlyingDutchman, serverPlayer, landType);
        serverPlayer.setDead(false);
        serverPlayer.setPlayerType(PlayerType.UNDEAD);

        // No one likes the undead.
        for (Player p : game.getPlayers()) {
            if (serverPlayer != (ServerPlayer) p
                && serverPlayer.hasContacted(p)) {
                serverPlayer.csChangeStance(Stance.WAR, p, true, cs);
            }
        }

        // Others can tell something has happened to the player,
        // and possibly see the units.
        cs.add(See.all(), serverPlayer);
        cs.add(See.perhaps(), start);
        sendToOthers(serverPlayer, cs);
        return cs.build(serverPlayer);
    }
}
