/* CVS: $Id: WorldController.java,v 1.17 2001/03/19 10:29:31 gvijf Exp $ */

package evolution;

import java.io.*;
import java.util.*;

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

/**
 * A class of world controllers.
 * This is a facade.
 */
public class WorldController implements evolution.events.Observer {

    /**
     * Create a new worldcontroller (and a new world).
     * The world will be created in the pause mode, so one needs
     * to call startGame() to start playing.
     * @param width The width of the game board
     * @param height The height of the game board
     * @param configFile The main configuration file on which this world
     * should be based. From this configuration file the path is stripped
     * and used as the resource path to load other config files and images.
     */
    public WorldController(String configFile, int width, int height) throws FileNotFoundException, IOException {
        world = new World(this, configFile, width, height);
        initState();
        EventManager.getInst().subscribe(this, HumanDiedEvt.class);
    }

    /**
     * Called when an event happens this observer is interested in.
     */
    public void update(Event evt) {
        if(evt instanceof HumanDiedEvt) {
            SystemMessage.message("One died! You have " + getHumanSelection().size() + " humans left in your selection");
        }
    }

    // actions which control the game

    /**
     * Start the game.
     * Does nothing if the game is already started.
     */
    public void startGame() {
        continueGame();
    }

    /**
     * Resume the game.
     */
    public void continueGame() {
        Evolution.getInst().resume();
    }

    /**
     * Pause the game.
     */
    public void pauseGame() {
        Evolution.getInst().pause();
    }

    /**
     * Is the game running?
     */
    public boolean isRunning() {
        return Evolution.getInst().isRunning();
    }

    // actions which manipulate the humans on the board

    /**
     * Select an action to be performed.
     * @exception NoHumanSelectionException If there are no humans selected
     * to perform the given action when a square would be selected.
     */
    public void selectActionType(String actionName) {
        if(!ActionKnowledgeCatalog.getInst().actionExists(actionName))
            throw new RuntimeException("Unknown action '" + actionName + "' selected");
        if(getHumanSelection().empty())
            SystemMessage.message("You must select some humans to perform " + actionName + ",\nbut I think you know what you are doing, right?");
        setActionName(actionName);
        setConstructionName(null);
        SystemMessage.message("action = " + actionName);
    }

    /**
     * Select a construction to be constructed.
     * @exception NoHumanSelectionException If there are no humans selected
     * to perform the action when a square would be selected.
     */
    public void selectConstructionType(String constructionName) {
        selectActionType("Constructing");
        if(!ConstructionKnowledgeCatalog.getInst().constructionExists(constructionName))
            throw new RuntimeException("Unknown construction '" + constructionName + "' selected");
        setConstructionName(constructionName);
        SystemMessage.message("construction = " + constructionName);
    }

    /**
     * Specify the shape the construction should have.
     * e.g. "1x1:1", "2x2:1101", "3x2:111111101"
     */
    public void selectConstructionShape(String shape) {
        setConstructionShape(shape);
    }

    /**
     * Toggle creating a human.
     */
    public void createHuman() {
        setCreateHuman(!isCreateHuman());
    }

    /**
     * Select a square of land.
     * <pre>
     * If there is no human on the square
     *   if we were to create a human
     *      => the human is created
     *   elseif there are humans selected
     *      => one or more humans will be popped of the selection and
     *         start performing the action.
     *   else
     *      => the square is selected
     * else
     *   if the human is selected
     *      => let it perform the selected action
     *   else
     *      => the human is added to the selection and the square is selected
     * </pre>
     * Precondition: the x, y must denote a valid square.
     * @exception IllegalPlacementException The popped human can not stand
     * on the given type of land. The human will stay were ever he was and
     * stays selected.
     */
    public void selectSquareOfLand(int x, int y) throws IllegalPlacementException {
        setSelectedSquare(world.getGameBoard().getSquare(x, y));
        //System.err.println("Selected + " + getSelectedSquare() + ", human = " +
        //    getSelectedSquare().getHuman() +  ", construction = " +
        //    getSelectedSquare().getConstruction());
        if(getSelectedSquare().getHuman() == null) {
            if(isCreateHuman()) {
                try {
                    world.createHuman(getSelectedSquare());
                    getSelectedSquare().getHuman().setAction(getActionName());
                    getHumanSelection().select(getSelectedSquare().getHuman());
                } catch(CreationPowerInsufficientException e) {
                    SystemMessage.message(e.getMessage());
                }
            } else {
                if(!getHumanSelection().empty()) {
                    startAction();
                } else {
                    // nothing to do
                }
            }
        } else {
            //System.err.println("a human");
            if(getHumanSelection().isSelected(getSelectedSquare().getHuman())) {
                //System.err.println("is selected, let him perform " + getActionName());
                startAction();
                //System.err.println("ok");
            } else {
                //System.err.println("not selected, select");
                getHumanSelection().select(getSelectedSquare().getHuman());
                //System.err.println("ok");
                SystemMessage.message("You have " + getHumanSelection().size() + " humans selected");
            }
        }
        setCreateHuman(false);
    }

    /**
     * Start the selected action.
     */
    protected void startAction() throws IllegalPlacementException {
        if(getConstructionName() != null) {
            if(getConstructionShape() == null) {
                SystemMessage.message("Please select the shape for your " + getConstructionName());
                return;
            }
            //System.err.println("start building a " + getConstructionName());
            //SystemMessage.message("Hey jules, I don't know how to build a construction yet! But I'll try...");
            int n = Math.min(ConstructionKnowledgeCatalog.getInst().
                countSquares(getConstructionShape()), getHumanSelection().size());
            SystemMessage.message("Ok, using " + n + " humans to construct a faboulous " + getConstructionName());
            List humans = new ArrayList();
            for(int i = 0; i < n; i++) humans.add(getHumanSelection().pop());
            try {
                ConstructionKnowledgeCatalog.getInst().build(
                    getConstructionName(), getConstructionShape(),
                    humans, getSelectedSquare());
            } catch(IllegalPlacementException e) {
                for(int i = 0; i < n; i++)
                    getHumanSelection().select((Human) humans.get(n-i-1));
                throw e;
            }
            setConstructionName(null);
            setConstructionShape(null);
            //resetActionName();
        } else {
            Human human = getHumanSelection().pop();
            SquareOfLand oldSquare = human.getSquareOfLand();
            try {
                if(getSelectedSquare() != oldSquare) {
                    getSelectedSquare().place(human);
                }
                SystemMessage.message("I command you, go " + getActionName());
                human.setAction(getActionName());
            } catch(IllegalPlacementException e) {
                // add the human back to the selection
                getHumanSelection().select(human);
                throw e;
            }
        }
        if(getHumanSelection().empty()) {
            SystemMessage.message("If I were you, I would search some lazy humans to command...");
        } else {
            SystemMessage.message("Still " + getHumanSelection().size() + " humans to command!");
        }
    }

    /**
     * Clear the selected humans.
     */
    public void clearSelection() {
        getHumanSelection().clear();
    }

    /** 
     * Modify the visionrange of this worldcontroller.
     */
    public void modifyVisionRange(int value) {
	while (!humanSelection.empty()) {
	    Human human = humanSelection.pop();
	    try {
		human.setVisionRange(human.getVisionRange() + value);
	    }
	    catch (InvalidVisionRangeException exc) {
		SystemMessage.message(exc.getMessage());
	    }
	}
    }

    // information requests about the current selected square

    /**
     * Get an infolist with all the world resources.
     */
    public InfoList getResourcesInfo() {
        return getWorld().getResourcesInfo();
    }

    /**
     * Get an infolist with info about the selected square.
     */
    public InfoList getSquareInfo() {
        if(getSelectedSquare() == null) return new InfoList();
        return getSelectedSquare().getInfo();
    }

    /**
     * Get an infolist with info about the selected human.
     */
    public InfoList getHumanInfo() {
        if((getSelectedSquare() == null) || (getSelectedSquare().getHuman() == null))
            return new InfoList();
        return getSelectedSquare().getHuman().getInfo();
    }

    /**
     * Get an infolist with info about the selected construction.
     */
    public InfoList getConstructionInfo() {
        if((getSelectedSquare() == null) || (getSelectedSquare().getConstruction() == null))
            return new InfoList();
        return getSelectedSquare().getConstruction().getInfo();

    }

    /**
     * The shapes the given construction can be build in.
     * Returns a List of Strings according to the shape spec.
     * e.g. {"1x1:1", "2x2:1101", "3x2:111111101"}
     */
    public List getConstructionShapes(String constructionName) {
        return ConstructionKnowledgeCatalog.getInst().getConstructionShapes(constructionName);
    }

    // information about the objects in this game

    /**
     * All available actions.
     */
    public InfoList getActionsInfo() {
        return ActionKnowledgeCatalog.getInst().getActionsInfo();
    }

    /**
     * All available constructions.
     */
    public InfoList getConstructionsInfo() {
        // the action to build a construction is hardwired as 'Constructing'
        if(!ActionKnowledgeCatalog.getInst().actionExists("Constructing")) {
            return new InfoList();
        }
        return ConstructionKnowledgeCatalog.getInst().getConstructionsInfo();
    }

    /**
     * The resource path.
     */
    public String getResourcePath() {
        return KnowledgeCatalog.getResourcePath();
    }

    /**
     * Get the width of this world.
     */
    public int getWidth() {
        return getWorld().getWidth();
    }

    /**
     * Get the height of this world.
     */
    public int getHeight() {
        return getWorld().getHeight();
    }

    /**
     * Let all the squares fire a changed event.
     */
    public void refresh() {
        getWorld().refresh();
    }

    /**
     * Is the given human selected?
     */
    public boolean isSelected(Human human) {
        return getHumanSelection().isSelected(human);
    }

    /**
     * Is the given human actively selected?
     */
    public boolean isActiveSelected(Human human) {
        return getHumanSelection().isActiveSelected(human);
    }

    // the things we use to do this

    /**
     * Return to the initial state of this worldcontroller.
     */
    protected void initState() {
        humanSelection = new HumanSelection();
        selectedSquare = world.getGameBoard().getSquare(0, 0);
        resetActionName();
        setConstructionName(null);
        setCreateHuman(false);
    }

    /**
     * Return the human selection of this human.
     */
    protected HumanSelection getHumanSelection() {
        return humanSelection;
    }

    /**
     * Return the square of land that is currently selected.
     */
    protected SquareOfLand getSelectedSquare() {
        return selectedSquare;
    }

    /**
     * Set the selected square of land to the given square of land.
     */
    protected void setSelectedSquare(SquareOfLand square) {
        selectedSquare = square;
    }

    /**
     * Return the name of the currently selected action.
     */ 
    protected String getActionName() {
        return actionName;
    }

    /**
     * Set the selected action to the given action.
     */
    protected void setActionName(String actionName) {
        this.actionName = actionName;
    }

    /**
     * Set the selected action to the default action.
     */
    protected void resetActionName() {
        String actionName = ActionKnowledgeCatalog.getInst().getDefaultActionType();
        setActionName(actionName);
        SystemMessage.message("action = " + actionName);
    }

    /**
     * Return the currently selected construction name.
     */
    protected String getConstructionName() {
        return constructionName;
    }

    /**
     * Set the selected construction name to the given construction name.
     */
    protected void setConstructionName(String constructionName) {
        this.constructionName = constructionName;
    }

    /**
     * Return the selected construction shape.
     */
    protected String getConstructionShape() {
        return constructionShape;
    }

    /**
     * Set the selected construction shape to the given construction shape.
     */
    protected void setConstructionShape(String constructionShape) {
        this.constructionShape = constructionShape;
    }

    /**
     * Return whether we are going to create a human with the next 
     * square selection 
     */
    protected boolean isCreateHuman() {
        return createHuman;
    }

    /**
     * Set the boolean value that determines whether we are going 
     * to create a human with the next square selection
     */
    protected void setCreateHuman(boolean b) {
        createHuman = b;
    }

    /**
     * return the world (= singleton !) of this worldcontroller.
     */ 
    protected World getWorld() {
        return world;
    }

    /**
     * The human selection.
     */
    private HumanSelection humanSelection;

    /**
     * The selected square of land.
     */
    private SquareOfLand selectedSquare;

    /**
     * The action name which was selected.
     */
    private String actionName;

    /**
     * The construction name which was selected.
     */
    private String constructionName = null;

    /**
     * The construction shape which was selected.
     */
    private String constructionShape = null;

    /**
     * Are we gonna create a human with the next square selection?
     */
    private boolean createHuman;

    /**
     * Just for easy referencing, but remember: world is a singleton.
     */
    private World world;
}