/* CVS: $Id: Action.java,v 1.30 2001/03/18 19:16:33 gvijf Exp $ */

package evolution.actions;

import java.util.*;
import java.lang.*;

import evolution.*;
import evolution.resources.*;
import evolution.lands.*;
import evolution.actions.*;

/**
 * An action which humans can do.
 */
abstract public class Action {

    /**
     * Checks if this action can be performed by the human on the selected square of land.
     * If not, some exception will be thrown.
     */
    protected void doChecks(Human human) throws NotEnoughResourcesException, IllegalLandTypeException, NotEnoughLandResourcesException {
        if(!hasEnoughResources())
            throw new NotEnoughResourcesException();
        if(!canBePerformedOn(human.getSquareOfLand()))
            throw new IllegalLandTypeException();
        if(!human.getSquareOfLand().hasEnoughLandResources(getUsesLandResources()))
            throw new NotEnoughLandResourcesException();
    }

    /**
     * Perform the action.
     * This is automatically triggered via Evolution.
     * @see _perform
     */
    public void perform(Human human) throws NotEnoughResourcesException, IllegalLandTypeException, NotEnoughLandResourcesException {
        //System.err.println("Action.perform");
        doChecks(human);
        // modify the world resources
        modifyResources(getProducesResources(), 1.0, true);
        modifyResources(getUsesResources(), -1.0, false); // can throw NotEnoughResourcesException
        // modify the land resources
        modifyLandResources(human.getSquareOfLand(), getProducesLandResources(), 1.0, true);
        modifyLandResources(human.getSquareOfLand(), getUsesLandResources(), -1.0, true); // can throw NotEnoughLandResourcesException
        _perform(human);
    }

    /**
     * Returns a map of the names of resources that are produced
     * by this action.
     */
    protected Map getProducesResources() {
        return ActionKnowledgeCatalog.getInst().producesResources(getName());
    }

    /**
     * Returns a map of the names of resources that are used
     * by this action.
     */
    protected Map getUsesResources() {
        return ActionKnowledgeCatalog.getInst().usesResources(getName());
    }

    /**
     * Returns a map of the names of landresources that are produced
     * by this action.
     */    
    protected Map getProducesLandResources() {
        return ActionKnowledgeCatalog.getInst().producesLandResources(getName());
    }

    /**
     * Returns a map of the names of landresources that are used
     * by this action.
     */
    protected Map getUsesLandResources() {
        return ActionKnowledgeCatalog.getInst().usesLandResources(getName());
    }

    /**
     * Checks whether this action can be performed on the given square of land.
     */
    protected boolean canBePerformedOn(SquareOfLand square) {
        return ActionKnowledgeCatalog.getInst().canBePerformedOn(getName(), square);
    }

    /**
     * Modify the resources that are mentioned in the given map.
     * mul has the value 1 when the
     */
    protected void modifyResources(Map resources, double mul, boolean influencedBy) throws NotEnoughResourcesException {
        Iterator it = resources.keySet().iterator();
        while(it.hasNext()) {
            String resName = (String) it.next();
            if (influencedBy)
                ResourceKnowledgeCatalog.getInst().modResource(resName, getName(), mul * ((Double) resources.get(resName)).doubleValue());
            else
                ResourceKnowledgeCatalog.getInst().modResource(resName, mul * ((Double) resources.get(resName)).doubleValue());
        }
    }

    
    /**
     * Modify the landresources that are mentioned in the given map.
     */
    protected void modifyLandResources(SquareOfLand square, Map resources, double mul, boolean influencedBy) throws NotEnoughLandResourcesException {
        Iterator it = resources.keySet().iterator();
        while(it.hasNext()) {
            String resName = (String) it.next();
            if (influencedBy) {
                double influencedValue =  ResourceKnowledgeCatalog.getInst()
                    .influencedByResources(getName(), ((Double) resources.get(resName)).doubleValue());
                square.modResource(resName, mul * influencedValue);
            } else
                square.modResource(resName, mul * ((Double) resources.get(resName)).doubleValue());
        }
    }

    /**
     * A special case of perform
     */
    public void performEnergyBuffer(SquareOfLand square, Human human) throws IllegalLandTypeException {
        if(!canBePerformedOn(square))
            throw new IllegalLandTypeException();
        try {
            human.decreaseEnergyBuffer(10);
            modifyResources(ActionKnowledgeCatalog.getInst().producesResources(getName()), 1.0, false);
            // FIXME: should we call _perform here?
            //_perform();
        } catch(EmptyEnergyBufferException e) {
            human.die();
        } catch(NotEnoughResourcesException e) {
        }
    }

    /**
     * Extra things which should be done when this action's perform
     * method is called.
     * Template method.
     */
    protected abstract void _perform(Human human) throws NotEnoughResourcesException, IllegalLandTypeException, NotEnoughLandResourcesException;

    /**
     * Return the name of this action.
     */
    public String getName() {
        return ActionKnowledgeCatalog.getInst().stripPathFromClassName(this);
    }

    /**
     * Checks whether this action is an energybufferaction = nofoodaction in our
     * .prop files)
     */
    public boolean isEnergyBufferAction() {
        return ActionKnowledgeCatalog.getInst().getEnergyBufferActionTypes().contains(getName());
    }

    /**
     * Checks whether this action had enough worldresources to be performed.
     */
    public boolean hasEnoughResources() {
        Map neededResources = getUsesResources();
        Iterator it = neededResources.keySet().iterator();
        while (it.hasNext()) {
            String key = (String) it.next();
            double resourceValue = ((Double) neededResources.get(key)).doubleValue();
            try {
                if (!World.getInst().getResource(key).has(resourceValue))
                    return false;
            } catch (NoSuchResourceException e) {
                //can't happen
            }
        }
        return true;
    }

    /**
     * Get the square in the range of a certain position where it is the most
     * suitable to perform a given action.
     */
    public SquareOfLand findSquare (SquareOfLand sq, int visionrange) {
        //System.err.println("findSquare");
        boolean maximizing = ActionKnowledgeCatalog.getInst().isMaximizing(getName());
        double optimum;
        if (maximizing) optimum = 0;
        else optimum = Long.MAX_VALUE;
        List optimums = new ArrayList();
        List possible = new ArrayList();
        Map neededResources = getUsesLandResources();
        GameBoard gameBoard = World.getInst().getGameBoard();
        List visibleSquares = gameBoard.getSurroundingSquares(sq, visionrange, true);
        Iterator it = visibleSquares.iterator();
        while (it.hasNext()) {
            SquareOfLand visibleSquare = (SquareOfLand) it.next();
            if(canBePerformedOn(visibleSquare) && (visibleSquare.getHuman() == null)) {
                possible.add(visibleSquare);
                double tempOptimum = calculateSquareValue(visibleSquare, neededResources, maximizing);
                if( (maximizing && (tempOptimum > optimum)) ||
                    (!maximizing && (tempOptimum < optimum)) ) {
                    optimum = tempOptimum;
                    optimums = new ArrayList();
                    optimums.add(visibleSquare);
                } else {
                    if(tempOptimum == optimum) {
                        optimums.add(visibleSquare);
                    }
                }
            }
        }
        if(optimums.isEmpty()) {
            if(possible.isEmpty()) {
                return sq;
            } else {
                return (SquareOfLand) possible.get((int) (Math.random() * possible.size()));
            }
        }
        return (SquareOfLand) optimums.get((int) (Math.random() * optimums.size()));
    }

    /**
     * Calculate the value of the given square of land for this action. 
     * This method is necessary for the vision of humans.
     */
    protected double calculateSquareValue(SquareOfLand sq, Map neededResources, boolean maximizing) {
        double value = 0;
        Iterator it = neededResources.keySet().iterator();
        while (it.hasNext()) {
            String resName = (String) it.next();
            if (sq.containsLandResource(resName)) {
                double containsValue = sq.getLandResource(resName).getVisibleValue();
                double usesValue = ((Double) neededResources.get(resName)).doubleValue();
                value += containsValue*usesValue;
            }
            else if (maximizing)
                return value = 0;
        }
        return value;
    }

/*    protected boolean betterSquareValue(double value, double optimum, boolean maximizing) {
       if (maximizing)
           return (value > optimum);
       return (value < optimum);
    }*/
}