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

package net.sf.freecol.server.ai;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

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

import net.sf.freecol.FreeCol;
import net.sf.freecol.common.model.DiplomaticTrade;
import net.sf.freecol.common.model.DiplomaticTrade.TradeStatus;
import net.sf.freecol.common.model.FoundingFather;
import net.sf.freecol.common.model.Game;
import net.sf.freecol.common.model.Goods;
import net.sf.freecol.common.model.Market;
import net.sf.freecol.common.model.Monarch.MonarchAction;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Unit;
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.IndianDemandMessage;
import net.sf.freecol.common.networking.LootCargoMessage;
import net.sf.freecol.common.networking.MessageHandler;
import net.sf.freecol.common.networking.MonarchActionMessage;
import net.sf.freecol.common.networking.NewLandNameMessage;
import net.sf.freecol.common.networking.NewRegionNameMessage;
import net.sf.freecol.common.networking.StreamedMessageHandler;
import net.sf.freecol.server.FreeColServer;
import net.sf.freecol.server.model.ServerPlayer;
import net.sf.freecol.server.networking.DummyConnection;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;


/**
 * Handles the network messages that arrives while in the game.
 */
public final class AIInGameInputHandler implements MessageHandler, StreamedMessageHandler {

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

    /** The player for whom I work. */
    private final ServerPlayer serverPlayer;

    /** The server. */
    private final FreeColServer freeColServer;

    private final AIMain aiMain;


    /**
     *
     * The constructor to use.
     *
     * @param freeColServer The main server.
     *
     * @param me The AI player that is being managed by this
     *            AIInGameInputHandler.
     *
     * @param aiMain The main AI-object.
     *
     */
    public AIInGameInputHandler(FreeColServer freeColServer, ServerPlayer me, AIMain aiMain) {
        this.freeColServer = freeColServer;
        this.serverPlayer = me;
        this.aiMain = aiMain;
        if (freeColServer == null) {
            throw new NullPointerException("freeColServer == null");
        } else if (me == null) {
            throw new NullPointerException("me == null");
        } else if (aiMain == null) {
            throw new NullPointerException("aiMain == null");
        }
        if (!me.isAI()) {
            logger.warning("VERY BAD: Applying AIInGameInputHandler to a non-AI player!!!");
        }
    }

    /**
     * Deals with incoming messages that have just been received.
     *
     * @param connection The <code>Connection</code> the message was received
     *            on.
     * @param element The root element of the message.
     * @return The reply.
     */
    public synchronized Element handle(Connection connection, Element element) {
        Element reply = null;
        try {
            if (element != null) {
                String type = element.getTagName();

                // Since we're the server, we can see everything.
                // Therefore most of these messages are useless.
                if (type.equals("update")) {
                } else if (type.equals("remove")) {
                } else if (type.equals("startGame")) {
                } else if (type.equals("updateGame")) {
                } else if (type.equals("addPlayer")) {
                } else if (type.equals("animateMove")) {
                } else if (type.equals("animateAttack")) {
                } else if (type.equals("setCurrentPlayer")) {
                    reply = setCurrentPlayer((DummyConnection) connection, element);
                } else if (type.equals("newTurn")) {
                } else if (type.equals("setDead")) {
                } else if (type.equals("gameEnded")) {
                } else if (type.equals("disconnect")) {
                } else if (type.equals("logout")) {
                } else if (type.equals("error")) {
                } else if (type.equals("setAI")) {
                } else if (type.equals("chat")) {
                } else if (type.equals("chooseFoundingFather")) {
                    reply = chooseFoundingFather((DummyConnection) connection, element);
                } else if (type.equals("reconnect")) {
                    logger.warning("The server requests a reconnect. This means an illegal operation has been performed. Please refer to any previous error message.");
                } else if (type.equals("setStance")) {
                } else if (type.equals("monarchAction")) {
                    reply = monarchAction((DummyConnection) connection, element);
                } else if (type.equals("removeGoods")) {
                } else if (type.equals("indianDemand")) {
                    reply = indianDemand((DummyConnection) connection, element);
                } else if (type.equals("diplomacy")) {
                    reply = diplomaticTrade((DummyConnection) connection, element);
                } else if (type.equals("addObject")) {
                } else if (type.equals("newLandName")) {
                    reply = newLandName((DummyConnection) connection, element);
                } else if (type.equals("newRegionName")) {
                    reply = newRegionName((DummyConnection) connection, element);
                } else if (type.equals("fountainOfYouth")) {
                    reply = fountainOfYouth((DummyConnection) connection, element);
                } else if (type.equals("lootCargo")) {
                    reply = lootCargo(connection, element);
                } else if (type.equals("multiple")) {
                    reply = multiple((DummyConnection) connection, element);
                } else {
                    logger.warning("Message is of unsupported type \"" + type + "\".");
                }
            }
        } catch (Exception e) {
            logger.log(Level.WARNING, "AI input handler for " + serverPlayer
                + " caught error handling " + element.getTagName(), e);
        }
        return reply;
    }

    /**
     * Gets the <code>AIPlayer</code> using this
     * <code>AIInGameInputHandler</code>.
     *
     * @return The <code>AIPlayer</code>.
     */
    private AIPlayer getAIPlayer() {
        return aiMain.getAIPlayer(serverPlayer);
    }

    /**
     * Gets the AI unit corresponding to a given unit, if any.
     *
     * @param unit The <code>Unit</code> to look up.
     * @return The corresponding AI unit or null if not found.
     */
    private AIUnit getAIUnit(Unit unit) {
        return aiMain.getAIUnit(unit);
    }

    /**
     * Handles the main element of an XML message.
     *
     * @param connection The connection the message came from.
     * @param in The stream containing the message.
     * @param out The output stream for the reply.
     */
    public void handle(Connection connection, XMLStreamReader in, XMLStreamWriter out) {
        // TODO: not yet implemented!
    }

    /**
     *
     * Checks if the message handler support the given message.
     *
     * @param tagName The tag name of the message to check.
     * @return The result (currently always false).
     */
    public boolean accepts(String tagName) {
        return false;
    }

    /**
     * Handles a "setCurrentPlayer"-message.
     *
     * @param connection The connection the message was received on.
     * @param setCurrentPlayerElement The element (root element in a DOM-parsed
     *            XML tree) that holds all the information.
     */
    private Element setCurrentPlayer(final DummyConnection connection, final Element setCurrentPlayerElement) {
        final Game game = freeColServer.getGame();
        final Player currentPlayer = (Player) game.getFreeColGameObject(setCurrentPlayerElement.getAttribute("player"));

        if (currentPlayer != null
            && serverPlayer.getId() == currentPlayer.getId()) {
            logger.finest("Starting new Thread for " + serverPlayer.getName());
            Thread t = new Thread(FreeCol.SERVER_THREAD+"AIPlayer (" + serverPlayer.getName() + ")") {
                public void run() {
                    try {
                        getAIPlayer().startWorking();
                    } catch (Exception e) {
                        logger.log(Level.SEVERE, "AI player failed while working!", e);
                    }
                    AIMessage.askEndTurn(connection);
                }
            };
            t.start();
        }
        return null;
    }

    /**
     * Handles a "chooseFoundingFather"-message.
     * Only meaningful for AIPlayer types that implement selectFoundingFather.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *     holds all the information.
     */
    private Element chooseFoundingFather(final DummyConnection connection,
                                         Element element) {
        ChooseFoundingFatherMessage message
            = new ChooseFoundingFatherMessage(aiMain.getGame(), element);
        AIPlayer aiPlayer = getAIPlayer();
        FoundingFather ff = aiPlayer.selectFoundingFather(message.getFathers());
        if (ff != null) message.setResult(ff);
        return message.toXMLElement();
    }

    /**
     * Handles a "monarchAction"-message.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     *
     */
    private Element monarchAction(DummyConnection connection, Element element) {
        Game game = aiMain.getGame();
        MonarchActionMessage message = new MonarchActionMessage(game, element);
        MonarchAction action = message.getAction();
        boolean accept;

        switch (action) {
        case RAISE_TAX_WAR: case RAISE_TAX_ACT:
            accept = getAIPlayer().acceptTax(message.getTax());
            message.setResult(accept);
            logger.finest("AI player monarch action " + action
                          + " = " + accept);
            break;

        case OFFER_MERCENARIES:
            accept = getAIPlayer().acceptMercenaries();
            message.setResult(accept);
            logger.finest("AI player monarch action " + action
                          + " = " + accept);
            break;

        default:
            logger.finest("AI player ignoring monarch action " + action);
            return null;
        }

        return message.toXMLElement();
    }

    /**
     * Handles an "indianDemand"-message.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return The original message with the acceptance state set if querying
     *     the colony player (result == null), or null if reporting the final
     *     result to the native player (result != null).
     */
    private Element indianDemand(DummyConnection connection, Element element) {
        Game game = aiMain.getGame();
        IndianDemandMessage message = new IndianDemandMessage(game, element);
        boolean accept = getAIPlayer()
            .indianDemand(message.getUnit(game), message.getColony(game),
                message.getGoods(), message.getGold());
        message.setResult(accept);
        logger.finest("AI handling native demand by " + message.getUnit(game)
            + " at " + message.getColony(game).getName()
            + " result: " + accept);
        return message.toXMLElement();
    }

    /**
     * Handles an "diplomaticTrade"-message.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return The original message with the acceptance state set.
     */
    private Element diplomaticTrade(DummyConnection connection, Element element) {
        DiplomacyMessage message
            = new DiplomacyMessage(freeColServer.getGame(), element);
        DiplomaticTrade agreement = message.getAgreement();
        boolean accept = getAIPlayer().acceptDiplomaticTrade(agreement);
        agreement.setStatus((accept) ? TradeStatus.ACCEPT_TRADE
                            : TradeStatus.REJECT_TRADE);
        return message.toXMLElement();
    }

    /**
     * Replies to offer to name the new land.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return The default offer.
     */
    private Element newLandName(DummyConnection connection, Element element) {
        NewLandNameMessage message
            = new NewLandNameMessage(freeColServer.getGame(), element);
        message.setAccept(true); // Always accept new land
        return message.toXMLElement();
    }

    /**
     * Replies to offer to name a new region name.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return The default offer.
     */
    private Element newRegionName(DummyConnection connection, Element element) {
        return new NewRegionNameMessage(freeColServer.getGame(), element)
            .toXMLElement();
    }

    /**
     * Replies to fountain of youth offer.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return Null.
     */
    private Element fountainOfYouth(DummyConnection connection, Element element) {
        String migrants = element.getAttribute("migrants");
        int n;
        try {
            n = Integer.parseInt(migrants);
        } catch (NumberFormatException e) {
            n = -1;
        }
        for (int i = 0; i < n; i++) {
            AIMessage.askEmigrate(connection, 0);
        }
        return null;
    }

    /**
     * Replies to loot cargo offer.
     *
     * @param connection The connection the message was received on.
     * @param element The element (root element in a DOM-parsed XML tree) that
     *            holds all the information.
     * @return Null.
     */
    private Element lootCargo(Connection connection, Element element) {
        Game game = freeColServer.getGame();
        LootCargoMessage message = new LootCargoMessage(game, element);
        Unit unit = message.getUnit(game);
        List<Goods> goods = message.getGoods();
        final Market market = serverPlayer.getMarket();
        Collections.sort(goods, new Comparator<Goods>() {
                public int compare(Goods g1, Goods g2) {
                    int p1 = market.getPaidForSale(g1.getType())
                        * g1.getAmount();
                    int p2 = market.getPaidForSale(g2.getType())
                        * g2.getAmount();
                    return p2 - p1;
                }
            });
        List<Goods> loot = new ArrayList<Goods>();
        int space = unit.getSpaceLeft();
        while (!goods.isEmpty() && space > 0) {
            Goods g = goods.remove(0);
            if (g.getSpaceTaken() > space) continue;
            loot.add(g);
            space -= g.getSpaceTaken();
        }
        AIMessage.askLoot(getAIUnit(unit), message.getDefenderId(), loot);
        return null;
    }


    /**
     * Handle all the children of this element.
     *
     * @param connection The <code>Connection</code> the element arrived on.
     * @param element The <code>Element</code> to process.
     * @return An <code>Element</code> containing the response/s.
     */
    public Element multiple(Connection connection, Element element) {
        NodeList nodes = element.getChildNodes();
        List<Element> results = new ArrayList<Element>();

        for (int i = 0; i < nodes.getLength(); i++) {
            try {
                Element reply = handle(connection, (Element) nodes.item(i));
                if (reply != null) results.add(reply);
            } catch (Exception e) {
                logger.log(Level.WARNING, "Caught crash in multiple item " + i
                    + ", continuing.", e);
            }
        }
        return DOMMessage.collapseElements(results);
    }
}
