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

package evolution.actions;

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

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

/**
 * The KnowledgeCatalog for evolution.actions.
 * @stereotype singleton 
 */
public class ActionKnowledgeCatalog extends KnowledgeCatalog {

    /**
     * Create a new KnowledgeCatalog from the given file.
     */
    protected ActionKnowledgeCatalog(String fileName) throws FileNotFoundException, IOException {
        super(fileName);
        parseActionTypes(getProperty("Actions"));
        parseDefaultActionType(getProperty("DefaultAction"));
        parseEnergyBufferActionTypes(getProperty("EnergyBufferActions"));
    }

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

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

    /**
     * The normal priority for this action.
     */
    public double getPriority(Action action) {
        return (new Double(getProperty(action.getName() + ".priority", "1"))).doubleValue();
    }

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

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

    /**
     * Which resource is transformed into which resource by a given action.
     * Returns a map with as key the name of the resource it transforms into and as
     * value an array with als first element the name of the resource
     * which is transformed and as second element a Double transformationvalue.
     */
    //public Map transformsResources(String actionType) {
    //    Map m = new HashMap();
    //    List lt = split(" ", getProperty(actionType + ".transformsResources", ""));
    //    Iterator it = lt.iterator();
    //    while(it.hasNext()) {
    //        String e = (String) it.next();
    //        List fromTo = split("-", extractName(e));
    //        Double value = extractValue(e);
    //        if (! m.containsKey(fromTo.get(1))) {
    //            Map n = new HashMap();
    //            //een hashmap met als sleutel de producerende resource en als waarde
    //            //in welke verhouding er geproduceerd wordt
    //            n.put(fromTo.get(0), value);
    //            //een hasmap met als sleutel de geproduceerde resource (bvb energy)
    //           //en als waarde een nieuw hashtabel die hierboven gedefinieerd is
    //            m.put(fromTo.get(1), n);
    //       }
    //        else {
    //            ((HashMap)m.get(fromTo.get(1))).put(fromTo.get(0), value);
    //        }
    //    }
    //    return m;
    //}

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

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

    /**
     * Which resources are transformed into a given resource by a given construction?
     */
    //public Object[] areTransformedInto(String resource, String actionType){
    //    Map map = this.getInst().transformsResources(actionType);
    //    return (Object[]) map.get(resource);
    //}

   /**
    * Which resources are transformed into a given resource.
    */
    //public List areTransformedInto(String resource) {
    //    Iterator it = this.getInst().getActionTypes().iterator();
    //    List list = new ArrayList();
    //    while(it.hasNext()) {
    //        String actionName = (String) it.next();
    //        list.add(areTransformedInto(resource, actionName));
    //    }
    //    return list;
    //}


    

    /**
     * A list of resources by which this action is influenced.
     */
    public List influencedBy(String actionName) {
        String property = getProperty(actionName + ".influencedBy", "");
        if (property == null)
            return new ArrayList();
        return split(" ", property);
    }

    /**
     * Check whether the given action must be explored or not.
     */
    public boolean mustBeExplored(String actionName) {
        return (new Boolean(getProperty(actionName + ".explored", "true"))).booleanValue();
    }

    /**
     * See if this action can be performed on the given square.
     * @param actionName Something like Resting, Fishing, ...
     */
    public boolean canBePerformedOn(String actionName, SquareOfLand sq) {
        List landTypes = split(" ", getProperty(actionName + ".lands", ""));
        if(sq.isExplored()) {
            // check if there is a construction
            if(sq.getConstruction() != null) {
                // check if the action would transform the land
                // and ifso if the construction can stand on that land
                Object[] r = transformsLand(sq, actionName);
                if(((Boolean) r[0]).booleanValue()) {
                    return landTypes.contains(sq.getLandType()) &&
                        ConstructionKnowledgeCatalog.getInst().canStandOnLandType(
                        sq.getConstruction().getName(), (String) r[1]);
                }
            }
            return landTypes.contains(sq.getLandType());
        }
        return !mustBeExplored(actionName) && landTypes.contains(sq.getLandType());
    }

    /**
     * See if this action can perform better when the needed landresources
     * are high.
     */
    public boolean isMaximizing(String actionName) {
        return (new Boolean(getProperty(actionName + ".maximizing", "true"))).booleanValue();
	}

    /**
     * Does the given action transform the landtype of the given square?
     * <pre>
     * Returns Object[] {
     *   Boolean transforms        : does the action transform yes or no
     *   String toLand             : the land type we transform to
     *   String detLandResource    : the determening land resource
     *   Double limit              : the limit the land resource should have
     *                               to transform the land type
     *   Boolean lte               : must the current value be less then equal
     *                               than the limit
     *   Boolean gte               : must the current value be greater then equal
     *                               than the limit
     * </pre>
     */
    public Object[] transformsLand(SquareOfLand sq, String actionName) {
        // FIXME: should be cashed!!!!
        Boolean lte = new Boolean(false);
        Boolean gte = new Boolean(false);
        String toLand = null;
        String detLandResource = null;
        Double limit = new Double(0);
        List l = split(" ", getProperty(actionName + ".transformsLand", ""));
        Iterator it = l.iterator();
        while(it.hasNext()) {
            String e = (String) it.next();
            String lands = extractName(e);
            String fromLand = (String) split("-", lands).get(0);
            if(fromLand.equals(sq.getLandType())) {
                toLand = (String) split("-", lands).get(1);
                detLandResource = (String) split("=", extractArgument(e)).get(0);
                limit = new Double((String) split("=", extractArgument(e)).get(1));
                if(producesLandResource(actionName, detLandResource) != null)
                    gte = new Boolean(true);
                if(usesLandResource(actionName, detLandResource) != null)
                    lte = new Boolean(true);
                return new Object[] {new Boolean(true), toLand, detLandResource, limit, lte, gte};
            }
        }
        return new Object[] {new Boolean(false)};
    }

    /**
     * Returns the amount produced by the given action of the given
     * land resource.
     * Returns null if the land resource is not produced by this action.
     */
    public Double producesLandResource(String actionName, String resName) {
        List lt = split(" ", getProperty(actionName + ".producesLandResources", ""));
        Iterator it = lt.iterator();
        while(it.hasNext()) {
            String e = (String) it.next();
            String res = extractName(e);
            if(res.equals(resName))
                return extractValue(e);
        }
        return null;
    }

    /**
     * Returns the amount used by the given action of the given
     * land resource.
     * Returns null if the land resource is not used by this action.
     */
    public Double usesLandResource(String actionName, String resName) {
        List lt = split(" ", getProperty(actionName + ".usesLandResources", ""));
        Iterator it = lt.iterator();
        while(it.hasNext()) {
            String e = (String) it.next();
            String res = extractName(e);
            if(res.equals(resName))
                return extractValue(e);
        }
        return null;
    }

    /**
     * A hash of resources/speeds for the given action.
     */
    public Map reveals(Action action) {
        return new HashMap();
    }

    /**
     * Does the action with this name exist?
     */
    public boolean actionExists(String actionName) {
        return actionTypes.contains(actionName);
    }

    /**
     * Instantiate the action with the given name on the given square.
     * precondition: the actionName must be valid
     */
    public Action instantiate(String actionName) {
        if(!actionExists(actionName))
            throw new RuntimeException(actionName + " is not a valid action name");
        try {
            // first try to dynamically load
            Class c = dlClassForName("actions", actionName);
            try {
                return (Action) c.newInstance();
            } catch(Exception e) {
                throw new RuntimeException(actionName +
                 " does not have a proper default constructor");
            }
        } catch(ClassNotFoundException e) {
            // if not dynamically loadable the action is completely defined
            // by the config file
            return new SimpleAction(actionName);
        }
    }

    /**
     * An info list with all possible actions in the game.
     */
    public InfoList getActionsInfo() {
        InfoList list = new InfoList();
        List actionTypes = getActionTypes();
        Iterator it = actionTypes.iterator();
        while(it.hasNext()) {
            String name = (String) it.next();
            //if(!name.equals("Constructing"))
            list.add(name, InfoList.STRING, name);
        }
        return list;
    }

    /**
     * Return a list of all possible actions.
     * This is a list of Strings.
     */
    public List getActionTypes() {
        return actionTypes;
    }
    
    /**
     * Return a list of all possible actions while using the energy buffer.
     * This is a list of Strings.
     */
    public List getEnergyBufferActionTypes() {
        return energyBufferActionTypes;
    }

    /**
     * Return the the name for the default action.
     */
    public String getDefaultActionType() {
        return defaultActionType;
    }

    /**
     * Return an instance of the default action type.
     * This is cached version.
     */
    public Action getDefaultAction() {
        if(defaultAction == null)
	        defaultAction = instantiate(getDefaultActionType());
        return defaultAction;
    }

    /**
     * Parse a actiontypes string.
     */
    protected void parseActionTypes(String actionTypesString) {
        actionTypes = parseActionTypesInto(actionTypesString, null);
        SystemMessage.message(actionTypes.size() + " actiontypes loaded");
    }

    /**
     * Parse a energy buffer actiontypes string.
     */
    protected void parseEnergyBufferActionTypes(String actionTypesString) {
        energyBufferActionTypes = parseActionTypesInto(actionTypesString, actionTypes);
    }

    /**
     * Parse the default actiontype string.
     */
    protected void parseDefaultActionType(String actionTypeString) {
        defaultActionType = actionTypeString;
        if(defaultActionType == null)
            throw new RuntimeException("No default actiontype found");
        if(!actionTypes.contains(defaultActionType))
            throw new RuntimeException("Default action type is not in the Actions property");
    }

    /**
     * Parse a actiontypes string.
     * @param retain A List of action names. If this is not null, only
     * the specified actions will be extracted.
     */
    protected List parseActionTypesInto(String actionTypesString, List retain) {
        List lt = split(" ", actionTypesString);
        Iterator it = lt.iterator();
        List result = new ArrayList();
        while(it.hasNext()) {
            String type = (String) it.next();
            if((retain == null) || retain.contains(type)) {
            	result.add(type);
            }
        }
        return result;
    }

    /**
     * An array of all the loaded actions.
     * These are extracted from the properties list at construction.
     * This is a list of Strings.
     */
    private List actionTypes = new ArrayList();

    /**
     * The default actiontype.
     * This is extracted from the properties list at construction.
     */
    private String defaultActionType;

    /**
     * A cached version of an instantiated default action.
     */
    private Action defaultAction;

    /**
     * The actiontypes that can be performed when using the EnergyBuffer.
     * These are extracted from the properties list at construction.
     * This is a list of Strings.
     */
    private List energyBufferActionTypes = new ArrayList();

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

}