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

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

import net.sf.freecol.common.model.Colony;
import net.sf.freecol.common.model.Location;
import net.sf.freecol.common.model.PathNode;
import net.sf.freecol.common.model.Player;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.Unit;
import net.sf.freecol.common.model.pathfinding.GoalDecider;
import net.sf.freecol.server.ai.mission.DefendSettlementMission;
import net.sf.freecol.server.ai.mission.Mission;
import net.sf.freecol.server.ai.mission.TransportMission;
import net.sf.freecol.server.ai.mission.UnitSeekAndDestroyMission;
import net.sf.freecol.server.ai.mission.UnitWanderHostileMission;
import net.sf.freecol.server.model.ServerPlayer;


/**
 * Objects of this class contains AI-information for a single REF player.
 *
 * For now, mostly just the EuropeanAIPlayer, with a few tweaks.
 */
public class REFAIPlayer extends EuropeanAIPlayer {

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

    private static int seekAndDestroyRange = 12;


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

        uninitialized = getPlayer() == null;
    }

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

        uninitialized = getPlayer() == null;
    }


    /**
     * Initialize the REF.
     * - Find the initial target
     * - Give valid missions to all the units
     *
     * Note that we can not rely on normal AI processing as the
     * "teleporting" Col1-style REF needs to be placed on the map
     * before its first turn starts, so the server should ask this
     * AI where it should arrive on the map.
     *
     * Note also that to find a target we can not just call
     * getMilitaryMission and aim for it as the getMilitaryMission
     * scoring includes distance from starting point, which is what we
     * are trying to determine.
     * So, just choose the best coastal colony.
     *
     * FIXME: Mission assignment is done here because ATM the European
     * AI is prone to send ships full of troops off to attack the
     * rebel navy.  If this is fixed check if the normal mission
     * assignment works and drop it from here.
     *
     * @param teleport "Teleporting" in is allowed.
     * @return The preferred entry location, or null if no useful preference.
     */
    public Tile initialize(boolean teleport) {
        // Find a representative offensive land unit to use to search
        // for the initial target.
        AIUnit aiUnit = null;
        for (AIUnit aiu : getAIUnits()) {
            if (!aiu.getUnit().isNaval() && aiu.getUnit().isOffensiveUnit()) {
                aiUnit = aiu;
                break;
            }
        }
        if (aiUnit == null) {
            logger.warning("REF has no army?!?");
            return null;
        }
        final Unit unit = aiUnit.getUnit();

        // Find the best coastal colony.
        Location target = UnitSeekAndDestroyMission.findTarget(aiUnit,
            INFINITY, false);
        if (!(target instanceof Colony)) {
            logger.warning("Rebels have no connected colonies?!?");
            return null;
        }
        Colony colony = (Colony)target;
        Tile tile = colony.getTile();

        // Give the army seek-and-destroy missions for the target,
        // then once the army has missions it is possible to give the
        // navy valid transport missions where needed.
        final AIMain aiMain = getAIMain();
        for (AIUnit aiu : getAIUnits()) {
            if (!aiu.getUnit().isNaval()) {
                aiu.setMission(new UnitSeekAndDestroyMission(aiMain, aiu,
                                                             colony));
            }
        }
        for (AIUnit aiu : getAIUnits()) {
            if (aiu.getUnit().isNaval()) {
                Unit ship = aiu.getUnit();
                if (ship.getUnitCount() > 0) {
                    TransportMission tm = new TransportMission(aiMain, aiu);
                    aiu.setMission(tm);
                }
            }
        }

        // Search from the target position to find a Tile to disembark
        // to, which must be:
        // - Unoccupied
        // - Have an unoccupied connected neighbour
        //
        // TODO: pick the tile with the best defence and try to avoid
        // hostile fortifications.
        final GoalDecider gd = new GoalDecider() {
                private PathNode goal = null;

                public PathNode getGoal() { return goal; }
                public boolean hasSubGoals() { return true; }
                public boolean check(Unit u, PathNode pathNode) {
                    Tile tile = pathNode.getTile();
                    if (tile == null || !tile.isEmpty()) return false;
                    for (Tile t : pathNode.getTile().getSurroundingTiles(1)) {
                        if (t.isHighSeasConnected() && t.isEmpty()) {
                            goal = pathNode;
                            return true;
                        }
                    }
                    return false;
                }
            };
        PathNode path = unit.search(tile, gd, null, 10, unit.getCarrier());
        if (path == null) {
            logger.warning("Can not find suitable REF landing site for: "
                + colony);
            return null;
        }
        tile = path.getTile();

        // If teleporting in, the connected tile is an acceptable target.
        for (Tile t : tile.getSurroundingTiles(1)) {
            if (t.isHighSeasConnected() && t.isEmpty()) {
                tile = t;
                break;
            }
        }
        if (teleport) return tile;

        // Unit should be aboard a man-o-war which we can use to find a
        // path to Europe.  Use the end of that path.
        if (unit.isOnCarrier()) {
            Tile entry = unit.getCarrier().getBestEntryTile(tile);
            if (entry != null) return entry;
            logger.warning("Can not find path to Europe from: " + tile);
            return null;
        }
        logger.warning("REF land unit not aboard a ship: " + unit);
        return null;
    }


    // AI Player interface

    /**
     * Tells this <code>REFAIPlayer</code> to make decisions.
     */
    public void startWorking() {
        final Player player = getPlayer();
        logger.finest("Entering method startWorking: "
            + player + ", year " + getGame().getTurn());
        if (!player.isWorkForREF()) {
            logger.warning("No work for REF: " + player);
            return;
        }
        super.startWorking();
    }

    /**
     * {@inheritDoc}
     */
    public int adjustMission(AIUnit aiUnit, PathNode path, Class type,
                             int value) {
        if (value > 0) {
            if (type == DefendSettlementMission.class) {
                // REF garrisons thinly.
                Location loc = DefendSettlementMission.extractTarget(aiUnit, path);
                if (loc instanceof Settlement
                    && getSettlementDefenders((Settlement)loc) > 0) value = 0;
            } else if (type == UnitSeekAndDestroyMission.class) {
                Location target = UnitSeekAndDestroyMission
                    .extractTarget(aiUnit, path);
                if (target instanceof Settlement) {
                    // Value connected settlements highly.
                    // Initially, accept no others.
                    if (((Settlement)target).isConnectedPort()) {
                        value += 500;
                    } else {
                        if (getPlayer().getNumberOfSettlements() <= 0) {
                            return Integer.MIN_VALUE;
                        }
                    }
                } else if (target instanceof Unit) {
                    // Do not chase units until at least one colony is captured.
                    if (getPlayer().getNumberOfSettlements() <= 0) {
                        return Integer.MIN_VALUE;
                    }
                    // Do not chase the same unit!
                    for (AIUnit au : getAIUnits()) {
                        Mission m = au.getMission();
                        Location loc;
                        if (m != null
                            && m instanceof UnitSeekAndDestroyMission
                            && (loc = ((UnitSeekAndDestroyMission)m)
                                .getTarget()) != null
                            && loc instanceof Unit
                            && loc == target) return Integer.MIN_VALUE;
                    }
                    // The REF is more interested in colonies.
                    value /= 2;
                }
            }
        }
        return value;
    }

    /**
     * Gives a mission to non-naval units.
     */
    @Override
    public void giveNormalMissions() {
        // Give military missions to all REF units.
        for (AIUnit aiu : getAIUnits()) {
            Unit u = aiu.getUnit();
            if (u.isNaval() || aiu.hasMission()) continue;
            if (u.isOffensiveUnit()) {
                Location target = UnitSeekAndDestroyMission.findTarget(aiu, 
                    seekAndDestroyRange, false);
                Mission m = (target == null)
                    ? new UnitWanderHostileMission(getAIMain(), aiu)
                    : new UnitSeekAndDestroyMission(getAIMain(), aiu, target);
                aiu.setMission(m);
            }
        }

        // Fall back to the normal EuropeanAI behaviour for non-army.
        super.giveNormalMissions();
    }
}
