/* CVS: $Id: Resource.java,v 1.17 2001/03/18 20:54:12 gvijf Exp $ */

package evolution.resources;

import java.lang.Math;
import java.util.*;

import evolution.*;

/**
 * A world resource.
 */
abstract public class Resource {

    /**
     * A linear transformation function.
     */
    public final static int LINEAR = 0;

    /**
     * A saturation transformation function.
     */
    public final static int SLOPE = 1;

    /**
     * Create a new world resource.
     * @param value  The initial value.
     * @param min The minimum value.
     * @param max The maximum value.
     * @param function The transform input/output function, one of LINEAR,
     * SLOPE.
     */
    public Resource(double value, double min, double max, int function){
        setLimits(min, max, function);
        try {
            setValue(value);
        } catch(NotEnoughResourcesException e) {
            //cannot happen, this would mean there's a mistake in our config files
            throw new RuntimeException(value + " not in [" + min + ", " + max + "]");
        }
    }

    /**
     * Set the limits of this resource.
     */
    protected void setLimits(double min, double max, int function) {
        if(max <= min)
            throw new RuntimeException("A resource max must be <= than a min");
        if ((min < 0) || (max < 0))
            throw new RuntimeException("The minimum and maximum value of a resource can not be negative");
        this.min = min;
        this.max = max;
        this.norm = max / Math.log(1 + max);
        this.function = function;
    }

    /**
     * Get the name of this world resource.
     */
    public String getName() {
        return ResourceKnowledgeCatalog.getInst().stripPathFromClassName(this);
    }

    /**
     * Get the value of this world resource.
     */
    public double getValue() {
        return value;
    }

    /**
     * Get the minimal value of this world resource.
     */
    public double getMin() {
        return min;
    }

    /**
     * Modify the value of this worldresource.
     * This is done according to the input/output function.
     * If this resource does not have enough it will try to transform
     * some other resource to get enough.
     * @exception NotEnoughResourcesException If we go below the minimum
     * of this resource and no other resource could fill the need.
     */
    public void modValue(double v) throws NotEnoughResourcesException {
        //System.err.println(getName() + ": mod(" + v + "):" + getValue());
        double oldValue = getValue();
        try {
            switch(function) {
                case LINEAR:
                   setValue(getValue() + v);
                   break;
                case SLOPE:
                    if (getValue() + v < 0) setValue(getValue() + v);
                    else setValue(norm * Math.log(1+getValue()+v));
                    break;
                default:
                    throw new RuntimeException("Unknown function name for world resource");
            }
        } catch(NotEnoughResourcesException e) {
            // proberen om een automatische omzetting te doen
            // van een andere world resource naar deze
            v += oldValue - getValue();
            if(autoModOthers(v) > 0) {
                // if still not fullfilled
                throw e;
            }
        }
    }

    /**
     * Try to transform another resource into this one.
     * @param amount The amount needed from this resource.
     * @return The amount left.
     */
    protected double autoModOthers(double amount) {
        //System.err.println(getName() + " autoMod(" + amount + ")");
        List alternatives = ResourceKnowledgeCatalog.getInst().transformsFrom(getName());
        //System.err.println(alternatives);
        Iterator it = alternatives.iterator();
        while(it.hasNext()) {
            Object o[] = (Object[]) it.next();
            Resource r = (Resource) o[0];
            double ratio = ((Double) o[1]).doubleValue();
            double oldValue = r.getValue();
            try {
                r.modValue(amount * ratio);
                return 0; // ok a fineshed job
            } catch(NotEnoughResourcesException e) {
                // see what's left and try the next one
                amount += (oldValue - r.getValue()) / ratio;
            }
        }
        return amount; // an unfineshd job for this amount
    }

    /**
     * See if we have enough of this resource.
     * Will try to use other resources to fullfill the need.
     */
    public boolean has(double value) {
        //System.err.println(getName() + ": has(" + value + "), value = " + getValue());
        return (getValue() >= value) || (autoHasOthers(value - getValue()) == 0);
    }

    /**
     * Try to convert from other resources.
     */
    protected double autoHasOthers(double value) {
        //System.err.println(getName() + ": autoHas(" + value + ")");
        List alternatives = ResourceKnowledgeCatalog.getInst().transformsFrom(getName());
        //System.err.println(getName() + ": " + alternatives);
        Iterator it = alternatives.iterator();
        while(it.hasNext()) {
            Object o[] = (Object[]) it.next();
            Resource r = (Resource) o[0];
            //System.err.println(getName() + ": alt = " + r.getName() + ", " + r.getValue());
            double ratio = ((Double) o[1]).doubleValue();
            if(r.getValue() * ratio >= value) return 0;
            value += r.getValue() / ratio;
        }
        return value; // an unfineshd job for this amount
    }

    /**
     * Set the value of this resource.
     * @exception NotEnoughResourcesException If you try to set this resource
     * below its minimal level.
     */
    public void setValue(double value) throws NotEnoughResourcesException {
        if(value > max) {
            this.value = max;
        } else if(value < min) {
            this.value = min;
            throw new NotEnoughResourcesException();
        } else this.value = value;
    }

    /**
     * Influence a value by this resource.
     */
    public double influence(double v) {
        return v*(1 + getValue()/max);
    }

    private double value;
    private double min;
    private double max;
    private double norm;
    private int function;

}
