content: package report;
  
  import core.Coord;
  import core.DTNHost;
  import core.Settings;
  import core.SettingsError;
  import core.SimScenario;
  
  import java.util.ArrayList;
  import java.util.List;
  
  /**
   * <p>Sampling report that counts the number of nodes in grid over the
   * simulation area. Output format is: G_x G_y average count_1, count_2, ...
   * Where G_x and G_y are the coordinates of the grid square [0, 1, ...] and
   * count_n is the count during the nth sample.
   *
   * <p>The report can be configured to output a gnuplot script file that
   * produces a heat map graph of the node densities.
   *
   * @author teemuk
   */
  public class NodeDensityReport
  extends SamplingReport {
  	//========================================================================//
  	// Settings
  	//========================================================================//
  	/** Number of divisions along the x-axis ({@value}). */
  	public static final String X_COUNT_SETTING = "xCount";
  	/** Number of divisions along the y-axis ({@value}). */
  	public static final String Y_COUNT_SETTING = "yCount";
  	/** Boolean setting to output gnuplot file ({@value}). */
  	public static final String GNUPLOT_SETTING = "outputGnuplot";
  	/** Setting for gnuplot terminal, i.e., terminal set X ({@value}). */
  	public static final String GNUPLOT_TERMINAL_SETTING = "gnuplotTerminal";
  	/** Setting for file extension for files generated by gnuplot ({@value}). */
  	public static final String GNUPLOT_FILE_EXTENSION_SETTING =
  			"gnuplotFileExtension";
  	/** Boolean setting to output only the average ({@value}). */
  	public static final String ONLY_AVERAGE_SETTING = "onlyAverage";
  
  	/** Default number of divisions along the x-axis ({@value}). */
  	public static final int DEFAULT_X_COUNT = 10;
  	/** Default number of divisions along the y-axis ({@value}). */
  	public static final int DEFAULT_Y_COUNT = 10;
  	/** Default value for outputting gnuplot instead of raw data ({@value}). */
  	public static final boolean DEFAULT_GNUPLOT = false;
  	/** Default value for gnuplot terminal ({@value}). */
  	public static final String DEFAULT_GNUPLOT_TERMINAL =
  			"png size 1024,768";
  	public static final String DEFAULT_GNUPLOT_FILE_EXTENSION = "png";
  	/** Default value for outputting only the average density ({@value}). */
  	public static final boolean DEFAULT_ONLY_AVERAGE = false;
  	//========================================================================//
  
  
  	//========================================================================//
  	// Instance vars
  	//========================================================================//
  	private final int horizontalCount;
  	private final int verticalCount;
  	private final double divisionWidth;
  	private final double divisionHeight;
  
  	private final boolean gnuplot;
  	private final String gnuplotTerminal;
  	private final String gnuplotFileExtension;
  
  	private final boolean onlyAverage;
  	private final String runName;
  	private final List<int[][]> samples;
  	//========================================================================//
  
  
  	//========================================================================//
  	// Constructor
  	//========================================================================//
  	public NodeDensityReport() {
  		super();
  
  		final Settings settings = super.getSettings();
  
  		this.gnuplot = settings.getBoolean(GNUPLOT_SETTING,
  				DEFAULT_GNUPLOT);
  		this.onlyAverage = settings.getBoolean(ONLY_AVERAGE_SETTING,
  				DEFAULT_ONLY_AVERAGE);
  		this.gnuplotTerminal = settings.getSetting(GNUPLOT_TERMINAL_SETTING,
  				DEFAULT_GNUPLOT_TERMINAL);
  		this.gnuplotFileExtension = settings.getSetting(
  				GNUPLOT_FILE_EXTENSION_SETTING, DEFAULT_GNUPLOT_FILE_EXTENSION);
  
  		// Set up the sampling grid
  		this.horizontalCount
  				= settings.getInt(X_COUNT_SETTING, DEFAULT_X_COUNT);
  		this.verticalCount
  				= settings.getInt(Y_COUNT_SETTING, DEFAULT_Y_COUNT);
  
  		if (this.horizontalCount <= 0 || this.verticalCount <= 0) {
  			throw new SettingsError("Settings '" + X_COUNT_SETTING + "' and '"
  					+ Y_COUNT_SETTING + "' must be positive. Found "
  					+ this.horizontalCount + ", " + this.verticalCount + ".");
  		}
  
  		final SimScenario scenario = SimScenario.getInstance();
  		final int worldWidth = scenario.getWorldSizeX();
  		final int worldHeight = scenario.getWorldSizeY();
  
  		this.divisionWidth = 1.0 * worldWidth / this.horizontalCount;
  		this.divisionHeight = 1.0 * worldHeight / this.verticalCount;
  
  		final double duration = scenario.getEndTime();
  		final int sampleCount = (int) (duration / super.interval + 1);
  
  		this.samples = new ArrayList<int[][]>(sampleCount);
  
  		this.runName = scenario.getName();
  	}
  	//========================================================================//
  
  
  	//========================================================================//
  	// SamplingReport
  	//========================================================================//
  	@Override
  	protected void sample(final List<DTNHost> hosts) {
  		final int[][] sample
  				= new int[this.horizontalCount][this.verticalCount];
  
  		for (final DTNHost host : hosts) {
  			final Coord location = host.getLocation();
  			final int xBucket = (int) (location.getX() / this.divisionWidth);
  			final int yBucket = (int) (location.getY() / this.divisionHeight);
  			sample[xBucket][yBucket]++;
  		}
  
  		this.samples.add(sample);
  	}
  	//========================================================================//
  
  
  	//========================================================================//
  	// Report
  	//========================================================================//
  	@Override
  	public void done() {
  
  		if (this.gnuplot) {
  			this.outputGnuplotPrefix();
  		}
  
  		final double[][] averages = getAverageDensities(this.samples,
  				this.horizontalCount, this.verticalCount);
  
  		for (int g_x = 0; g_x < this.horizontalCount; g_x++) {
  			for (int g_y = 0; g_y < this.verticalCount; g_y++) {
  				String line = "" + g_x + " " + g_y + " "
  						+ averages[g_x][g_y];
  				if (!this.onlyAverage) {
  					for (final int[][] sample : this.samples) {
  						line += " " + sample[g_x][g_y];
  					}
  				}
  				super.write(line);
  			}
  		}
  
  		if (this.gnuplot) {
  			this.outputGnuplotSuffix(this.samples.size());
  		}
  
  		super.done();
  	}
  	//========================================================================//
  
  
  	//========================================================================//
  	// Private
  	//========================================================================//
  	private static double[][] getAverageDensities(
  			final List<int[][]> samples,
  			final int horizontalCount,
  			final int verticalCount) {
  		final double[][] averages = new double[horizontalCount][verticalCount];
  
  		// Get total count
  		for (final int[][] sample : samples) {
  			for (int g_x = 0; g_x < horizontalCount; g_x++) {
  				for (int g_y = 0; g_y < verticalCount; g_y++) {
  					averages[g_x][g_y] += sample[g_x][g_y];
  				}
  			}
  		}
  
  		// Calculate average
  		final int count = samples.size();
  		for (int g_x = 0; g_x < horizontalCount; g_x++) {
  			for (int g_y = 0; g_y < verticalCount; g_y++) {
  				averages[g_x][g_y] /= count;
  			}
  		}
  
  		return averages;
  	}
  	//========================================================================//
  
  
  	//========================================================================//
  	// Private - Gnuplot
  	//========================================================================//
  	private void outputGnuplotPrefix() {
  		super.write("set terminal " + this.gnuplotTerminal);
  		super.write("set tic scale 0");
  		super.write("set xrange [-0.5:" + (this.horizontalCount - 1) + ".5]");
  		super.write("set yrange [-0.5:" + (this.verticalCount - 1) + ".5]");
  		super.write("$data << EOD");
  	}
  
  	private void outputGnuplotSuffix(final int sampleCount) {
  		super.write("EOD");
  		super.write("set output sprintf('" + this.runName
  				+ "-avrg." + this.gnuplotFileExtension + "')");
  		super.write("plot '$data' using 2:1:3 with image");
  
  		if (!this.onlyAverage) {
  			super.write("do for [ii=4:" + (3 + sampleCount) + "] {");
  			super.write("set output sprintf('" + this.runName
  					+ "%04.0f." + this.gnuplotFileExtension + "',ii-3)");
  			super.write("plot '$data' using 2:1:ii with image");
  			super.write("}");
  		}
  	}
  	//========================================================================//
  }
  