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

package evolution.constructions;

import java.io.*;
import java.util.*;
import java.net.URL;

import evolution.*;
import evolution.lands.*;

/**
 * KnowledgeCatalog for constructions.
 */
public class ConstructionKnowledgeCatalog extends KnowledgeCatalog {

    /**
     * Create a new KnowledgeCatalog from the given file.
     */
    protected ConstructionKnowledgeCatalog(String fileName) throws IOException, FileNotFoundException {
        super(fileName);
        parseConstructions(getProperty("Constructions"));
    }

    /**
     * Return the instance of this singleton.
     */
    public static ConstructionKnowledgeCatalog getInst() {
        if(instance == null)
            throw new RuntimeException("ConstructionKnowledgeCatalog was not initialized");
        return instance;
    }

    /**
     * Initialize this singleton from the given file.
     */
    public static void initialize(String fileName) throws FileNotFoundException, IOException {
        instance = new ConstructionKnowledgeCatalog(fileName);
    }

    /**
     * Does this construction exist in the current game?
     */
    public boolean constructionExists(String constructionName) {
        return getConstructionTypes().contains(constructionName);
    }

    /**
     * Get the priority of this construction.
     */
    public double getPriority(Construction construction) {
        return (new Double(getProperty(construction.getName() + ".priority", "1"))).doubleValue();
    }

    /**
     * Checks if the construction can stand on the given square of land.
     * @param construction The name of the construction.
     * @param sq The square of land to test.
     * @param humans A List of Humans's who will construct the construction.
     * @return true if the construction can stand on this square, false
     * otherwise. If there is a human on the square that is not in the given
     * list, then the construction can not stand on this square.
     */
    protected boolean canStandOn(String construction, SquareOfLand sq, List humans) {
        // if there's already a construction, we don't allow another one
        if(sq.getConstruction() != null) return false;
        if(!sq.isExplored()) return false;
        // if there is a human on the piece not in the list to work
        // on constructing this construction, we don't allow it to be build here
        if((sq.getHuman() != null) && !humans.contains(sq.getHuman())) return false;
        return canStandOnLandType(construction, sq.getLandType());
    }

    /**
     * Checks if the construction can stand on the given squares of land.
     * @param construction The name of the construction.
     * @param squares A List of SquareOfLand's were the construction will
     * be build.
     * @param humans A List of Humans's who will construct the construction.
     * @return true if the construction can stand on the squares, false
     * otherwise. If there is a human on the squares that is not in the given
     * list, then the construction can not stand on these squares.
     */
    protected boolean canStandOn(String construction, List squares, List humans) {
        Iterator it = squares.iterator();
        while(it.hasNext()) {
            SquareOfLand sq = (SquareOfLand) it.next();
            if(!canStandOn(construction, sq, humans)) return false;
        }
        return true;
    }

    /**
     * Checks if the construction can stand on the given landtype.
     */
    public boolean canStandOnLandType(String construction, String landType) {
        List l = split(" ", getProperty(construction + ".constructing.lands", ""));
        return l.contains(landType);
    }

    /**
     * Get the speed at which this construction will be build.
     */
    public double getConstructionSpeed(String constructionName) {
        return Double.parseDouble(getProperty(constructionName + ".constructing.speed", "4"));
    }

    /**
     * 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 split(" ", getProperty(constructionName + ".constructing.shapes", "1x1:1"));
    }

    /**
     * Return the squares used for this shape.
     */
    public int countSquares(String shape) {
        int result = 0;
        try {
            List l = split(":", shape);
            String s = (String) l.get(1);
            for(int i = 0; i < s.length(); i++)
                if(s.charAt(i) == '1')
                    result++;
        } catch(IndexOutOfBoundsException e) {
            throw new IllegalArgumentException("Malformed shapestring: " + shape);
        }
        return result;
    }

    /**
     * Get the squares marked in the shape string with the given square
     * as upper left corner.
     * Returns a list of SquareOfLands.
     * @exception IndexOutOfBoundsException If the given shapes extends out
     * of the game board.
     * @exception IllegalArgumentException If the shape string is malformed.
     */
    protected List getSquares(SquareOfLand sq, String shape) {
        boolean parsingShape = true;
        List result = new ArrayList();
        try {
            List l = split(":", shape);
            List l1 = split("x", (String) l.get(0));
            String s = (String) l.get(1);
            int w = Integer.parseInt((String) l1.get(0));
            int h = Integer.parseInt((String) l1.get(1));
            parsingShape = false;
            for(int y = 0; y < h; y++) {
                for(int x = 0; x < w; x++) {
                    if(s.charAt(y * w + x) == '1') {
                        result.add(World.getInst().getGameBoard().getSquare(sq.getX() + x, sq.getY() + y));
                    }
                }
            }
        } catch(IndexOutOfBoundsException e) {
            if(parsingShape)
                throw new IllegalArgumentException("Malformed shapestring: " + shape);
            throw e;
        }
        return result;
    }

    /**
     * Build a construction.
     * @param constructionName The name of the construction type to build.
     * @param shape The shape of the construction to build,
     * e.g. "1x1:1", "2x2:1101", "3x2:111111101", ...
     * @param humans The humans who will build the construction.
     * @param sq The square of land in the top left of the shape, to position
     * the construction.
     * @exception IllegalPlacementException If it is impossible to build
     * the construction on the indicated place or you have assigned more
     * humans to the job than there are build squares for this construction.
     */
    public void build(String constructionName, String shape, List humans, SquareOfLand sq) throws IllegalPlacementException {
        List squares;
        try {
            squares = getSquares(sq, shape);
        } catch(IndexOutOfBoundsException e) {
            throw new IllegalPlacementException("Sorry dude, you've got to build the " +
                constructionName + " completeley on the board.");
        }
        if(!canStandOn(constructionName, squares, humans))
            throw new IllegalPlacementException("Sorry dude, you've got to build the " +
                constructionName + " somewhere else.");
        if(squares.size() < humans.size())
            throw new IllegalPlacementException("What did I say! You can't build on less squares than you use humans");
        instantiate(constructionName, squares);
        Iterator it1 = humans.iterator();
        Iterator it2 = squares.iterator();
        while(it1.hasNext()) {
            Human human = (Human) it1.next();
            SquareOfLand square = (SquareOfLand) it2.next();
            try {
                //System.err.println("Current human = " + square.getHuman() + ", setting there " + human);
                square.place(human);
                human.setAction("Constructing");
            } catch(IllegalPlacementException e) {
                throw new RuntimeException("This was not supposed to happen Tim!");
            }
        }
    }

    /**
     * Instantiate the given construction on the given squares.
     */
    protected void instantiate(String constructionName, List squares) {
        Construction construction;
        try {
            // first try to dynamically load
            Class c = dlClassForName("constructions", constructionName);
            try {
                construction = (Construction) c.getConstructor(new Class[] {List.class}).
                    newInstance(new Object[] {squares});
            } catch(Exception e1) {
                throw new RuntimeException(constructionName + " does not have a proper constructor.");
            }
        } catch(ClassNotFoundException e) {
            construction = new SimpleConstruction(constructionName, squares);
        }
    }

    /**
     * The resources produced when constructing this construction.
     * @return A Map of (String, Double).
     */
    public Map producesResourcesConstructing(String construction) {
        return makeStringDoubleMap(construction + ".constructing.producesResources", "");
    }

    /**
     * The resources used for constructing this construction.
     * @return A Map of (String, Double).
     */
    public Map usesResourcesConstructing(String construction) {
        return makeStringDoubleMap(construction + ".constructing.usesResources", "");
    }

    /**
     * The landresources produced when constructing this construction.
     * @return A Map of (String, Double).
     */
    public Map producesLandResourcesConstructing(String construction) {
        return makeStringDoubleMap(construction + ".constructing.producesLandResources", "");
    }

    /**
     * The landresources used for constructing this construction.
     * @return A Map of (String, Double).
     */
    public Map usesLandResourcesConstructing(String construction) {
        return makeStringDoubleMap(construction + ".constructing.usesLandResources", "");
    }

    /**
     * Can this construction be build on the specified landtype?
     */
    public boolean canBeBuildOn(String constructionName, String landType) {
        List l = split(" ", getProperty(constructionName + ".constructing.lands", ""));
        return l.contains(landType);
    }

    /**
     * A list with the names of the resources produced by a given construction.
     */
    public List producesResourcesNames(String constructionName) {
        // Dit werkt
        return Arrays.asList((producesResources(constructionName).keySet()).toArray());
    }

    /**
     * A map with as keys the resource and as value the amount the construction produces.
     */
    public Map producesResources(String constructionName) {
           // Dit werkt
        return makeStringDoubleMap(constructionName + ".producesResources", "");
    }

     /**
     * A list with the names of the resources used by a given construction.
     */
    public List usesResourcesNames(String constructionName) {
        // Dit werkt
        return Arrays.asList((usesResources(constructionName).keySet()).toArray());
    }


    /**
     * A map with as keys the resource and as value the amount the construction uses.
     */
    public Map usesResources(String constructionName) {
        return makeStringDoubleMap(constructionName + ".usesResources", "");
    }

    /**
     * A map with as keys the landresource and as value the amount the
     * construction uses.
     */
    public Map usesLandResources(String constructionName) {
        return makeStringDoubleMap(constructionName + ".usesLandResources", "");
    }

    /**
     * Is the given resource in the produce energy chain?
     */
    public boolean isEnergyResource(String resourceName) {
        // hardcoded, could be done generic, but too much work...
        // other things first...
        return resourceName.equals("Energy") ||
            resourceName.equals("Coals");
    }

    /**
     * Parse the constructions-string.
     */
    protected void parseConstructions(String cstring) {
        constructionTypes = split(" ", cstring);
        SystemMessage.message(constructionTypes.size() +
            " constructiontypes loaded");
    }

    /**
     * Return the different types of constructions.
     */
    public List getConstructionTypes() {
        return constructionTypes;
    }

    /**
     * Return infolist for constructions.
     */
    public InfoList getConstructionsInfo() {
        InfoList list = new InfoList();
        Iterator it = getConstructionTypes().iterator();
        while(it.hasNext()) {
            String name = (String) it.next();
            list.add(name, InfoList.STRING, name);
        }
        return list;
    }

    /**
     * The different types of constructions.
     */
    private List constructionTypes;

    /**
     * Instance of the singleton.
     */
    protected static ConstructionKnowledgeCatalog instance = null;

}
