/* CVS: $Id: Human.java,v 1.48 2001/03/18 12:26:41 gvijf Exp $ */

package evolution;

import java.util.*;

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

/**
 * Class of humans.
 */
public class Human implements Evolver {

    /**
     * Create a new human.
     */
    public Human(SquareOfLand sq) throws IllegalPlacementException {
        sq._place(this);
        setAction(ActionKnowledgeCatalog.getInst().getDefaultActionType());
        try {
            setVisionRange(EvolutionKnowledgeCatalog.getInst().getDefaultVisionRange());
        } catch (InvalidVisionRangeException exc) {
            // can not happen, this would mean there is an error in our config file
            throw new RuntimeException("Invalid default vision range for a human");
        }
        Evolution.getInst().register(this);
        EventManager.getInst().signalEvent(new SquareChangedEvt(sq));
    }

    /**
     * Set the vision range for this human.
     * @exception InvalidVisionRangeException If the vision range is negative.
     */
    public void setVisionRange(int range) throws InvalidVisionRangeException {
        if (range < 0)
            throw new InvalidVisionRangeException("Hey you! I can't see inside myself");
        visionRange = range;
    }

    /**
     * Return the vision range for this human.
     */
    public int getVisionRange() {
        return visionRange;
    }

    /**
     * Let this human perform the given action from now on.
     * Precondition: the action must be a valid action name.
     */
    public void setAction(String actionName) {
        // if we stand on unexplored land we may only change
        // to an allowed action on unexplored land
        if(ActionKnowledgeCatalog.getInst().mustBeExplored(actionName) &&
            !getSquareOfLand().isExplored()) {
            SystemMessage.message("Master, " + actionName + " is not allowed here!");
            return;
        }
        setAction(ActionKnowledgeCatalog.getInst().instantiate(actionName));
        EventManager.getInst().signalEvent(
            new SquareChangedEvt(getSquareOfLand()));
    }

    /**
     * Does this evolver produce energy?
     */
    public boolean producesEnergy() { return false; }

    /**
     * How much energy will this evolver produce in this step.
     */
    public double maxEnergyProduction(Map m1, Map m2) { return 0; }

    /**
     * The evolution for the human when Evolution ticks time.
     */
    public void evolve(double value) {
        SquareOfLand oldSq = getSquareOfLand();
        energyBufferChanged = false;
        //System.err.println("Human.evolve(): " + getState());
        boolean wasPerforming = isPerforming();
        setPerforming(true);
        try {
            if(!energyBufferFull())
                increaseEnergyBuffer(); // can throw NotEnougResourcesException
            evolve(getAction());
        } catch(NotEnoughResourcesException e) {
            //System.err.println("NotEnoughResources");
            // not enough resources to increase energy buffer
            if(getAction().isEnergyBufferAction()) {
                evolve(getAction());
                try {
                    increaseEnergyBuffer();
                } catch(NotEnoughResourcesException e2) {
                    // do nothing
                }
            } else {
                setPerforming(false);
                evolve(ActionKnowledgeCatalog.getInst().getDefaultAction());
            }
        }
        setTimeToLive(getTimeToLive() - 1);
        if((isPerforming() != wasPerforming) || energyBufferChanged)
            EventManager.getInst().signalEvent(
            	new SquareChangedEvt(getSquareOfLand()));
        transformLand(oldSq);
    }

    /**
     * If the current action also transforms land, then do that.
     */
    protected void transformLand(SquareOfLand sq) {
        Object[] r = ActionKnowledgeCatalog.getInst().transformsLand(sq, getAction().getName());
        if(!((Boolean) r[0]).booleanValue())
            return;
        String toLand = (String) r[1];
        String detLandResource = (String) r[2];
        double value = ((Double) r[3]).doubleValue();
        boolean lte = ((Boolean) r[4]).booleanValue();
        boolean gte = ((Boolean) r[5]).booleanValue();
        try {
            if(
                (lte && (sq.getLandResource(detLandResource).getValue() <= value)) ||
                (gte && (sq.getLandResource(detLandResource).getValue() >= value)) ||
                (sq.getLandResource(detLandResource).getValue() == value)
              )
                sq.setLandType(toLand);
        } catch(NullPointerException e) {
        }
    }

    /**
     * Evolve by doing the given action.
     */
    protected void evolve(Action action) {
        //System.err.println("Human.evolve(" + action.getName() + ")");
        try {
        	action.perform(this);
            //System.err.println("ok => normal perform done");
        } catch (NotEnoughResourcesException e) {
            if(action.isEnergyBufferAction()) {
                try {
                    action.performEnergyBuffer(getSquareOfLand(), this);
                    //hunting en fishing kunnen nooit NotEnoughResourcesException tegen komen
                	//daarvoor hebben we ook de speciale methode performEnergyBuffer gedefinieerd
                } catch (IllegalLandTypeException ex) {
                    SquareOfLand newsquare = action.findSquare(getSquareOfLand(), getVisionRange());
                    if(newsquare != getSquareOfLand()) {
                        try {
                            newsquare.place(this);
                        } catch (IllegalPlacementException excep) {
                            //can't happen
                        }
                        try {
                            action.performEnergyBuffer(newsquare, this);
                        } catch (IllegalLandTypeException excep) {
                            //can't happen
                        }
                    } else {
                        setPerforming(false);
                        evolve(ActionKnowledgeCatalog.getInst().getDefaultAction());
                    }
                }
            } else {
                setPerforming(false);
                evolve(ActionKnowledgeCatalog.getInst().getDefaultAction());
                //try {
                //ActionKnowledgeCatalog.getInst().getDefaultAction().perform(this);
                //} catch(Exception ee) {}
            }
        } catch (IllegalLandTypeException e) {
            //System.err.println("IllegalLandType");
            findSquare(action);
        } catch (NotEnoughLandResourcesException e) {
            //System.err.println("NotEnoughLandResources");
            findSquare(action);
        }
    }

    /**
     * Find some squares to do the given action on.
     */
    protected void findSquare(Action action) {
	    SquareOfLand newsquare = action.findSquare(getSquareOfLand(), getVisionRange());
        if(newsquare != getSquareOfLand()) {
            try {
                newsquare.place(this);
            } catch (IllegalPlacementException ex) {
                //can't happen
            }
            try {
                action.perform(this);
            } catch (NotEnoughResourcesException ex) {
                //can't happen
                //in de methode perform wordt eerst NotEnougResourcesException gegooid
                //daarna pas IllegalLandTypeException
            } catch (IllegalLandTypeException ex) {
                //can't happen
            } catch (NotEnoughLandResourcesException ex) {
                //can't happen
                //deze uitzondering wordt alleen gegooid als de landresources nul zijn
                //en als onze findsquare methode een andere square teruggeeft, gaan
                //daarvan de resources nooit nul zijn
            }
        } else {
            setPerforming(false);
            evolve(ActionKnowledgeCatalog.getInst().getDefaultAction());
        }
    }

    /**
     * Set the action for this human.
     */
    protected void setAction(Action a) {
        action = a;
    }

    /**
     * Get the action for this human.
     */
    public Action getAction() {
        return action;
    }

    /**
     * Returns the priority of this evolver.
     */
    public double getPriority() {
        // FIXME: quick hack to give dying humans precedence: multiply
        // their priority by 2
        double mul = 1;
        if(!energyBufferFull()) mul = 2;
        return mul * ActionKnowledgeCatalog.getInst().getPriority(getAction());
    }

    /**
     * Get the state of this human.
     * Returns null if the human has no associated action.
     * Returns the action name, if he's performing the action and
     * "Not" + the action name if he's unable to perform the action.
     */
    public String getState() {
        if(getAction() == null) return null;
        if(!isPerforming()) return "Not" + getAction().getName();
        return getAction().getName();
    }

    /**
     * Get the energy buffer state.
     * Returns "E" + the percentage the buffer is filled.
     * Possible return values: E0 .. E90.
     */
    public String getEnergyBufferState() {
        if(energyBufferFull()) return null;
        return "E" + ((int) (getEnergyBuffer() / 10) * 10);
    }

    /**
     * Let this human die.
     */
    public void die() {
        Evolution.getInst().deregister(this);
        SquareOfLand sq = getSquareOfLand();
		sq._setHuman(null);
        // do NOT set the square to null -> event could be fired
        //setSquareOfLand(null);
        EventManager.getInst().signalEvent(new SquareChangedEvt(sq));
        EventManager.getInst().signalEvent(new HumanDiedEvt(this));
    }

    /**
     * Set the time to live.
     */
    public void setTimeToLive(long time) {
        timeToLive = time;
        if(timeToLive <= 0) die();
    }

    /**
     * Get the time to live.
     */
    public long getTimeToLive() {
        return timeToLive;
    }

    /**
     * Get the square of land this human stands on.
     */
    public SquareOfLand getSquareOfLand() {
        return square;
    }

    /**
     * Set the square of land this human stands on.
     * This is not supposed to be used, except by SquareOfLand.
     */
    public void setSquareOfLand(SquareOfLand square) {
        this.square = square;
    }

    /**
     * Create an infolist with information about this human.
     */
    public InfoList getInfo() {
        InfoList result = new InfoList();
        result.add("Time to live", InfoList.VALUE, getTimeToLive());
        result.add("Energy buffer", InfoList.PERCENTAGE, getEnergyBuffer());
        result.add("Vision range", InfoList.VALUE, getVisionRange());
        if(getAction() != null)
            result.add("Action", InfoList.STRING, getAction().getName());
        return result;
    }

    /**
     * Returns the energybuffer of this human
     */
    public double getEnergyBuffer() {
        return energyBuffer;
    }

    /**
     * Set the energybuffer of this human
     * @exception EmptyEnergyBufferException If the energybuffer of this human
     * is empty.
     */
    protected void setEnergyBuffer(double value) throws EmptyEnergyBufferException {
        if(value > maxEnergyBuffer) value = maxEnergyBuffer;
        if(value < minEnergyBuffer) {
            energyBuffer = minEnergyBuffer;
            energyBufferChanged = true;
            throw new EmptyEnergyBufferException();
        }
        if(energyBuffer != value) energyBufferChanged = true;
        energyBuffer = value;
    }

    /**
     * Checks whether the energybuffer of this human is full (100%)
     */
    public boolean energyBufferFull() {
        return getEnergyBuffer() >= maxEnergyBuffer;
    }

    /**
     * Checks whether the energybuffer of this human is empty
     */
    public boolean energyBufferEmpty() {
        return getEnergyBuffer() <= minEnergyBuffer;
    }

    /**
     * Increases the energybuffer of this human.
     */
    protected void increaseEnergyBuffer() throws NotEnoughResourcesException {
        //System.err.println("EnergyBuffer = " + getEnergyBuffer());
        try {
            //System.err.println("Trying to fill energy buffer");
            double food = World.getInst().getResource("Food").getValue();
            double foodneeded = (maxEnergyBuffer - getEnergyBuffer())/10;
            World.getInst().getResource("Food").modValue(-foodneeded); // throws NotEnoughResourcesException
            setEnergyBuffer(getEnergyBuffer() + food*10);
        } catch(NoSuchResourceException e) {
        } catch(EmptyEnergyBufferException e) {
            // can't happen
        }
    }

    /**
     * Decreases the energybuffer of this human  with the given value.
     */
    public void decreaseEnergyBuffer(double value) throws EmptyEnergyBufferException{
        setEnergyBuffer(getEnergyBuffer() - value);
    }

    /**
     * Mark the human as capable or incapable of performing its assigned
     * action.
     */
    protected void setPerforming(boolean performing) {
        this.performing = performing;
    }

    /**
     * Is the human capable of performing its assigned task?
     */
    public boolean isPerforming() {
        return performing;
    }

    /**
     * The action this human is currently assigned.
     * @supplierCardinality 1 
     */
    private Action action;

    /**
     * Is the human capable of performing its assigned task?
     */
    private boolean performing = true;

    /**
     * Did we change the energy buffer during evolve?
     * Used to fire a SquareChangedEvt so the energy buffer will
     * be drawn correct.
     */
    protected boolean energyBufferChanged = false;

    /**
     * The vision range of this human.
     */
    private int visionRange;

    /**
     * The time to live of this human.
     */
    private long timeToLive = 80;

    /**
     * The square this human is standing on.
     */
    private SquareOfLand square;

    /**
     * The energy buffer of this human.
     */
    private double energyBuffer = maxEnergyBuffer;

    /**
     * Maximum energy buffer value (100%).
     */
    protected static final int maxEnergyBuffer = 100;

    /**
     * Minimum energy buffer value (0%).
     */
    protected static final int minEnergyBuffer = 0;

}
