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

package evolution.lands;

import java.util.*;

import evolution.Human;
import evolution.InfoList;
import evolution.Evolution;
import evolution.Evolver;
import evolution.constructions.Construction;
import evolution.events.*;
import evolution.actions.*;

/**
 * Class of squares of land.
 */
public class SquareOfLand implements Evolver {

    /**
     * Create a new SquareOfLand of the given type. 
     */
    public SquareOfLand(String landType, int x, int y, boolean explored) {
        setLandType(landType);
        setX(x);
        setY(y);
        if(explored) explorationState = 100;
        landResources = LandKnowledgeCatalog.getInst().generateLandResources(landType);
        EventManager.getInst().signalEvent(new SquareChangedEvt(this));
    }

    /**
     * Create a new SquareOfLand of a random type. 
     */
    public SquareOfLand(int x, int y, boolean explored) {
        this(LandKnowledgeCatalog.getInst().getRandomType(), x, y, explored);
    }

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

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

    /**
     * Evolve this square of land.
     */
    public void evolve(double val) {
        boolean transform = false;
        double speed = LandKnowledgeCatalog.getInst().transformationSpeed(getLandType());
        Map m = LandKnowledgeCatalog.getInst().transformsLandResources(getLandType());
        Iterator it = m.keySet().iterator();
        while(it.hasNext()) {
            String from = (String) it.next();
            String to = (String) ((Object[]) m.get(from))[0];
            Double v = (Double) ((Object[]) m.get(from))[1];
            double value = getLandResource(from).extract(speed * v.doubleValue());
            modResource(to, value);
            if(getLandResource(from).isBoundary()) transform = true;
        }
        if(transform) setLandType(LandKnowledgeCatalog.getInst().transformsTo(getLandType()));
    }

    /**
     * Return the human on this square of land.
     */
    public Human getHuman() {
        return human;
    }

    /**
     * Set the given human on this square of land.
     * This is an internal method, made public to be accessible from
     * out of this package.
     */
    public void _setHuman(Human human) {
        this.human = human;
    }

    /**
     * Return the construction on this square of land.
     */
    public Construction getConstruction() {
        return construction;
    }

    /**
     * Set the given construction on this square of land.
     */
    protected void setConstruction(Construction construction) {
        this.construction = construction;
    }

    /**
     * Is this square explored? 
     */
    public boolean isExplored() {
        //return true;
        return getExplorationState() >= 100;
    }

    /**
     * Checks whether this square has enough landresources to perform a
     * particular action. The needs are in the map.
     * This function results true if all the needed landresources are still
     * there, but does not look at the needed values.
     */
    public boolean hasEnoughLandResources(Map neededLandResources) {
        Iterator it = neededLandResources.keySet().iterator();
        while (it.hasNext()) {
            String resName = (String) it.next();
            //double v = ((Double) neededLandResources.get(resName)).doubleValue();
            LandResource r = getLandResource(resName);
            if((r == null) || (r.getValue() <= 0))
                return false;
        }
        return true;
    }

    /**
     * Return the named landresource.
     * Returns null if the landresource is not here.
     */
    public LandResource getLandResource(String resName) {
        return (LandResource) landResources.get(resName);
    }

    /**
     * Get all the landresources for this square.
     * Map of (String, LandResource).
     */
    public Map getLandResources() {
        return landResources;
    }

    /**
     * Install a map of landresources.
     * Map of (String, LandResource).
     */
    protected void setLandResources(Map r) {
        landResources = r;
    }

    /**
     * Check wheter this square has the named landresource.
     */
    public boolean containsLandResource(String resourceName) {
        return getLandResource(resourceName) != null;
    }

    /**
     * Modify the named landresource by value.
     * If the landresource did not exist, it is created and initialized
     * at the given value.
     */
    public void modResource(String resName, double value) {
        LandResource r = getLandResource(resName);
        if(r == null) {
            // create the land resource
            // and initialize it at 0
            r = LandKnowledgeCatalog.getInst().
                instantiateLandResource(resName, new Double(0), getLandType());
            getLandResources().put(resName, r);
        }
        r.setValue(r.getValue() + value);
    }

    /**
     * Place a human on this square, without triggering an event.
     * @param human The human to place on this square.
     * @exception IllegalPlacementException If this spot is already taken
     * or the spot is unexplored.
     */
    public void _place(Human human) throws IllegalPlacementException {
        if(this.human == human) return;
        if(this.human != null)
            throw new IllegalPlacementException("If you really want this spot, you'll have to kill me!");
        boolean checkExplored;
        try {
            checkExplored = ActionKnowledgeCatalog.getInst().mustBeExplored(human.getAction().getName());
        } catch(NullPointerException e) {
            // this happens when the human is created and the action is not yet assigned
            checkExplored = true; // in other words: humans can only be created on explored land
        }
        if(checkExplored && !isExplored())
            throw new IllegalPlacementException("Stop! That square is unexplored, there may be monsters and stuff...");
        SquareOfLand oldSq = human.getSquareOfLand();
        _setHuman(human);
        human.setSquareOfLand(this);
        if(oldSq != null)
            oldSq._setHuman(null);
	}

    /**
     * Place a human on this square, and trigger a SquareChangedEvt.
     * @param human The human to place on this square.
     * @exception IllegalPlacementException If this spot is already taken
     * or the spot is unexplored.
     */
    public void place(Human human) throws IllegalPlacementException {
        SquareOfLand oldSq = human.getSquareOfLand();
        _place(human);
		EventManager.getInst().signalEvent(new SquareChangedEvt(this));
        if(oldSq != null)
	        EventManager.getInst().signalEvent(new SquareChangedEvt(oldSq));
    }

    /**
     * Place a construction on this square, without triggering an event.
     * @exception IllegalPlacementException If this spot is already build on.
     */
    public void _place(Construction construction) throws IllegalPlacementException {
        if(this.construction != null)
            throw new IllegalPlacementException("Hey you! This spot is already build on!");
        setConstruction(construction);
    }

    /**
     * Place a construction on this square, and trigger a SquareChangedEvt.
     * @exception IllegalPlacementException If this spot is already build on.
     */
    public void place(Construction construction) throws IllegalPlacementException {
        _place(construction);
		EventManager.getInst().signalEvent(new SquareChangedEvt(this));
    }

    /**
     * Set the land type of this square of land.
     * precondition: the landtype must be valid.
     */
    public void setLandType(String landType) {
        if(landType == getLandType()) return;
        // modify the land resources
        Map newResources = LandKnowledgeCatalog.getInst().generateLandResources(landType);
        Iterator it = newResources.keySet().iterator();
        while(it.hasNext()) {
            String res = (String) it.next();
            if(getLandResources().containsKey(res)) {
                // copy the value
                newResources.put(res, getLandResources().get(res));
            }
        }
        setLandResources(newResources);
        // deregister does nothing when we are not registered
        Evolution.getInst().deregister(this);
        this.landType = landType;
        // if this landtype auto transforms, then register with Evolution
        if(LandKnowledgeCatalog.getInst().isEvolver(landType))
            Evolution.getInst().register(this);
        EventManager.getInst().signalEvent(new SquareChangedEvt(this));
    }

    /**
     * Return the land type of this square of land.
     */
    public String getLandType() {
        return landType;
    }

     /**
     * Returns the priority of this evolver.
     */
    public double getPriority() {
        return 0;
    }

   /**
     * Return the state of exploration of this square of land.
     * Returns a percentage.
     */
    public double getExplorationState() {
        return explorationState;
    }

   /**
     * Set the state of exploration of this square of land.
     * @param s A percentage.
     */
    public void setExplorationState(double s) {
        explorationState = (s < 0) ? 0 : ((s > 100) ? 100 : s);
    }

    /**
     * Return the x-coordinate of this square of land.
     */
    public int getX() {
        return x;
    }

    /**
     * Set the x-coordinate of this square of land.
     */
    protected void setX(int x) {
        this.x = x;
    }

    /**
     * Return the y-coordinate of this square of land.
     */
    public int getY() {
        return y;
    }

    /**
     * Set the y-coordinate of this square of land.
     */
    protected void setY(int y) {
        this.y = y;
    }

    /**
     * Return the state of the land on this square,
     * "Unexplored" if the landtype is yet unknown.
     */
    public String getLandState() {
        if(isExplored()) return getLandType();
        return "Unexplored";
    }

    /**
     * Return the infolist for this square of land.
     */
    public InfoList getInfo() {
        InfoList l = new InfoList();
        if(isExplored()) {
            l.add("Land type", InfoList.STRING, getLandType());
            Iterator it = landResources.keySet().iterator();
            while(it.hasNext()) {
                String name = (String) it.next();
                LandResource r = (LandResource) landResources.get(name);
                if(r.isVisible()) {
                    if(r.getDetermination().equals("numeric"))
                        l.add("o " + name, InfoList.VALUE, r.getVisibleValueAsString());
                    else
                        l.add("o " + name, InfoList.STRING, r.getVisibleValueAsString());
                } else
                    l.add("o " + name, InfoList.STRING, r.getVisibleValueAsString());
            }
        } else {
            l.add("Exploration state", InfoList.PERCENTAGE, getExplorationState());
        }
        return l;
    }

    /**
     * Just for testing and debugging.
     */
    public void printInfo() {
        System.out.println(this);
        InfoList l = getInfo();
        while(l.next())
            System.out.println(l.getName() + ": " + l.getString());
    }

    /**
     * The landtype of this square.
     */
    private String landType;

    /**
     * The human which is living on this square.
     */
    private Human human = null;

    /**
     * The construction which is placed on this square.
     */
    private Construction construction = null;

    /**
     * The explorationstate in percentage.
     */
    private double explorationState = 0;

    /**
     * The coordinates of this square of land.
     */
    private int x, y;

    /**
     * A hash of resource names and a reference to the land resource for this
     * square.
     */
    private Map landResources = new HashMap();

}
