/*$$
 * 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 java.awt.Color;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
// import java.util.TreeSet;
import java.util.Vector;
// import java.lang.Math;

import uchicago.src.reflector.BooleanPropertyDescriptor;
import uchicago.src.sim.analysis.DataSource;
import uchicago.src.sim.analysis.DataRecorder;
import uchicago.src.sim.analysis.Histogram;
//import uchicago.src.sim.analysis.OpenHistogram;
import uchicago.src.sim.analysis.OpenSequenceGraph;
import uchicago.src.sim.analysis.OpenStats;
import uchicago.src.sim.analysis.Sequence;
import uchicago.src.sim.analysis.BinDataSource;
import uchicago.src.sim.analysis.plot.OpenGraph;
import uchicago.src.sim.analysis.Plot;
// import uchicago.src.sim.engine.BasicAction;
import uchicago.src.sim.engine.Schedule;
import uchicago.src.sim.engine.SimInit;
import uchicago.src.sim.engine.SimModelImpl;
import uchicago.src.sim.gui.ColorMap;
// import uchicago.src.sim.gui.DisplaySurface;
// import uchicago.src.sim.gui.Object2DDisplay;
// import uchicago.src.sim.gui.Value2DDisplay;
// import uchicago.src.sim.space.Object2DTorus;
import uchicago.src.sim.util.SimUtilities;
import uchicago.src.sim.util.Random;

// import cern.jet.random.Uniform;
// import cern.jet.random.Normal;

/**
 * 
 * @author Emma Norling
 * with mistakes, switches and extra graphs added by Bruce Edmonds
 */

// Simulation models should extend SimModelImpl which does some basic setup
// and allows a controller easy access to a simulations initial parameters.
public class FoodModel extends SimModelImpl {

	// Every model must have a schedule
	private Schedule schedule;

	// A list of all the agents. Convenient for any method that
	// iterates through a list of agents. A least one list like
	// this is common to most simulations. See below for examples
	// of its use.
	private ArrayList<TaggedAgent> agentList = new ArrayList<TaggedAgent>();

	// A list of TaggedAgents who have been "birthed" and should
	// be introduced to the simulation in the next turn
	private Vector birthList = new Vector();

	// A queue that tracks dead agents.
	private Vector reaperQueue = new Vector();

	// A DataRecorder allows data generated by a simulation to be written
	// to a file
	private DataRecorder recorder;

	private DataRecorder finalRecorder;

	// initial starting parameters of the model

	private int initialPopSize = 0;

	private int maxTime = 10;

	private int numPairings = 6;

	private double probMutVal = 0.05;

	private double sdMut = 0.05;

	private int maxNumNew = 2;

	private double donationCost = 1;

	private double donationBenefit = 0.95;

    // private int foodPerInitialPop = 2;
	// private int numFood = initialPopSize * foodPerInitialPop;
    private int numFood = 200;
    private int realNumFood = numFood;
    
	private int numFoodTypes = 2;

	private int maxTagAge = 30;

	private int maxStartAge = 0;

	private double initialFood = 1;

	private double foodOfTypeNecessaryForReproduction = 4;

	private double foodOfTypeBelowWhichTagDies = 0;

	private double foodOfTypeAboveWhichIsExtra = 5;

	private double foodUsageRate = 0.25;

	private double maxReservoir = 7.5;

	private double maxTol = 0.05;

	private double reproductionNoiseSd = 0.0001;

	private int lastMaxDataSet = 0;

	// variables for statistics
	private int numDonationPackages;
    
	private double sumNumDonationPackages;

    private int numDonationEvents;
    
    private double sumNumDonationEvents;

	private double sumDonationRate;

	private int numCloneDonationPackages;

	private double sumCloneDonationRate;

	private int numBirths;

	private double sumNumBirths;

	private int numAgeDeaths;

	private double sumNumAgeDeaths;

	private int numStarveDeaths;

	private double sumNumStarveDeaths;

	private double gathered;

	private double excess;

	private double sumAvExcess;

	private double totalDonationAmount;

	private int sumNumAgents;

	private int totalNumPairings;

	private double sumAvTolerance;

	private double sumAvGathered;

	private double sumAvDonationAmount;

	private double sumTotalResources;

	private boolean popLow;

	private int countPopLow;
	
	private boolean bornOfAllTypes;
	
	private int countNotBornOfAllTypes;

	private int finalStartClick = 1000;

	{
		assert finalStartClick <= maxTime;
	}

	private int[] numAgentType;

	// various switches for recording and display
	private boolean write = true;

	private boolean writeFinal = false;

	private boolean display = false;

	private boolean profile = false;

	private ColorMap colorMap = new ColorMap();

	private boolean histogram = false;

	private boolean subPops = false;

	// extra switches for testing code
	private boolean withDeath = true;

	private boolean withAsexualReproduction = true;

	private boolean withReproductionNoise = false;

	private boolean withNew = true;

	private boolean withValMut = true;

	private boolean withSkillMut = false;

	private boolean withTagVariety = true;

	private boolean withRandomFoodScatter = true;

	private boolean withRandomInitialSkills = true;

	/**
	 * @return Returns the write.
	 */
	public boolean isWrite() {
		return write;
	}

	/**
	 * @param write
	 *            The write to set.
	 */
	public void setWrite(boolean write) {
		this.write = write;
	}

	public boolean isWriteFinal() {
		return writeFinal;
	}

	public void setWriteFinal(boolean writeFinal) {
		this.writeFinal = writeFinal;
	}

	public int getFinalStartClick() {
		return finalStartClick;
	}

	public void setFinalStartClick(int finalStartClick) {
		this.finalStartClick = finalStartClick;
	}

//    public int getFoodPerInitialPop() {
//        return foodPerInitialPop;
//    }
//
//    public void setFoodPerInitialPop(int foodPerInitialPop) {
//        this.foodPerInitialPop = foodPerInitialPop;
//    }
    
	public boolean isProfile() {
		return profile;
	}

	public void setProfile(boolean profile) {
		this.profile = profile;
	}

	public boolean isDisplay() {
		return display;
	}

	public void setDisplay(boolean display) {
		this.display = display;
	}

	public boolean isHistogram() {
		return histogram;
	}

	public void setHistogram(boolean histogram) {
		this.histogram = histogram;
	}

	public boolean isSubPops() {
		return subPops;
	}

	public void setSubPops(boolean subPops) {
		this.subPops = subPops;
	}

	public boolean isWithDeath() {
		return withDeath;
	}

	public void setWithDeath(boolean withDeath) {
		this.withDeath = withDeath;
	}

	public boolean isWithAsexualReproduction() {
		return withAsexualReproduction;
	}

	public void setWithAsexualReproduction(boolean withAsexualReproduction) {
		this.withAsexualReproduction = withAsexualReproduction;
	}

	public boolean isWithReproductionNoise() {
		return withReproductionNoise;
	}

	public void setWithReproductionNoise(boolean withReproductionNoise) {
		this.withReproductionNoise = withReproductionNoise;
	}

	public boolean isWithNew() {
		return withNew;
	}

	public void setWithNew(boolean withNew) {
		this.withNew = withNew;
	}

	public boolean isWithValMut() {
		return withValMut;
	}

	public void setWithValMut(boolean withValMut) {
		this.withValMut = withValMut;
	}

	public boolean isWithSkillMut() {
		return withSkillMut;
	}

	public void setWithSkillMut(boolean withSkillMut) {
		this.withSkillMut = withSkillMut;
	}

	public boolean isWithTagVariety() {
		return withTagVariety;
	}

	public void setWithTagVariety(boolean withTagVariety) {
		this.withTagVariety = withTagVariety;
	}

	public boolean isWithRandomFoodScatter() {
		return withRandomFoodScatter;
	}

	public void setWithRandomFoodScatter(boolean withRandomFoodScatter) {
		this.withRandomFoodScatter = withRandomFoodScatter;
	}

	public boolean isWithRandomInitialSkills() {
		return withRandomInitialSkills;
	}

	public void setWithRandomInitialSkills(boolean withRandomInitialSkills) {
		this.withRandomInitialSkills = withRandomInitialSkills;
	}

	public double getReproductionNoiseSd() {
		return reproductionNoiseSd;
	}

	public void setReproductionNoiseSd(double reproductionNoiseSd) {
		this.reproductionNoiseSd = reproductionNoiseSd;
	}

	private OpenSequenceGraph graph;

	private Plot profilePlot;

	private OpenSequenceGraph subPopsGraph;

	private Histogram histogramGraph;

	// All simulations should have a no argument constructor - this
	// is empty and thus not strictly necessary
	public FoodModel() {
		BooleanPropertyDescriptor bd = new BooleanPropertyDescriptor(
				"Replacement", false);
		descriptors.put("Replacement", bd);

		Random.createNormal(0.0, sdMut);
		Random.createUniform();

	}

	private void buildModel() {
		
		numDonationPackages = 0;
        numDonationEvents = 0;
		numCloneDonationPackages = 0;
		numAgentType = new int[numFoodTypes];
		foodVals = new double[numFoodTypes];
		for (int i = 0; i < numAgentType.length; i++)
			numAgentType[i] = 0;
		agentList = new ArrayList<TaggedAgent>();
		birthList = new Vector();
		reaperQueue = new Vector();

		for (int i = 0; i < initialPopSize; i++) {
			int skill = -1;
			if (!withRandomInitialSkills) {
				skill = i % numFoodTypes;
			}
			addNewAgent(skill);
		}

		if (write) {
			recorder = new DataRecorder("./output/food_model_data.csv", this);

			recorder.addObjectDataSource("pop", new DataSource() {
				public Object execute() {
					return new Integer(agentList.size());
				}
			});

			recorder.addObjectDataSource("don", new DataSource() {
				public Object execute() {
					double rate = (double) numDonationPackages
							/ (numPairings * agentList.size());
					return new Double(rate);
				}
			});

			recorder.addObjectDataSource("clone",
					new DataSource() {
						public Object execute() {
							double rate = (double) numCloneDonationPackages
									/ (numPairings * agentList.size());
							return new Double(rate);
						}
					});

			recorder.addObjectDataSource("tol", new DataSource() {
				public Object execute() {
					double tol = 0;
					for (int i = 0; i < agentList.size(); i++)
						tol += agentList.get(i).getTolerance();
					return new Double(tol / agentList.size());
				}
			});

			recorder.addObjectDataSource("births", new DataSource() {
				public Object execute() {
					return new Integer(numBirths);
				}
			});

			recorder.addObjectDataSource("deaths",
					new DataSource() {
						public Object execute() {
							return new Integer(numAgeDeaths + numStarveDeaths);
						}
					});

			recorder.addObjectDataSource("old",
					new DataSource() {
						public Object execute() {
							return new Integer(numAgeDeaths);
						}
					});

            recorder.addObjectDataSource("starve",
                    new DataSource() {
                        public Object execute() {
                            return new Integer(numStarveDeaths);
                        }
                    });

			recorder.addObjectDataSource("tdons",
					new DataSource() {
						public Object execute() {
							return new Integer(numDonationPackages);
						}
					});

			recorder.addObjectDataSource("avGath", new DataSource() {
				public Object execute() {
					return new Double(gathered / agentList.size());
				}
			});

			recorder.addObjectDataSource("avExcess", new DataSource() {
				public Object execute() {
					return new Double(excess
							/ (numFoodTypes * agentList.size()));
				}
			});

			recorder.addObjectDataSource("avDonAmount", new DataSource() {
				public Object execute() {
					if (numDonationEvents != 0) {return new Double(totalDonationAmount / numDonationEvents);
                    }
                    else {return (double) 0}
                    }
				}
			});

			recorder.addObjectDataSource("tres", new DataSource() {
				public Object execute() {
					double resources = 0;
					for (int i = 0; i < agentList.size(); i++)
						resources += agentList.get(i).getFood();
					return new Double(resources);
				}
			});

			// attempt at un-hard coding of the above code
			for (int i = 0; i < numFoodTypes; i++) {
				final int ii = i;
				recorder.addObjectDataSource("Proportion with skill " + i,
						new DataSource() {
							public Object execute() {
								return new Double((double) numAgentType[ii]
										/ agentList.size());
							}
						});
			}
		}

		if (writeFinal) {
			final int base = maxTime - finalStartClick + 1;
			// final int base = 1;
			sumDonationRate = 0;
			sumNumDonationPackages = 0;
			sumCloneDonationRate = 0;
			sumNumBirths = 0;
			sumNumAgeDeaths = 0;
			sumNumStarveDeaths = 0;
			sumAvGathered = 0;
			sumAvExcess = 0;
			sumNumAgents = 0;
			totalNumPairings = 0;
			sumAvTolerance = 0;
			sumAvGathered = 0;
			sumAvExcess = 0;
			sumAvDonationAmount = 0;
			sumTotalResources = 0;
			countPopLow = 0;
			countNotBornOfAllTypes = 0;

			finalRecorder = new DataRecorder(
					"./output/food_model_final_data.csv", this);

			finalRecorder.addObjectDataSource("pop",
					new DataSource() {
						public Object execute() {
							return new Double((double) sumNumAgents
									/ (double) base);
						}
					});

			finalRecorder.addObjectDataSource("don",
					new DataSource() {
						public Object execute() {
							return new Double(sumDonationRate / base);
						}
					});

			finalRecorder.addObjectDataSource("clone",
					new DataSource() {
						public Object execute() {
							return new Double(sumCloneDonationRate / base);
						}
					});

			finalRecorder.addObjectDataSource("tol",
					new DataSource() {
						public Object execute() {
							return new Double(sumAvTolerance / base);
						}
					});

			finalRecorder.addObjectDataSource("births",
					new DataSource() {
						public Object execute() {
							return new Double(sumNumBirths / base);
						}
					});

			finalRecorder.addObjectDataSource("deaths",
					new DataSource() {
						public Object execute() {
							return new Double(
									(sumNumAgeDeaths + sumNumStarveDeaths)
											/ base);
						}
					});

			finalRecorder.addObjectDataSource(
					"starve", new DataSource() {
						public Object execute() {
							return new Double(sumNumStarveDeaths / base);
						}
					});

			finalRecorder.addObjectDataSource("old",
					new DataSource() {
						public Object execute() {
							return new Double(sumNumAgeDeaths / base);
						}
					});

			finalRecorder.addObjectDataSource("tdons",
					new DataSource() {
						public Object execute() {
							return new Double(sumNumDonationPackages / base);
						}
					});

			finalRecorder.addObjectDataSource("avGath",
					new DataSource() {
						public Object execute() {
							return new Double(sumAvGathered / base);
						}
					});

			finalRecorder.addObjectDataSource("avExcess",
					new DataSource() {
						public Object execute() {
							return new Double(sumAvExcess / base);
						}
					});

			finalRecorder.addObjectDataSource("avDonAmount",
					new DataSource() {
						public Object execute() {
							return new Double(sumAvDonationAmount / base);
						}
					});

			finalRecorder.addObjectDataSource("tres",
					new DataSource() {
						public Object execute() {
							return new Double(sumTotalResources / base);
						}
					});

			finalRecorder.addObjectDataSource("prop time pop low",
					new DataSource() {
						public Object execute() {
							return new Double((double) countPopLow / base);
						}
					});
			finalRecorder.addObjectDataSource("prop time not born of all types",
					new DataSource() {
						public Object execute() {
							return new Double((double) countNotBornOfAllTypes / base);
						}
					});
		}
	}

	private void buildDisplay() {

		final double maxPop = (double) realNumFood
				/ ((double) numFoodTypes * foodUsageRate);
		if (display) {
			// final double maxPop = (double)numFood / ((double)numFoodTypes *
			// foodUsageRate);
			graph.addSequence("Size", new Sequence() {
				public double getSValue() {
					return (double) agentList.size() / maxPop;
				}
			}, OpenGraph.PLUS_SIGN);

			graph.addSequence("Av. tol.", new Sequence() {
				public double getSValue() {
					double tol = 0;
					for (int i = 0; i < agentList.size(); i++)
						tol += agentList.get(i).getTolerance();
					return tol / (agentList.size() * maxTol);
				}
			}, OpenGraph.PLUS_SIGN);

			graph.addSequence("Don. rate", new Sequence() {
				public double getSValue() {
					return (double) numDonationPackages
							/ (numPairings * agentList.size());
				}
			}, OpenGraph.PLUS_SIGN);

			graph.setAxisTitles("Time", "Proportion of Maximum");
			graph.setXRange(0, 100);
			graph.setYRange(0.05, 0.95);
			graph.setYIncrement(0.05);
			graph.setSize(400, 250);
		}

		if (histogram) {

			BinDataSource source = new BinDataSource() {
				public double getBinValue(Object o) {
					TaggedAgent agent = (TaggedAgent) o;
					return agent.getTag();
				}
			};

			histogramGraph.createHistogramItem("Number", agentList, source);
			histogramGraph.setBarWidth(0.01);
			histogramGraph.setStatsVisible(false);
			histogramGraph.setXIncrement(0);
			histogramGraph.setYRange(0, maxPop);
		}

		if (subPops) {
			for (int i = 0; i < numFoodTypes; i++) {
				final int ii = i;
				subPopsGraph.addSequence("Skill" + ii, new Sequence() {
					public double getSValue() {
						return (double) numAgentType[ii];
					}
				}, colorMap.getColor(ii), OpenGraph.PLUS_SIGN);
			}

			subPopsGraph.setAxisTitles("Time", "Size");
			subPopsGraph.setXRange(0, 1);
			subPopsGraph.setYRange(0, 100);
			subPopsGraph.setYIncrement(0.05);
			subPopsGraph.setSize(400, 250);

		}

		if (profile) {
			profilePlot.setAxisTitles("Tag values", "Age");
			profilePlot.setXRange(0.01, 0.99);
			profilePlot.setYRange(0.75, maxTagAge - 1);
			profilePlot.setSize(400, 600);
		}

	}

	// buildSchedule builds the schedule that changes the simulation's
	// state.  Under this scheme, a simulation is a state machine
	// where all transitions between states are the result of actions
	// initiated by a schedule.
	private void buildSchedule() {

		// this is a static schedule (no need to add or replace actions)
		// so we can create an inner class that extends from basic
		// action. The execute method of the inner class will execute
		// all the methods that we wish to schedule on the agents and
		// the environment.

		schedule.scheduleActionBeginning(0, this, "step");

		// On a pause in the simulation run, call the writeToFile method
		// on the recorder object. (Writes the data collected by the
		// recorder to a file. 
		if (write)
			schedule.scheduleActionAtPause(recorder, "writeToFile");
		if (display)
			schedule.scheduleActionAtPause(graph, "writeToFile");

		// When the simulation run ends, call the writeToFile method
		// on the recorder object. (Writes the data collected by the recorder
		// to a file.
		if (write)
			schedule.scheduleActionAtEnd(recorder, "writeToFile");
		if (writeFinal)
			schedule.scheduleActionAtEnd(finalRecorder, "writeToFile");
		if (display)
			schedule.scheduleActionAtEnd(graph, "writeToFile");

		schedule.scheduleActionAt(maxTime, this, "stop");
	}

	public void step() {
		numDonationPackages = 0;
		numCloneDonationPackages = 0;
		numBirths = 0;
		numAgeDeaths = 0;
		numStarveDeaths = 0;
		excess = 0;
		totalDonationAmount = 0;
		// Add the new individuals for this generation
		if (withNew)
			for (int i = 0; i < maxNumNew; i++)
				addNewAgent(-1);
		// Now kill the agents that should die,
		// and for those that should, give birth
		for (int i = 0; i < agentList.size(); i++) {
			TaggedAgent agent = agentList.get(i);
			// agent.age();

			if (withAsexualReproduction
					&& agent
							.shouldReproduce(foodOfTypeNecessaryForReproduction)) {
				int dummy = 1;
				if (this.getTickCount() > 5) {
					dummy = 0;
				}
				agentBirth(agent.giveBirth(probMutVal, sdMut, initialFood,
						withValMut, withReproductionNoise, reproductionNoiseSd,
						maxStartAge));
				if (!withTagVariety) {
					agent.setTolerance(1);
				}
				if (withSkillMut && Random.uniform.nextDouble() < probMutVal) {
					agent.setSkill(Random.uniform.nextIntFromTo(0,
							numFoodTypes - 1));
				}
				numBirths++;
			}
		}

		// call the birthAgents methods on this model
		birthAgents();
		// call the shuffleAgents method on this model
		shuffleAgents();

		int numAgents = agentList.size();

		// put fresh food in the world
		genFood();

		// Distribute each food type evenly between agents with that skill
		gathered = 0;
		for (int i = 0; i < numAgents; i++) {
			TaggedAgent agent = agentList.get(i);
			int type = agent.getSkill();
			agent.harvestFood(foodVals[type] / numAgentType[type]);
			gathered += foodVals[type] / numAgentType[type];
		}

		// Now for each agent, choose a random set of potential recipients
		for (int i = 0; i < numAgents; i++) {
			TaggedAgent agent = agentList.get(i);
			ArrayList<Integer> pairings = new ArrayList<Integer>(numPairings);
			for (int j = 0; j < numPairings && j < agentList.size() - 1; j++) {
				Integer next = Integer.valueOf(Random.uniform.nextIntFromTo(0,
						numAgents - 1));
				while (next.intValue() == i || pairings.contains(next))
					next = Integer.valueOf(Random.uniform.nextIntFromTo(0,
							numAgents - 1));
				pairings.add(next);
			}
			// And then for each of these, if they are similar enough
			// and this original agent has surplus, donate food.
			int j = 0;
			while (j < pairings.size()) {
				TaggedAgent potential = agentList.get(Integer.valueOf(pairings
						.get(j)));
				if (agent.shouldDonate(potential.getTag()))
					j++;
				else
					pairings.remove(j);
			}
			double[] excesses = new double[numFoodTypes];
			boolean hasExcess = false;
			for (int type = 0; type < numFoodTypes; type++) {
				excesses[type] = agent.excessFood(type,
						foodOfTypeAboveWhichIsExtra);
				if (excesses[type] > 0)
					hasExcess = true;
				excess += excesses[type];
			}

			if (hasExcess) {
				int n = pairings.size();
				numDonationPackages += n;
				for (j = 0; j < n; j++) {
					TaggedAgent potential = agentList.get(Integer
							.valueOf(pairings.get(j)));
					if (potential.getTag() == agent.getTag())
						numCloneDonationPackages++;
					for (int type = 0; type < numFoodTypes; type++) {
						if (excesses[type] > 0) {
                            numDonationEvents += n;
							double donAmount = excesses[type] / n;
							totalDonationAmount += donAmount;
							agent.donateFood(type, donAmount * donationCost);
							potential.receiveDonation(type, donAmount
									* donationBenefit);
						}
					}
				}
			}
		}

		for (TaggedAgent agent : agentList) {
			agent.age();
			agent.consumeFood(foodUsageRate);
			if (withDeath) {
				if (agent.reachedAge(maxTagAge)) {
					agentDeath(agent);
					numAgeDeaths++;
				} else if (agent.isStarved(foodOfTypeBelowWhichTagDies)) {
					agentDeath(agent);
					numStarveDeaths++;
				}
			}
		}

		// displays won't be in synch with what it displays
		// at end or at pause.
		if (display)
			graph.step();

		if (histogram)
			histogramGraph.step();

		if (subPops)
			subPopsGraph.step();

		if (profile) {
			ArrayList<ArrayList<TaggedAgent>> agedAgentList = new ArrayList<ArrayList<TaggedAgent>>(
					maxTagAge + 1);
			for (int age = 0; age < maxTagAge + 1; age++) {
				agedAgentList.add(new ArrayList<TaggedAgent>());
			}
			for (TaggedAgent thisAgent : agentList) {
				int thisAge = thisAgent.getCurrentAge();
				ArrayList<TaggedAgent> thisList = agedAgentList
						.get(thisAge - 1);
				thisList.add(thisAgent);
			}
			for (int i = 0; i < lastMaxDataSet; i++)
				profilePlot.clear(i);
			int dataSet = 0;
			for (int age = 0; age < maxTagAge + 1; age++) {
				ArrayList<TaggedAgent> agentsThisAge = agedAgentList.get(age);
				int size = agentsThisAge.size();
				int numSoFar = 0;
				Iterator<TaggedAgent> theseAgents = agentsThisAge.iterator();
				while (theseAgents.hasNext()) {
					TaggedAgent thisAgent = theseAgents.next();
					double tag = thisAgent.getTag();
					double tol = thisAgent.getTolerance();
					int skill = thisAgent.getSkill();
					double yPos = 0.001
							+ (double) age
							+ ((double) numSoFar / ((double) size + (double) 2));
					profilePlot.clear(dataSet);
					profilePlot.setConnected(true, dataSet);
					profilePlot.plotPoint(this.bound(tag - tol, 0, 1), yPos,
							dataSet);
					profilePlot.plotPoint(this.bound(tag + tol, 0, 1), yPos,
							dataSet);
					profilePlot.plotPoint(tag, yPos, dataSet);
					profilePlot.addLegend(dataSet, "",
							colorMap.getColor(skill), OpenGraph.PLUS_SIGN);
					dataSet++;
					numSoFar++;
				}

			}
			lastMaxDataSet = dataSet;
			profilePlot.step();

		}

		if (write)
			recorder.record();

		if (writeFinal && (int) this.getTickCount() >= finalStartClick) {
			//  update the sums for final statistics:

			sumDonationRate += (double) numDonationPackages
					/ ((double) numPairings * (double) agentList.size());
			sumNumDonationPackages += numDonationPackages;
			sumCloneDonationRate += (double) numCloneDonationPackages
					/ ((double) numPairings * (double) agentList.size());
			sumNumBirths += numBirths;
			sumNumAgeDeaths += numAgeDeaths;
			sumNumStarveDeaths += numStarveDeaths;
			sumNumAgents += agentList.size();
			totalNumPairings = agentList.size() * numPairings;
			double ttol = 0;
			for (int i = 0; i < agentList.size(); i++)
				ttol += agentList.get(i).getTolerance();
			sumAvTolerance += ttol / (double) agentList.size();
			sumAvGathered += (double) gathered / (double) agentList.size();
			sumAvExcess += excess / (numFoodTypes * agentList.size());
			if (numDonationPackages != 0)
                sumAvDonationAmount += (double) totalDonationAmount
                        / (double) numDonationEvents;
			double tresources = 0;
			for (int i = 0; i < agentList.size(); i++)
				tresources += agentList.get(i).getFood();
			final double maxPop = (double) realNumFood
					/ ((double) numFoodTypes * foodUsageRate);
			sumTotalResources += tresources;
			if ((this.agentList.size() / maxPop) < (double) 1 / 5)
				countPopLow++;
			
			ArrayList<Boolean> bornOfType = new ArrayList<Boolean>(numFoodTypes);
			for (int i = 0; i < numFoodTypes; i++) bornOfType.add(false);
			for (TaggedAgent ag : agentList) {
				if (ag.isBorn()) {
					bornOfType.set(ag.getSkill(), true);
				}
			}
			bornOfAllTypes = true;
			for (boolean bornOfThis : bornOfType)
				if (bornOfThis == false) bornOfAllTypes = false;
			if (! bornOfAllTypes) countNotBornOfAllTypes++;
			
			if ((int) this.getTickCount() == maxTime)
				// if ((int)this.getTickCount() >= finalStartClick)
				finalRecorder.record();
		}

		reapAgents();

		if (agentList.size() == 0) {stop();}
	}

	private double[] foodVals;

	private int mod(double num, double base) {
		return 1;
	}

	// Generate fresh food, randomly distributed between types
	public void genFood() {
		for (int i = 0; i < foodVals.length; i++)
			foodVals[i] = 0;
		for (int i = 0; i < realNumFood; i++) {
			int box = 0;
			if (withRandomFoodScatter) {
				box = Random.uniform.nextIntFromTo(0, foodVals.length - 1);
			} else {
				box = i % numFoodTypes;
			}
			foodVals[box]++;
		}
	}

	// Randomize the order of the object (the TaggedAgents) in the agentList
	public void shuffleAgents() {
		SimUtilities.shuffle(agentList);
	}

	// Add a new agent.
	public void addNewAgent(int skill) {
		TaggedAgent agent = new TaggedAgent(numFoodTypes, initialFood,
				maxReservoir, maxTol, maxStartAge);
		agent.setBorn(false);
		agentBirth(agent);
		if (skill >= 0) {
			agent.setSkill(skill);
		}
		if (!withTagVariety) {
			agent.setTolerance(1);
		}
	}

	public void agentBirth(TaggedAgent agent) {
		numAgentType[agent.getSkill()]++;
		if (this.getTickCount() == 0) {
			agentList.add(agent);
		} else {
			birthList.add(agent);
		}
	}

	public void birthAgents() {
		agentList.addAll(birthList);
		birthList.clear();
	}

	// When an agent "dies" it is added to the reaperQueue
	public void agentDeath(TaggedAgent agent) {
		numAgentType[agent.getSkill()]--;
		reaperQueue.add(agent);
	}

	public void reapAgents() {
		ListIterator li = reaperQueue.listIterator();

		while (li.hasNext()) {
			TaggedAgent agent = (TaggedAgent) li.next();
			agentList.remove(agent);
		}

		reaperQueue.clear();
	}

	public int getInitialPopSize() {
		return initialPopSize;
	}

	public void setInitialPopSize(int num) {
		initialPopSize = num;
	}

	public int getMaxTime() {
		return maxTime;
	}

	public void setMaxTime(int num) {
		this.maxTime = num;
	}

	public int getNumPairings() {
		return numPairings;
	}

	public void setNumPairings(int num) {
		this.numPairings = num;
	}

	public int getMaxNumNew() {
		return maxNumNew;
	}

	public void setMaxNumNew(int num) {
		this.maxNumNew = num;
	}

	public int getNumFood() {
		return numFood;
	}

	public void setNumFood(int num) {
		this.numFood = num;
	}

	public int getNumFoodTypes() {
		return numFoodTypes;
	}

	public void setNumFoodTypes(int num) {
		this.numFoodTypes = num;
	}

	public int getMaxTagAge() {
		return maxTagAge;
	}

	public void setMaxTagAge(int num) {
		this.maxTagAge = num;
	}

	public int getMaxStartAge() {
		return maxStartAge;
	}

	public void setMaxStartAge(int num) {
		this.maxStartAge = num;
	}

	public double getProbMutVal() {
		return probMutVal;
	}

	public void setProbMutVal(double num) {
		this.probMutVal = num;
	}

	public double getSdMut() {
		return sdMut;
	}

	public void setSdMut(double num) {
		this.sdMut = num;
	}

	public double getDonationCost() {
		return donationCost;
	}

	public void setDonationCost(double num) {
		this.donationCost = num;
	}

	public double getDonationBenefit() {
		return donationBenefit;
	}

	public void setDonationBenefit(double num) {
		this.donationBenefit = num;
	}

	public double getInitialFood() {
		return initialFood;
	}

	public void setInitialFood(double num) {
		this.initialFood = num;
	}

	public double getFoodOfTypeNecessaryForReproduction() {
		return foodOfTypeNecessaryForReproduction;
	}

	public void setFoodOfTypeNecessaryForReproduction(double num) {
		this.foodOfTypeNecessaryForReproduction = num;
	}

	public double getFoodOfTypeBelowWhichTagDies() {
		return foodOfTypeBelowWhichTagDies;
	}

	public void setFoodOfTypeBelowWhichTagDies(double num) {
		this.foodOfTypeBelowWhichTagDies = num;
	}

	public double getFoodOfTypeAboveWhichIsExtra() {
		return foodOfTypeAboveWhichIsExtra;
	}

	public void setFoodOfTypeAboveWhichIsExtra(double num) {
		this.foodOfTypeAboveWhichIsExtra = num;
	}

	public double getFoodUsageRate() {
		return foodUsageRate;
	}

	public void setFoodUsageRate(double num) {
		this.foodUsageRate = num;
	}

	public double getMaxReservoir() {
		return maxReservoir;
	}

	public void setMaxReservoir(double num) {
		this.maxReservoir = num;
	}

	public double getMaxTol() {
		return maxTol;
	}

	public void setMaxTol(double num) {
		this.maxTol = num;
	}

	private double bound(double val, double min, double max) {
		if (val > max)
			return max;
		if (val < min)
			return min;
		return val;
	}

	public String[] getInitParam() {
		String[] params = { "InitialPopSize", "MaxTime", "NumPairings",
				"ProbMutVal", "SdMut", "MaxNumNew", "DonationCost",
				"DonationBenefit", "NumFood", "NumFoodTypes", "NumSkillBits",
				"NumNutritionBits", "MaxTagAge", "MaxStartAge", "InitialFood",
				"FoodOfTypeNecessaryForReproduction",
				"FoodOfTypeBelowWhichTagDies", "FoodOfTypeAboveWhichIsExtra",
				"FoodUsageRate", "MaxReservoir", "MaxTol", "Display", "Write",
				"WriteFinal", "FinalStartClick", "Profile", "Histogram",
				"SubPops", "withDeath", "withAsexualReproduction",
				"withReproductionNoise", "reproductionNoiseSd", "withNew",
				"withValMut", "withSkillMut", "withTagVariety",
				"withRandomFoodScatter", "withRandomInitialSkills" };
		return params;
	}

	public void begin() {

		buildModel();
		buildDisplay();
		buildSchedule();

		if (display)
			graph.display();

		if (histogram)
			histogramGraph.display();

		if (subPops)
			subPopsGraph.display();

		if (profile) {
			profilePlot.display();
			lastMaxDataSet = 0;
		}
	}

	public void setup() {
		schedule = null;

		if (graph != null)
			graph.dispose();
		graph = null;

		if (profilePlot != null)
			profilePlot.dispose();
		profilePlot = null;

		if (subPopsGraph != null)
			subPopsGraph.dispose();
		subPopsGraph = null;

		if (histogramGraph != null)
			histogramGraph.dispose();
		histogramGraph = null;

		colorMap.mapColor(0, ColorMap.green);
		colorMap.mapColor(1, ColorMap.red);
		colorMap.mapColor(2, ColorMap.blue);
		colorMap.mapColor(3, ColorMap.black);
        colorMap.mapColor(4, ColorMap.cyan);
        colorMap.mapColor(5, ColorMap.orange);
        colorMap.mapColor(6, ColorMap.yellow);
        colorMap.mapColor(7, ColorMap.pink);
        

		//if (bar != null)
		//bar.dispose();
		//bar = null;

		System.gc();

		// create a schedule with an interval of one.
		schedule = new Schedule(1);

		// Create a sequence graph called Agent Attributes and allow the
		// data plotted by this graph to be written to the file
		// ./graph_data.txt in comma delimited format.
		// if (display) 
		graph = new OpenSequenceGraph("Agent Attributes", this,
				"./output/food_graph_data.csv", OpenStats.CSV);

		histogramGraph = new Histogram("Tag distribution", 100, 0, 1, this);

		subPopsGraph = new OpenSequenceGraph("Sub Populations", this);

		// Create a plot for the tag profile
		profilePlot = new Plot("Profile of tag population", this);
		//		profilePlot.setConnected(false);

		this.registerMediaProducer("Plot", graph);
		this.registerMediaProducer("Profile", profilePlot);
		this.registerMediaProducer("SubPops", subPopsGraph);
		this.registerMediaProducer("Histogram", histogramGraph);
	}

	// a required method
	public Schedule getSchedule() {
		return schedule;
	}

	// a required method - displayed on the Controller toolbar.
	public String getName() {
		return "Symbiotic Sharing";
	}

	public static void main(String[] args) {
		SimInit init = new SimInit();
		FoodModel model = new FoodModel();
		init.loadModel(model, "", false);
	}
}

