/*$$
 * Copyright (c) 1999, Trustees of the University of Chicago
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with 
 * or without modification, are permitted provided that the following 
 * conditions are met:
 *
 *	 Redistributions of source code must retain the above copyright notice,
 *	 this list of conditions and the following disclaimer.
 *
 *	 Redistributions in binary form must reproduce the above copyright notice,
 *	 this list of conditions and the following disclaimer in the documentation
 *	 and/or other materials provided with the distribution.
 *
 * Neither the name of the University of Chicago nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE TRUSTEES OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *$$*/

import uchicago.src.sim.util.Random;
//import cern.jet.random.Normal;
//import cern.jet.random.Uniform;

/**
 * The agent for the symbiosis simulation. This agent 
 *
 * The source is annotated so see that for more info.
 *
 */

public class TaggedAgent {

	double tag; // my identifying tag

	double tolerance; // how `close' people must be in order for me to donate

	int skill; // the particular skill I have (type of food I gather)

	int numFoodTypes; // The number of different sorts of food

	double[] foodVals; // the amount of each type of food that I have

	double[] newFoodVals; // the amount of each type of food that I have received

	// this round (not to be used for donation in this
	// round)
	double maxReservoir; // maximum amount I can hold of any food type; 

	double maxTol; // maximum tolerance I can have
	
	boolean born;

	int currentAge;

	public TaggedAgent(int numFoodTypes, double initialFood, double maxRes,
			double maxTol, int maxStartAge) {
		this.numFoodTypes = numFoodTypes;
		foodVals = new double[numFoodTypes];
		newFoodVals = new double[numFoodTypes];
		for (int i = 0; i < numFoodTypes; i++) {
			foodVals[i] = initialFood;
			newFoodVals[i] = 0;
		}
		maxReservoir = maxRes;
		this.maxTol = maxTol;
		tag = Random.uniform.nextDouble();
		tolerance = Random.uniform.nextDouble() * maxTol;
		skill = Random.uniform.nextIntFromTo(0, numFoodTypes - 1);
		currentAge = Random.uniform.nextIntFromTo(0, maxStartAge);
		born = false;
	}

	// Just as the model needs get and set accessor methods in order for
	// its initial parameters to be displayed and thus subject to modification,
	// an agent (or any other object) can be probed through similar get and set
	// methods.
	//
	// Probing consists of clicking on an object in the display, causing that
	// object's current state to be displayed. What is displayed depends on
	// the various get and set methods implemented by the object. For example,
	// if an object has a setMetabolism and a getMetabolism method, a Metabolism
	// field will be displayed providing the current value of the metabolism
	// variable and allowing the user to change the value by entering a new value
	// and pressing enter. As of this release only number, String, and boolean
	// fields can be displayed. Of course a user can use the get and set methods
	// to turn a Vector, for example, into a String or whatever is appropriate.

	public boolean isBorn() {
		return born;
	}

	public void setBorn(boolean born) {
		this.born = born;
	}
	
	public void setTag(double tag) {
		this.tag = tag;
	}

	public void setTolerance(double tolerance) {
		this.tolerance = tolerance;
	}

	public void setSkill(int skill) {
		this.skill = skill;
	}

	public void setFood(int foodType, double val) {
		foodVals[foodType] = val;
	}

	public double getTag() {
		return tag;
	}

	public double getTolerance() {
		return tolerance;
	}

	public int getSkill() {
		return skill;
	}

	public double getFood(int foodType) {
		return foodVals[foodType];
	}

	public double getFood() {
		double food = 0;
		for (int foodType = 0; foodType < numFoodTypes; foodType++)
			food += foodVals[foodType];
		return food;
	}

	public int getCurrentAge() {
		return currentAge;
	}

	public void harvestFood(double amount) {
		foodVals[skill] += amount;
		if (foodVals[skill] > maxReservoir)
			foodVals[skill] = maxReservoir;
	}

	public void receiveDonation(int foodType, double amount) {
		newFoodVals[foodType] += amount;
	}

	public void donateFood(int foodType, double amount) {
		foodVals[foodType] -= amount;
	}

	public void consumeFood(double amount) {
		// first incorporate all the food that I received by donation into
		// my main food store, then consume the food I need for this time step
		for (int i = 0; i < numFoodTypes; i++) {
			foodVals[i] += newFoodVals[i];
			if (foodVals[i] > maxReservoir)
				foodVals[i] = maxReservoir;
			newFoodVals[i] = 0;
			foodVals[i] -= amount;
		}
	}

	public boolean shouldReproduce(double threshold) {
		boolean shouldReproduce = true;
		for (int i = 0; i < numFoodTypes; i++) {
			if (foodVals[i] <= threshold)
				shouldReproduce = false;
		}
		return shouldReproduce;
	}

	public boolean reachedAge(int age) {
		return currentAge >= age;
	}

	public boolean isStarved(double threshold) {
		for (int i = 0; i < numFoodTypes; i++) {
			if (foodVals[i] < threshold)
				return true;
		}
		return false;
	}

	public boolean enoughFood(int type, double threshold) {
		return foodVals[type] > threshold;
	}

	public double excessFood(int type, double threshold) {
		double e = foodVals[type] - threshold;
		if (e > 0)
			return e;
		else
			return 0;
	}

	public boolean shouldDonate(double tag) {
		if (Math.abs(this.tag - tag) < tolerance)
			return true;
		else
			return false;
	}

	public void age() {
		currentAge++;
	}

	public void report() {
		System.out.println("My resources:");
		System.out.println("Type\tAmount");
		for (int i = 0; i < numFoodTypes; i++)
			System.out.println(i + "\t" + foodVals[i]);
	}

	public TaggedAgent giveBirth(double probMutation, double sdMut,
            double food, boolean withValMut, boolean withReproductionNoise,
            double noiseSd, int maxStartAge) {
        Random.createNormal(0.0, sdMut);
        Random.createUniform();
        TaggedAgent agent = new TaggedAgent(numFoodTypes, food, maxReservoir,
                maxTol, maxStartAge);
        consumeFood(food); // food passed to offspring
        double tolerance = this.tolerance;
        double tag = this.tag;

        if (withValMut) {
            if (Random.uniform.nextDouble() < probMutation) {
                tolerance += Random.normal.nextDouble();
            }
            if (Random.uniform.nextDouble() < probMutation) {
                tag += Random.normal.nextDouble();
            }
        }
        
        if (withReproductionNoise) {
            tolerance += noiseSd * Random.normal.nextDouble();
            tag += noiseSd * Random.normal.nextDouble();
        }
        
        agent.setTolerance(bound(tolerance, 0, maxTol));
        agent.setTag(bound(tag, 0, 1));
        agent.setSkill(skill);
		agent.setBorn(true);
        return agent;
    }

	private double bound(double val, double min, double max) {
		if (val > max)
			return max;
		if (val < min)
			return min;
		return val;
	}

}

