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

package net.sf.freecol.common.model.pathfinding;

import net.sf.freecol.common.model.Map;
import net.sf.freecol.common.model.Tile;
import net.sf.freecol.common.model.Settlement;
import net.sf.freecol.common.model.Unit;


/**
 * Cost deciders to be used while finding paths.
 */
public final class CostDeciders {

    /**
     * A <code>CostDecider</code> only considering the number of tiles
     * visited when determining the cost.
     */
    private static final CostDecider tileCostDecider
        = new CostDecider() {
            public int getCost(Unit unit, Tile oldTile, Tile newTile,
                               int movesLeft) {
                if (unit.isNaval()) {
                    if (!newTile.isLand()) return 1;
                    Settlement settlement = newTile.getSettlement();
                    return (settlement != null
                            && settlement.getOwner().equals(unit.getOwner()))
                        ? 1
                        : ILLEGAL_MOVE;
                } else {
                    return (newTile.isLand()) ? 1 : ILLEGAL_MOVE;
                }
            }
            public int getMovesLeft() {
                return 0;
            }
            public boolean isNewTurn() {
                return false;
            }
        };


    /**
     * A <code>CostDecider</code> that costs unit moves normally.
     */
    private static final CostDecider
        avoidIllegalCostDecider = new BaseCostDecider();


    /**
     * A <code>CostDecider</code> that uses server-side knowledge of where
     * a player has explored to searches.
     */
    private static class ServerBaseCostDecider extends BaseCostDecider {
        @Override
        public int getCost(Unit unit, Tile oldTile, Tile newTile,
                           int movesLeft) {
            int cost = super.getCost(unit, oldTile, newTile, movesLeft);
            if (cost != ILLEGAL_MOVE && cost != Map.COST_INFINITY) {
                if (!newTile.isExploredBy(unit.getOwner())) return ILLEGAL_MOVE;
            }
            return cost;
        }
    };


    /**
     * A server-side <code>CostDecider</code> that costs unit moves normally.
     */
    private static final CostDecider
        serverAvoidIllegalCostDecider = new ServerBaseCostDecider();


    /**
     * A <code>CostDecider</code> that costs unit moves normally while
     * avoiding other player settlements.
     */
    private static class AvoidSettlementsCostDecider
        extends BaseCostDecider {
        @Override
        public int getCost(Unit unit, Tile oldTile, Tile newTile,
                           int movesLeft) {
            int cost = super.getCost(unit, oldTile, newTile, movesLeft);
            if (cost != ILLEGAL_MOVE && cost != Map.COST_INFINITY) {
                Settlement settlement = newTile.getSettlement();
                if (settlement != null
                    && settlement.getOwner() != unit.getOwner()) {
                    return ILLEGAL_MOVE;
                }
            }
            return cost;
        }
    };
    private static final AvoidSettlementsCostDecider
        avoidSettlementsCostDecider = new AvoidSettlementsCostDecider();


    /**
     * A <code>CostDecider</code> that costs unit moves normally while
     * avoiding other player settlements and units, and does not explore
     * if it is trading.
     */
    private static class AvoidSettlementsAndBlockingUnitsCostDecider
        extends BaseCostDecider {
        @Override
        public int getCost(Unit unit, Tile oldTile, Tile newTile,
                           int movesLeft) {
            int cost = super.getCost(unit, oldTile, newTile, movesLeft);
            if (cost != ILLEGAL_MOVE && cost != Map.COST_INFINITY) {
                Settlement settlement = newTile.getSettlement();
                if (settlement != null
                    && settlement.getOwner() != unit.getOwner()) {
                    return ILLEGAL_MOVE;
                }
                final Unit defender = newTile.getFirstUnit();
                if (defender != null
                    && defender.getOwner() != unit.getOwner()) {
                    return ILLEGAL_MOVE;
                } else if (newTile.isLand()
                           && newTile.getFirstUnit() != null
                           && newTile.getFirstUnit().isNaval()
                           && newTile.getFirstUnit().getOwner() != unit.getOwner()) {
                    // An enemy ship in land tile without a settlement
                    // is blocking the path:
                    return ILLEGAL_MOVE;
                } else if (unit.getTradeRoute() != null
                           && unit.getMoveType(newTile)
                           == Unit.MoveType.EXPLORE_LOST_CITY_RUMOUR) {
                    return ILLEGAL_MOVE;
                }
            }
            return cost;
        }
    };
    private static final AvoidSettlementsAndBlockingUnitsCostDecider
        avoidSettlementsAndBlockingUnitsCostDecider
        = new AvoidSettlementsAndBlockingUnitsCostDecider();


    /**
     * Selects a default <code>CostDecider</code> for the given unit
     * depending on the owner of the unit (ai/human) and if the unit
     * can attack other units.
     * 
     * @param unit The <code>Unit</code> to choose a CostDecider for.
     * @return A suitable <code>CostDecider</code>.
     */
    public static CostDecider defaultCostDeciderFor(final Unit unit) {
        return (unit == null
                || !unit.getOwner().isAI()
                || !unit.isOffensiveUnit())
            ? avoidSettlementsAndBlockingUnits()
            : avoidSettlements();
    }

    /**
     * A <code>CostDecider</code> only considering the number of tiles
     * visited when determining the cost.
     * 
     * @return The <code>CostDecider</code>.
     */
    public static CostDecider numberOfTiles() {
        return tileCostDecider;
    }

    /**
     * A <code>CostDecider</code> that returns the cost of moving
     * across the terrain, excluding only illegal moves.
     *
     * @return The <code>CostDecider</code>.
     */
    public static CostDecider avoidIllegal() {
        return avoidIllegalCostDecider;
    }

    /**
     * A <code>CostDecider</code> that returns the cost of moving
     * across the terrain, excluding only illegal moves, and works correctly
     * on the server side by refusing to consider tiles unexplored by the
     * player.
     *
     * @return The <code>CostDecider</code>.
     */
    public static CostDecider serverAvoidIllegal() {
        return serverAvoidIllegalCostDecider;
    }

    /**
     * A <code>CostDecider</code> returning only the cost of moving
     * across the terrain (no additional cost for blocking enemy units
     * etc) but excluding settlements.
     * 
     * @return The <code>CostDecider</code>.
     */
    public static CostDecider avoidSettlements() {
        return avoidSettlementsCostDecider;
    }
    
    /**
     * A <code>CostDecider</code> for avoiding using tiles which have
     * blocking enemy units on them. Paths containing an enemy
     * settlement are considered illegal, and so are paths where the
     * next move has an enemy unit on it.
     * 
     * @return The <code>CostDecider</code>.
     */
    public static CostDecider avoidSettlementsAndBlockingUnits() {
        return avoidSettlementsAndBlockingUnitsCostDecider;
    }
}
