/*$$
 * packages uchicago.src.*
 * 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.
 *
 * Nick Collier
 * nick@src.uchicago.edu
 *
 * packages cern.jet.random.*
 * Copyright (c) 1999 CERN - European Laboratory for Particle
 * Physics. Permission to use, copy, modify, distribute and sell this
 * software and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice appear in
 * supporting documentation. CERN makes no representations about the
 * suitability of this software for any purpose. It is provided "as is"
 * without expressed or implied warranty.
 *
 * Wolfgang Hoschek
 * wolfgang.hoschek@cern.ch
 *$$*/

package WealthModel;

import java.util.Vector;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.ListIterator;
import java.awt.Color;
import java.util.Collections;
import java.lang.Math;

import uchicago.src.reflector.ListPropertyDescriptor;
import uchicago.src.sim.space.*;
import uchicago.src.sim.engine.*;
import uchicago.src.sim.gui.*;
import uchicago.src.sim.util.SimUtilities;
import uchicago.src.sim.analysis.*;
import cern.jet.random.Uniform;
import cern.jet.random.Normal;
import uchicago.src.reflector.BooleanPropertyDescriptor;


// Simulation models should extend SimModelImpl which does some basic setup
// and allows a controller easy access to a simulations initial parameters.
public class WealthModel 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 agentList = new ArrayList();
  public static ArrayList agentList = new ArrayList();

  // A list of WealthAgents who have been "birthed" and should be introduced
  // to the simulation in the next turn
  private Vector birthList = new Vector();

  // The Wealth Space - the agent's environment consisting of variable
  // amounts of wealth arranged on a grid
  private WealthSpace space;

  // The 2 dimensional torusr on which the agents act.
  private static Object2DTorus agentGrid;

  // A queue that tracks dead agents.
  private Vector reaperQueue = new Vector();

  // A DataRecorder or ObjectDataRecorder allows data generated by
  // a simulation to be written to a file
  // Note to self:  lines beginning //** should be uncommented to record data

  private DataRecorder recorder;

  // default and modifiable parameters of the model
  
  private String behaviour = "Both";          // Behaviour of agents
  public static boolean Exchange = false;    // Do exchange model
  public static boolean Both = false;          // Combine exchange and investment economies
  public static boolean DeathTax = false;     // Implement "death tax"
  public static int MaxAge = 1200;            // Age for "death tax" (if turned on)
  private int MaxInitialWealth = 110;         // Maximum initial wealth for agents
  private int MinInitialWealth = 90;          // Minimum initial wealth for agents
  public static double MinWealth = 1.0;       // Minimum wealth
  public static boolean Moving = false;       // Do agents move (for exchange or "synch" models)
  private static int NumAgents = 1500;		  // Total number of agents (must be <= 2500)
  private static int NumLogLogBins = 30;	  // Number of bins for log:log plot
  private static int NumPlainBins = 100;	  // Number of bins for plain plot
  private static double PlainPlotXMax = 300.0;// Maximum X (wealth) plotted in plain plot (-1 = any)
  private static double PlainPlotYMax = -1.0; // Maximum Y (P_i) plotted in plain plot (-1 = any)
  private static boolean PlotLogLog = true;   // Whether to display log:log plot
  private static boolean PlotPlain = true;    // Whether to display plain plot
  public static boolean ROI = false;          // Whether to do investment economy (only)
  public static double ROIMean = 0.00;           // Mean return on investment
  public static double ROIStdDev = 0.05;          // Standard deviation of normal dist. of return
  public static boolean RandomWalk = false;   // Whether to do individual random walk
  public static boolean Smarter = false;	  // Are some agents smarter?
  public static int SmarterNum = 50;          // 1 in SmarterNum is smarter.
  public static boolean Sync = false;         // Whether to run the sync model instead
  public static double SyncLatency = 3.0;     // Latency in sync mode after "flash"
  public static int SyncMax = 150;            // Max value in sync model
  public static boolean SyncRegion = true;   // Do agents affect only nearbys or everybody (in sync)
  public static int TaxPCT = 10;              // Tax rate for death tax
  private static boolean ViewAvg = true;      // Whether to show average wealth of all agents
  private static boolean ViewStDAvg = true;   // Whether to show moving average of Std. Dev. of wealth of all agents
  private static boolean ViewStDev = true;    // Whether to show standard deviation of wealth of all agents
  private static boolean ViewStats = true;    // Whether to show average wealth of all agents
  private static boolean WriteStats = false;  // Whether to write stats out to a file on pause or stop
  private static boolean ViewPowerLawExp = true;  // Whether to show power law exponent
  private static int RichMinPct = 90;		  // Rich above what per cent . . .
  private static boolean ViewEntropy = true;  // Whether to show entropy
  private static int entropyBin = 5;          // size of bins for calculating entropy
  private static boolean logEntropy = true;   // scale entropy bins by logarithm . . .
  private static int numLogEntropyBins = 50;  // number of log-entropy bins . . .
  
  private boolean replace = true;
  private static int MAvgRange = 1000;
  private static double[] StDAvg = new double[MAvgRange];
  private static int stepModMAvgRange = 0;
  private static boolean testing = false;
  
  private static int step = 0;

  // The surface on which the agents and their environment are displayed
  private DisplaySurface dsurf;

  // A graph that plots sequence
  private OpenSequenceGraph statsGraph;
  
  // A graph for the "power law" exponent;
  private OpenSequenceGraph powerlawGraph;
  
  // A graph for the entropy;
  private OpenSequenceGraph entropyGraph;

  // A histogram for wealth
  private OpenHistogram bar;
  
  // A graph for log-log plot
  private Plot logLogPlot;
  
  // A graph for plain plot
  private Plot plainPlot;
  
  // A histogram for ages
//  private OpenHistogram barAge;


  // These inner classes are used by all the data collection methods - i.e.
  // DataRecorder, SequenceGraph and the Histogram.


  // All simulations should have a no argument constructor - this is empty and
  // thus not strictly necessary
  public WealthModel() {
	  
	  String[] props1 = new String[] {"Both", "Exchange", "ROI", "RandomWalk", "Sync"};
	  ListPropertyDescriptor pd1 = new ListPropertyDescriptor("Behaviour", props1);
	  descriptors.put("Behaviour", pd1);
	  
	  Controller.ALPHA_ORDER = false;

  }

  // The typical way to code a simulation is to divide up the creation of the
  // simulation into three methods - buildModel(), buildDisplay(),
  // buildSchedule(). This division is not strictly necessary, but it does
  // make the creation conceptually clearer.

  // The buildModel() method is responsible for creating those parts of the
  // simulation that represent what is being modeled. Consequently, the agents
  // and their environment are typically created here together with any
  // optional data collection objects. Of course, this method may call other
  // methods to help build the model.
  private void buildModel() {

    // creates the wealth space using the values in wealthspace.pgm as
    // the amount of wealth (see WealthSpace.java for more).
    
//    space = new WealthSpace("/WealthModel/wealthspace.pgm");
    space = new WealthSpace("/wealthspace.pgm");
    agentGrid = new Object2DTorus(space.getXSize(), space.getYSize());
    
    // createNormal(ROIMean, ROIStdDev);

    for (int i = 0; i < NumAgents; i++) {
      addNewAgent();
    }

    // create a new DataRecorder and write the data to ./wealth_data.txt
    
	if (WriteStats) {
      recorder = new DataRecorder("./wealth_data.txt", this);
	}

    // first we make an inner class that implements the DataSource interface
    
//    class AgentCountClass implements DataSource {
//      public Object execute() {
//        int i = agentList.size();
//        return new Integer(i);
//      }
//    };

    class AgentWealthClass implements DataSource {
      public Object execute() {
        String s = " ";
        for (int i = 0; i < agentList.size(); i++) {
            WealthAgent agent = (WealthAgent)agentList.get(i);
            if (i < agentList.size() - 1)
              s = s + agent.getWealth() + ",";
            else
              s = s + agent.getWealth();
        }
        return s;
      }
    };

//    recorder.addObjectDataSource("AgentCount", new AgentCountClass());
	if (WriteStats) {
      recorder.addObjectDataSource("AgentWealth", new AgentWealthClass());
	}

    // we could also have done the above with an anonymous inner class
    // recorder.addObjectDataSource("Agent Count", new DataSource() {
    //  public Object execute() {
    //    return new Integer(agentList.size());
    //  }
    // });
  }

  // buildDisplay() builds those parts of the simulation that have to do with
  // displaying the simulation to a user.
  private void buildDisplay() {

    // Create a display to display the agentGrid on the screen.
    // This will display any Drawable contained within the Object2DTorus.
    // In a non-batch simulation the agents in a simulation will typically
    // implement the Drawable interface.
    Object2DDisplay agentDisplay = new Object2DDisplay(agentGrid);

    // Rather than have the agentDisplay iterating over each object in the
    // Object2DTorus, give the display a list of Drawables to display.
    agentDisplay.setObjectList(agentList);

    // Maps 4 shades of yellow to integers 1 - 4, and white to 0.
    ColorMap map = new ColorMap();
    map.mapColor(4, new Color(255, 255, 0));
    map.mapColor(3, new Color(255, 255, 255 / 3));
    map.mapColor(2, new Color(255, 255, 255 / 2));
    map.mapColor(1, new Color(255, 255, (int)(255 / 1.2)));
    map.mapColor(0, Color.white);

    // WealthSpace.getCurrentWealth returns an Object2DTorus containing an
    // Integer of value (0 - 4) at each x, y coordinate. Using the map created
    // above this Value2DDisplay will display will display each of these
    // Integers as a shade of yellow (or white, if the Integer == 0).
    Value2DDisplay wealthDisplay = new Value2DDisplay(space.getCurrentWealth(),
                                                map);

    // DisplaySurface does the actual drawing of the various displays on the
    // user's screen. Displays are displayed in the order they are added.
    // Our dsurf object is created in the setup() method

    dsurf.addDisplayableProbeable(wealthDisplay, "Wealth Space");
    dsurf.addDisplayableProbeable(agentDisplay, "Agents");


    // Histograms get their data from histogramItems. This creates an item
    // called wealth that gets its data by calling getWealth on each agent
    // in the agent list.
    bar.createHistogramItem("Wealth", agentList, new BinDataSource() {
      public double getBinValue(Object o) {
        WealthAgent agent = (WealthAgent)o;
        return agent.getWealth();
      }
    },7 ,0);

    bar.setYRange(0, 1.0);
	
	if (ViewPowerLawExp) {
		powerlawGraph = new OpenSequenceGraph("Power Law Exponent", this);
		powerlawGraph.setXRange(0,200);
		powerlawGraph.setYRange(1.5,4.0);
		powerlawGraph.setAxisTitles("time", "exponent - alpha");
		powerlawGraph.addSequence("alpha", new Sequence() {
			public double getSValue() {
				int nMin, n;
				double xMin, alpha = 0.0;
				
				double[] agentWealths = new double[agentList.size()];
				
				for (int i = 0; i < agentList.size(); i++) {
					WealthAgent a = (WealthAgent) agentList.get(i);
					agentWealths[i] = a.getWealth();
				}
				
				java.util.Arrays.sort(agentWealths);
				
				nMin = (int) Math.round(agentList.size() * RichMinPct / 100.0);
				n = agentList.size() - nMin + 1;
				xMin = agentWealths[nMin];
				
				for (int i = nMin; i < agentList.size(); i++) {
					alpha = alpha + Math.log(agentWealths[i] / xMin);
				}
				
				alpha = 1 + n * (1 / alpha);
				
				return alpha;
			}
        });
	}
	if (ViewEntropy) {
		entropyGraph = new OpenSequenceGraph("Entropy", this);
		entropyGraph.setXRange(0,200);
		entropyGraph.setYRange(1.5,4.0);
		entropyGraph.setAxisTitles("time", "entropy");
		entropyGraph.addSequence("entropy", new Sequence() {
			public double getSValue() {
				int nBins;
				double wealthMax = 0.0, entropy = 0.0, binStep = 0.0;
				
				if (logEntropy) {
					for (int i = 0; i < agentList.size(); i++) {
						WealthAgent a = (WealthAgent) agentList.get(i);
						if ( a.getWealth() > wealthMax )
							wealthMax = a.getWealth();
						}
								 
						nBins = numLogEntropyBins;
						binStep = Math.log(wealthMax) / nBins;
								 
						int[] agentCounts = new int[nBins];
								 
						for (int i = 0; i < nBins; i++)
						agentCounts[i] = 0;
								 
						for (int i = 0; i < agentList.size(); i++) {
							WealthAgent a = (WealthAgent) agentList.get(i);
							if (a.getWealth() <= 1.0)
								 agentCounts[0]++;
							else
								 agentCounts[(int) Math.floor(Math.log(a.getWealth())/(binStep + 1.0))]++;
						}
								 
						for (int i = 0; i < nBins; i++)
							if ( agentCounts[i] > 0 )
								entropy = entropy + ((double) agentCounts[i] / agentList.size())
								 * Math.log((double) agentList.size() / agentCounts[i]);
								 
						return entropy;
				}
				else {
					for (int i = 0; i < agentList.size(); i++) {
						WealthAgent a = (WealthAgent) agentList.get(i);
						if ( a.getWealth() > wealthMax )
							wealthMax = a.getWealth();
					}
					
					nBins = (int) Math.floor(wealthMax/entropyBin) + 1;
					
					int[] agentCounts = new int[nBins];
					
					for (int i = 0; i < nBins; i++)
						agentCounts[i] = 0;
					
					for (int i = 0; i < agentList.size(); i++) {
						WealthAgent a = (WealthAgent) agentList.get(i);
						agentCounts[(int) Math.floor(a.getWealth()/entropyBin)]++;
					}
					
					for (int i = 0; i < nBins; i++)
						if ( agentCounts[i] > 0 )
							entropy = entropy + ((double) agentCounts[i] / agentList.size())
								* Math.log((double) agentList.size() / agentCounts[i]);
					
					return entropy;
				}
			}
        });
		if (testing) {
			entropyGraph.addSequence("max wealth", new Sequence() {
				public double getSValue() {
					double wealthMax = 0.0;
					
					
					for (int i = 0; i < agentList.size(); i++) {
						WealthAgent a = (WealthAgent) agentList.get(i);
						if ( a.getWealth() > wealthMax )
							wealthMax = a.getWealth();
					}
					
					return wealthMax;
				}
			});
		}
	}
	
	if (ViewStats || ViewAvg || ViewStDev || ViewStDAvg ) {
	  statsGraph = new OpenSequenceGraph("Agent Stats.", this);
      statsGraph.setXRange(0, 200);
      statsGraph.setYRange(0, 200);
      statsGraph.setAxisTitles("time", "agent attributes");
	  
	  

	  if (ViewAvg) {
        statsGraph.addSequence("Avg. Wealth", new Sequence() {
          public double getSValue() {
            double totalWealth = 0;
            for (int i = 0; i < agentList.size(); i++) {
              WealthAgent a = (WealthAgent)agentList.get(i);
              totalWealth += a.getWealth();
            }
    
          return totalWealth / agentList.size();
          }
        });
	  }
	  
	  if (ViewStDev) {
	    statsGraph.addSequence("Std. Dev. - Wealth", new Sequence() {
	  	  public double getSValue() {
			  double totalWealth = 0.0;
			  double avgWealth = 0.0;
			  double calcSD = 0.0;
			  
			  for (int i = 0; i < agentList.size(); i++) {
				  WealthAgent a = (WealthAgent)agentList.get(i);
				  totalWealth += a.getWealth();
			  }
			  
			  avgWealth = totalWealth / agentList.size();
			  
			  for (int i = 0; i < agentList.size(); i++) {
				  WealthAgent a = (WealthAgent)agentList.get(i);
				  calcSD += (a.getWealth() - avgWealth) * (a.getWealth() - avgWealth);
			  }
			  
			  calcSD = calcSD / agentList.size();
			  
			  calcSD = Math.sqrt(calcSD);
			  
			  return calcSD;
		  }
        });
	  }
	  
	  if (ViewStDAvg) {
		  statsGraph.addSequence("Std. Dev. - Mvg. Avg.", new Sequence() {
			  public double getSValue() {
				  double totalWealth = 0.0;
				  double avgWealth = 0.0;
				  double calcSD = 0.0;
				  
				  for (int i = 0; i < agentList.size(); i++) {
					  WealthAgent a = (WealthAgent)agentList.get(i);
					  totalWealth += a.getWealth();
				  }
				  
				  avgWealth = totalWealth / agentList.size();
				  
				  for (int i = 0; i < agentList.size(); i++) {
					  WealthAgent a = (WealthAgent)agentList.get(i);
					  calcSD += (a.getWealth() - avgWealth) * (a.getWealth() - avgWealth);
				  }
				  
				  calcSD = calcSD / agentList.size();
				  
				  calcSD = Math.sqrt(calcSD);
				  
				  stepModMAvgRange = (stepModMAvgRange + 1) % MAvgRange;
				  
				  StDAvg[stepModMAvgRange] = calcSD;
				  
				  calcSD = 0.0;
				  
				  for (int i = 0; i < MAvgRange; i++)
					  calcSD = calcSD + StDAvg[i];
				  
				  calcSD = calcSD / MAvgRange;
				  
				  return calcSD;
			  }
		  });
	  }
    
      if (DeathTax) {
    
          statsGraph.addSequence("Avg. Age", new Sequence() {
            public double getSValue() {
              double totalAge = 0;
              for (int i = 0; i < agentList.size(); i++) {
                WealthAgent a = (WealthAgent)agentList.get(i);
                totalAge += a.getAge();
              }
    
            return totalAge / agentList.size();
            }
          });
      }
    }
	if (PlotLogLog) {
		logLogPlot = new Plot("Log(P_i) : Log(W_i)  Plot");
	}
   
   if (PlotPlain) {
	   plainPlot = new Plot("Plain  Plot");
	   if (PlainPlotXMax > 0.0) {
		   plainPlot.setXRange(0.0, PlainPlotXMax);
	   }
	   if (PlainPlotYMax > 0.0) {
		   plainPlot.setYRange(0.0, PlainPlotYMax);
	   }

	   plainPlot.addLegend(1, "> 250", java.awt.Color.magenta, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(2, "> 200", java.awt.Color.pink, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(3, "> 150", java.awt.Color.red, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(4, "> 125", java.awt.Color.orange, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(5, "> 100", java.awt.Color.yellow, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(6, "> 75", java.awt.Color.green, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(7, "> 50", java.awt.Color.cyan, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(8, "> 40", java.awt.Color.blue, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(9, "> 30", java.awt.Color.lightGray, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(10, "> 20", java.awt.Color.gray, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(11, "> 10", java.awt.Color.darkGray, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   plainPlot.addLegend(12, "<= 10", java.awt.Color.black, uchicago.src.sim.analysis.plot.OpenGraph.FILLED_CIRCLE);
	   
   }
		   
	   
    
//    if (DeathTax) {
//      barAge.createHistogramItem("Age", agentList, new BinDataSource() {
//        public double getBinValue(Object o) {
//          WealthAgent agent = (WealthAgent)o;
//          return agent.getAge();
//        }
//      },4 ,0);
//
//      barAge.setYRange(0, 1.0);
//    }


    //bar.createHistogramItem("Wealth", agentList, "getWealth");

    // This causes the display surface to update the display whenever the
    // simulation run is paused or ended. The DisplaySurface now listens
    // for SimEvents produced by the WealthModel
   
   if ( WealthModel.Both ) {
	   setROI(false);
   }
   
   addSimEventListener(dsurf);
  }

  // buildSchedule builds the schedule that changes the simulations 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.

    // this inner class could also be done anonymously with the
    // same result, but doing it this way is clearer for those
    // with less experience with Java.

    // we could also move everything in the execute method of the WealthRunner
    // into a method of this WealthModel class, a step() method for example,
    // and then schedule that as follows:
    //
    // schedule.scheduleActionBeginning(0, this, "step");

    class WealthRunner extends BasicAction {
      public void execute() {
		  
		  step = step + 1;
		  
          // call the birthAgents methods on this model
          birthAgents();
          // call the shuffleAgents method on this model
          shuffleAgents();


          // call the step method on each WealthAgent in the agentList
          for (int i = 0; i < agentList.size(); i++) {
            WealthAgent agent = (WealthAgent)agentList.get(i);
            agent.step();
          }
          space.updateWealth();

          // should call update display after the things that it displays
          // have changed their state. Otherwise, displays won't be in
          // synch with what it displays at end or at pause.
          dsurf.updateDisplay();
          bar.step();
		  
		  if (ViewPowerLawExp && (step > 300) && ((step % 10) == 0) )
			  powerlawGraph.step();
		  
		  if (ViewEntropy && ((step % 10) == 0) )
			  entropyGraph.step();
		  
          if (ViewStats || ViewAvg || ViewStDev || ViewStDAvg)
            statsGraph.step();
          
//          if (DeathTax)
//            barAge.step();

//          recorder.record();
          reapAgents();
        }
	};



    // the schedule has been created in setup()
    schedule.scheduleActionBeginning(0, new WealthRunner());

    // 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.
    //   schedule.scheduleActionAtPause(recorder, "writeToFile");

    if (WriteStats) {
      schedule.scheduleActionAtPause(recorder, "record");
      schedule.scheduleActionAtPause(recorder, "writeToFile");

    // When the simulation run ends, call the writeToFile method
    // on the recorder object. (Writes the data collected by the recorder
    // to a file.
    
      schedule.scheduleActionAtEnd(recorder, "record");
      schedule.scheduleActionAtEnd(recorder, "writeToFile");
    }

	
    class LogLogPlot extends BasicAction {
      public void execute() {
		  int i, j;
		  double logWealth;
		  
		  WealthAgent agent = (WealthAgent)agentList.get(0);
		  double min = agent.wealth;
		  double max = agent.wealth;
		  for (i = 1; i < agentList.size(); i++) {
			  agent = (WealthAgent)agentList.get(i);
			  if (agent.wealth < min)
				  min = agent.wealth;
			  if (agent.wealth > max)
				  max = agent.wealth;
		  }
		  double logMin = Math.log(min)/Math.log(10.0);
		  double logMax = Math.log(max)/Math.log(10.0);
		  double binStep = (logMax - logMin) / NumLogLogBins;
		  double[] plotBins = new double[NumLogLogBins];
		  for (i = 0; i < NumLogLogBins; i++)
			  plotBins[i] = 0.0;
		  for (i = 0; i < agentList.size(); i++) {
			  agent = (WealthAgent)agentList.get(i);
			  logWealth = Math.log(agent.wealth)/Math.log(10.0);
			  j = 0;
			  while ( logWealth > (logMin + (j + 1) * binStep))
				  j++;
			  if (j < NumLogLogBins)
				  plotBins[j] += 1.0;
			  else
				  plotBins[NumLogLogBins -1] += 1;
		  }
		  logLogPlot.clear(1);
		  logLogPlot.clear(2);
		  double cum = 0.0;
		  for (i = 0; i < NumLogLogBins; i++)
			  if (plotBins[i] > 0.0) {
				  
				  logLogPlot.plotPoint(logMin + (i + 0.5) * binStep,
									   Math.log(1 - cum)/Math.log(10.0), 1);
				  logLogPlot.plotPoint(logMin + (i + 0.5) * binStep,
								   Math.log(plotBins[i] / agentList.size())/Math.log(10.0), 2);
				  cum += plotBins[i] / agentList.size();
			  }
		  logLogPlot.fillPlot();
		  logLogPlot.updateGraph();

	  }
    };
	  
    if (PlotLogLog) {
	  schedule.scheduleActionAtInterval(100, new LogLogPlot(),
											Schedule.LAST);
    };
	
	class PlainPlot extends BasicAction {
		public void execute() {
			int i, j;
			double min = MinWealth;
			double max;
			
			WealthAgent agent = (WealthAgent)agentList.get(0);

			if (PlainPlotXMax > 0.0){
				max = PlainPlotXMax;
			}
			else {
				max = agent.wealth;
				for (i = 1; i < agentList.size(); i++) {
					agent = (WealthAgent)agentList.get(i);
					if (agent.wealth > max)
						max = agent.wealth;
				}
			}
				
			
			
			double binStep = (max - min) / NumPlainBins;
			double[] plotBins = new double[NumPlainBins];
			for (i = 0; i < NumPlainBins; i++)
				plotBins[i] = 0.0;
			for (i = 0; i < agentList.size(); i++) {
				agent = (WealthAgent)agentList.get(i);
				j = 0;
				while ( (agent.wealth > (min + (j + 1) * binStep)) && (j < NumPlainBins))
					j++;
				if (j < NumPlainBins)
					plotBins[j] += 1.0;
//				else
//					plotBins[NumPlainBins -1] += 1;
			}
			for (i = 1; i <= 12; i++)
				plainPlot.clear(i);
			for (i = 0; i < NumPlainBins; i++) {
				plotBins[i] = plotBins[i] / agentList.size();
				if (plotBins[i] > 0.0) {
					if (PlainPlotYMax > 0.0) {
						if ((plotBins[i]) <= PlainPlotYMax) {
							if (min + (i + 0.5) * binStep > 250)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 1);
							else if (min + (i + 0.5) * binStep > 200)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 2);
							else if (min + (i + 0.5) * binStep > 150)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 3);
							else if (min + (i + 0.5) * binStep > 125)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 4);
							else if (min + (i + 0.5) * binStep > 100)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 5);
							else if (min + (i + 0.5) * binStep > 75)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 6);
							else if (min + (i + 0.5) * binStep > 50)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 7);
							else if (min + (i + 0.5) * binStep > 40)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 8);
							else if (min + (i + 0.5) * binStep > 30)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 9);
							else if (min + (i + 0.5) * binStep > 20)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 10);
							else if (min + (i + 0.5) * binStep > 10)
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 11);
							else
								plainPlot.plotPoint(min + (i + 0.5) * binStep,
													(plotBins[i]), 12);
						}
					}
					else {
						if (min + (i + 0.5) * binStep > 250)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 1);
						else if (min + (i + 0.5) * binStep > 200)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 2);
						else if (min + (i + 0.5) * binStep > 150)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 3);
						else if (min + (i + 0.5) * binStep > 125)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 4);
						else if (min + (i + 0.5) * binStep > 100)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 5);
						else if (min + (i + 0.5) * binStep > 75)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 6);
						else if (min + (i + 0.5) * binStep > 50)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 7);
						else if (min + (i + 0.5) * binStep > 40)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 8);
						else if (min + (i + 0.5) * binStep > 30)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 9);
						else if (min + (i + 0.5) * binStep > 20)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 10);
						else if (min + (i + 0.5) * binStep > 10)
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 11);
						else
							plainPlot.plotPoint(min + (i + 0.5) * binStep,
												(plotBins[i]), 12);
					}
				}
			}
			
			
			if ((PlainPlotXMax < 0.0) || (PlainPlotYMax < 0.0))
				plainPlot.fillPlot();
			plainPlot.updateGraph();
			
		}
    };
	
    if (PlotPlain) {
		schedule.scheduleActionAtInterval(100, new PlainPlot(),
										  Schedule.LAST);
    };
  }

  // Randomize the order of the object (the WealthAgents) in the agentList
  public void shuffleAgents() {
   SimUtilities.shuffle(agentList);
  }

  // Add a new agent.
  public void addNewAgent() {
    WealthAgent agent = new WealthAgent(space, this);
    int x, y;

    do {
      x = Uniform.staticNextIntFromTo(0, space.getXSize() - 1);
      y = Uniform.staticNextIntFromTo(0, space.getYSize() - 1);
    } while (agentGrid.getObjectAt(x, y) != null);

    agentGrid.putObjectAt(x, y, agent);
    agent.setXY(x, y);

    // Use the initial simulation parameters (maxMetabolism etc.) to construct
    //the agents
    if (Sync)
        agent.setWealth((double) (Uniform.staticNextIntFromTo(0,
                  SyncMax)));
    else
        agent.setWealth((double) (Uniform.staticNextIntFromTo(MinInitialWealth,
                  MaxInitialWealth)));
    
    if (DeathTax) {
      agent.setMaxAge(Uniform.staticNextIntFromTo(WealthModel.MaxAge / 2, WealthModel.MaxAge));
      agent.setAge(Uniform.staticNextIntFromTo(0, agent.getMaxAge()));
    }
    else
      agent.setAge(0);
	
	if (Smarter)
		if (1 == Uniform.staticNextIntFromTo(1, SmarterNum))
			agent.setSmart(Normal.staticNextDouble(0.01,0.005));
    
    agentBirth(agent);
  }

  public void agentBirth(WealthAgent agent) {
//    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(WealthAgent agent) {
    reaperQueue.add(agent);
    if (replace) {
      addNewAgent();
    }
  }

  public static WealthAgent getAgentAt(int x, int y) {
    return (WealthAgent)agentGrid.getObjectAt(x, y);
  }

  public void reapAgents() {
    ListIterator li = reaperQueue.listIterator();

    while (li.hasNext()) {
      WealthAgent agent = (WealthAgent)li.next();
      agentList.remove(agent);
      agentGrid.putObjectAt(agent.getX(), agent.getY(), null);
    }

    reaperQueue.clear();
  }

  public static void moveAgent(WealthAgent agent, int x, int y) {
    agentGrid.putObjectAt(agent.getX(), agent.getY(), null);
    agentGrid.putObjectAt(x, y, agent);
    agent.setXY(x, y);
  }


  // When a simulation is started through SimInit, some BaseController is
  // created to control the running of that model. This BaseController calls
  // getInitParam() on the model and receives a list of initial parameters
  // that can be displayed for modification to the user. In order to display
  // the value of these parameters the controller determines if the model
  // has implemented get and set methods for that parameter. If so, the
  // controller calls the get method and displays the result to the user. A
  // similar process occurs when a model's initial starting parameters
  // are written to a data file.
  //
  // What this means is that if a user wants to display some initial starting
  // parameter and have this parameter be modifiable, the name of the parameter
  // must be returned by the getInitParam() method and the model must contain
  // the appropriate get and set methods. For example, to do the above for
  // a parameter called NumAgents, "NumAgents" must be present in the array
  // return by getInitParam and the model must have a getNumAgents method and a
  // setNumAgents method. The parameter name must match the method names minus
  // the "get" and "set". So "numAgents" and getNumAgents won't work, but
  // "NumAgents" and getNumAgents will.
  //
  // The following methods are an example of this pattern.
  public int getNumAgents() {
    return NumAgents;
  }

  public void setNumAgents(int num) {
    NumAgents = num;
  }

  public int getMaxInitialWealth() {
    return MaxInitialWealth;
  }

  public void setMaxInitialWealth(int maxInitWealth) {
    MaxInitialWealth = maxInitWealth;
  }

  public int getMinInitialWealth() {
    return MinInitialWealth;
  }

  public void setMinInitialWealth(int minInitWealth) {
    MinInitialWealth = minInitWealth;
  }
  
  public boolean getROI() {
    return ROI;
  }

  public void setROI(boolean newROI) {
    ROI = newROI;
  }
  
  public boolean getBoth() {
	  return Both;
  }
  
  public void setBoth(boolean newBoth) {
	  Both = newBoth;
  }
  
  public double getMinWealth() {
	  return MinWealth;
  }
  
  public void setMinWealth(double newMinWealth) {
	  MinWealth = newMinWealth;
  }  

  public boolean getRandomWalk() {
    return RandomWalk;
  }

  public void setRandomWalk(boolean newRandomWalk) {
    RandomWalk = newRandomWalk;
  }
  
  public boolean getSmarter() {
	  return Smarter;
  }
  
  public void setSmarter(boolean newSmarter) {
	  Smarter = newSmarter;
  }
  
  public int getSmarterNum() {
	  return SmarterNum;
  }
  
  public void setSmarterNum(int newSmarterNum) {
	  SmarterNum = newSmarterNum;
  }
  
  
  public boolean getDeathTax() {
    return DeathTax;
  }

  public void setDeathTax(boolean newDeathTax) {
    DeathTax = newDeathTax;
  }
  
  public boolean getMoving() {
    return Moving;
  }

  public void setMoving(boolean newMoving) {
    Moving = newMoving;
  }
  
  public boolean getSync() {
    return Sync;
  }

  public void setSync(boolean newSync) {
    Sync = newSync;
  }
  
    public boolean getSyncRegion() {
    return SyncRegion;
  }

  public void setSyncRegion(boolean newSyncRegion) {
    SyncRegion = newSyncRegion;
  }
  
  public double getSyncLatency() {
	  return SyncLatency;
  }
  
  public void setSyncLatency(double newSyncLatency) {
	  SyncLatency = newSyncLatency;
  }
  
  public int getSyncMax() {
    return SyncMax;
  }

  public void setSyncMax(int newSyncMax) {
    SyncMax = newSyncMax;
  }
  
  public int getNumLogLogBins() {
	  return NumLogLogBins;
  }
  
  public void setNumLogLogBins(int newNumLogLogBins) {
	  NumLogLogBins = newNumLogLogBins;
  }
  
  public int getNumPlainBins() {
	  return NumPlainBins;
  }
  
  public void setNumPlainBins(int newNumPlainBins) {
	  NumPlainBins = newNumPlainBins;
  }
  
  public double getPlainPlotXMax() {
	  return PlainPlotXMax;
  }
  
  public void setPlainPlotXMax(double newPlainPlotXMax) {
	  PlainPlotXMax = newPlainPlotXMax;
  }
	
  public double getPlainPlotYMax() {
		return PlainPlotYMax;
  }
	
  public void setPlainPlotYMax(double newPlainPlotYMax) {
		PlainPlotYMax = newPlainPlotYMax;
  }
	
  public boolean getPlotLogLog() {
		return PlotLogLog;
  }
	
  public void setPlotLogLog(boolean newPlotLogLog) {
		PlotLogLog = newPlotLogLog;
  }
  
  public boolean getPlotPlain() {
	  return PlotPlain;
  }
  
  public void setPlotPlain(boolean newPlotPlain) {
	  PlotPlain = newPlotPlain;
  }
  
  public boolean getViewStats() {
    return ViewStats;
  }

  public void setViewStats(boolean newViewStats) {
    ViewStats = newViewStats;
  }
  
  public boolean getViewAvg() {
	  return ViewAvg;
  }
  
  public void setViewAvg(boolean newViewAvg) {
	  ViewAvg = newViewAvg;
  }
  
  public boolean getViewStDev() {
	  return ViewStDev;
  }
  
  public void setViewStDev(boolean newViewStDev) {
	  ViewStDev = newViewStDev;
  }
  
  public boolean getViewStDAvg() {
	  return ViewStDAvg;
  }
  
  public void setViewStDAvg(boolean newViewStDAvg) {
	  ViewStDAvg = newViewStDAvg;
  }
  
  public boolean getWriteStats() {
	  return WriteStats;
  }
  
  public void setWriteStats(boolean newWriteStats) {
	  WriteStats = newWriteStats;
  }  

  public int getMaxAge() {
    return MaxAge;
  }

  public void setMaxAge(int newMaxAge) {
    MaxAge = newMaxAge;
  }
  
  public int getTaxPCT() {
    return TaxPCT;
  }

  public void setTaxPCT(int newTaxPCT) {
    TaxPCT = newTaxPCT;
  }
  
  public double getROIMean() {
    return ROIMean;
  }

  public void setROIMean(double newROIMean) {
    ROIMean = newROIMean;
  }
  
  public double getROIStdDev() {
    return ROIStdDev;
  }

  public void setROIStdDev(double newROIStdDev) {
    ROIStdDev = newROIStdDev;
  }
  
  public boolean getViewPowerLawExp() {
	  return ViewPowerLawExp;
  }
  
  public void setViewPowerLawExp(boolean newViewPowerLawExp) {
	  ViewPowerLawExp = newViewPowerLawExp;
  }
  
  public int getRichMinPct() {
	  return RichMinPct;
  }
  
  public void setRichMinPct(int newRichMinPct) {
	  RichMinPct = newRichMinPct;
  }
  
  public boolean getViewEntropy() {
	  return ViewEntropy;
  }
  
  public void setentropyBin(int newentropyBin) {
	  entropyBin = newentropyBin;
  }
  
  public int getentropyBin() {
	  return entropyBin;
  }
  
  public void setViewEntropy(boolean newViewEntropy) {
	  ViewEntropy = newViewEntropy;
  }
  
  public String getBehaviour()
  {
	  return behaviour;
  }
  
  public void setBehaviour(String behaviour)
  {
	  this.behaviour = behaviour;
  }

  public String[] getInitParam() {
	  
      String[] params =  {"NumAgents", "Behaviour", "Moving", "DeathTax", "TaxPCT", "MaxAge",
					"MaxInitialWealth", "MinInitialWealth", "MinWealth",
					"NumLogLogBins", "NumPlainBins",
					"PlainPlotXMax", "PlainPlotYMax", "PlotLogLog", "PlotPlain",
					"ROIMean", "ROIStdDev", "Smarter", "SmarterNum",
                    "SyncLatency", "SyncMax", "SyncRegion",
					"ViewAvg", "ViewStDAvg", "ViewStDev", "ViewStats", "ViewPowerLawExp", "RichMinPct",
					"ViewEntropy", "entropyBin",
					"WriteStats" };
    return params;
  }

  // Every model must have begin() and setup() methods (required for implementing
  // the SimModel inteface).

  // begin() should intialize the model for the start of a run. Consequently,
  // the build* methods are called here, and any displays are displayed. build()
  // is called whenever the start button (or the step button if the run has
  // not yet started) is clicked.
  public void begin() {
    buildModel();
    buildDisplay();
    buildSchedule();
	
	Exchange = false;
	Both = false;
	ROI = false;
	RandomWalk = false;
	Sync = false;
	
	step = 0;
	if (behaviour == "Exchange") {
		Exchange = true;
		setViewPowerLawExp(false);
	}
	if (behaviour == "Both") {
		Both = true;
		ROI = true;
		Exchange = true;
	}
	if (behaviour == "ROI")
		ROI = true;
	if (behaviour == "RandomWalk") {
		RandomWalk = true;
		setViewPowerLawExp(false);
	}
	if (behaviour == "Sync") {
		Sync = true;
		setPlotLogLog(false);
		setPlotPlain(false);
		setViewPowerLawExp(false);
		setViewAvg(false);
		setViewStDAvg(false);
		setViewStDev(false);
		setViewStats(false);
		setViewEntropy(false);
	}

    dsurf.display();
    bar.display();
	if (ViewPowerLawExp)
		powerlawGraph.display();
	if (ViewEntropy)
		entropyGraph.display();
    if (ViewStats || ViewAvg || ViewStDev || ViewStDAvg)
      statsGraph.display();
	if (PlotLogLog) {
		logLogPlot.display();
		
		logLogPlot.addLegend(1, "log(1-cumul.)", java.awt.Color.green);
		logLogPlot.addLegend(2, "log:log plot", java.awt.Color.blue);
	}
	if (PlotPlain) {
		plainPlot.display();
		plainPlot.setConnected(false);
	}
//    if (DeathTax)
//      barAge.display();
  }

  // setup() prepares the model for another run. Called whenver the setup button
  // is clicked. Setup should set any objects that are created over the course
  // of the run to null, and dispose of any DisplaySurfaces or graphs. While
  // not strictly necessary this should some prevent memory leaks and calling
  // System.gc() helps too. The initial parameters should be set to whatever
  // defaults the user wants to see initially.
  public void setup() {
    schedule = null;
    agentList = new ArrayList();
    birthList = new Vector();
    space = null;
    agentGrid = null;
    reaperQueue = new Vector();

	Both = false;
	ROI = false;
	RandomWalk = false;
	Sync = false;
	
	if (behaviour == "Both")
		Both = true;
	if (behaviour == "ROI")
		ROI = true;
	if (behaviour == "RandomWalk")
		RandomWalk = true;
	if (behaviour == "Sync")
		Sync = true;
	
    if (dsurf != null)
      dsurf.dispose();
    dsurf = null;
	
	if (powerlawGraph != null)
		powerlawGraph.dispose();
	powerlawGraph = null;
	
	if (entropyGraph != null)
		entropyGraph.dispose();
	entropyGraph = null;
	
    if (statsGraph != null)
      statsGraph.dispose();
    statsGraph = null;
	
	if (ViewStDAvg)
		for (int i = 0; i < MAvgRange; i++)
			StDAvg[i] = 0.0;

    if (bar != null)
      bar.dispose();
    bar = null;
	
	if (logLogPlot != null)
		logLogPlot.dispose();
	logLogPlot = null;
	
	if (plainPlot != null)
		plainPlot.dispose();
	plainPlot = null;
    
//    if (barAge != null)
//      barAge.dispose();
//    barAge = null;

    System.gc();

    // create a schedule with an interval of one.
    schedule = new Schedule(1);
    dsurf = new DisplaySurface(this, "Wealth Scape");

    // By registering a DisplaySurface, you are allowing Repast to automate
    // some tasks on the displays surface, such as making movies or taking
    // snapshots.
    registerDisplaySurface("Wealth Scape", dsurf);

    // creates a dynamic histogram of the distribution of wealth over the
    // agents.
    bar = new OpenHistogram("Agent Wealth Distribution", 16, 0, this);

    this.registerMediaProducer("Hist", bar);
    

//    barAge = new OpenHistogram("Agent Age Distribution", 16, 0, this);
//    this.registerMediaProducer("Hist", barAge);


    // agent properties
//  numAgents = 1600;
//  maxInitialWealth = 100;
//  minInitialWealth = 100;

  }

  // a required method
  public Schedule getSchedule() {
    return schedule;
  }

  // a required method - displayed on the Controller toolbar.
  public String getName() {
    return "WealthScape";
  }

  public static void main(String[] args) {
    SimInit init = new SimInit();
    WealthModel model = new WealthModel();
    init.loadModel(model, "", false);
  }
}
