content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package input;
  
  import java.util.Random;
  
  import core.Settings;
  import core.SettingsError;
  
  /**
   * Message creation -external events generator. Creates uniformly distributed
   * message creation patterns whose message size and inter-message intervals can
   * be configured.
   */
  public class MessageEventGenerator implements EventQueue {
  	/** Message size range -setting id ({@value}). Can be either a single
  	 * value or a range (min, max) of uniformly distributed random values.
  	 * Defines the message size (bytes). */
  	public static final String MESSAGE_SIZE_S = "size";
  	/** Message creation interval range -setting id ({@value}). Can be either a
  	 * single value or a range (min, max) of uniformly distributed
  	 * random values. Defines the inter-message creation interval (seconds). */
  	public static final String MESSAGE_INTERVAL_S = "interval";
  	/** Sender/receiver address range -setting id ({@value}).
  	 * The lower bound is inclusive and upper bound exclusive. */
  	public static final String HOST_RANGE_S = "hosts";
  	/** (Optional) receiver address range -setting id ({@value}).
  	 * If a value for this setting is defined, the destination hosts are
  	 * selected from this range and the source hosts from the
  	 * {@link #HOST_RANGE_S} setting's range.
  	 * The lower bound is inclusive and upper bound exclusive. */
  	public static final String TO_HOST_RANGE_S = "tohosts";
  
  	/** Message ID prefix -setting id ({@value}). The value must be unique
  	 * for all message sources, so if you have more than one message generator,
  	 * use different prefix for all of them. The random number generator's
  	 * seed is derived from the prefix, so by changing the prefix, you'll get
  	 * also a new message sequence. */
  	public static final String MESSAGE_ID_PREFIX_S = "prefix";
  	/** Message creation time range -setting id ({@value}). Defines the time
  	 * range when messages are created. No messages are created before the first
  	 * and after the second value. By default, messages are created for the
  	 * whole simulation time. */
  	public static final String MESSAGE_TIME_S = "time";
  
  	/** Time of the next event (simulated seconds) */
  	protected double nextEventsTime = 0;
  	/** Range of host addresses that can be senders or receivers */
  	protected int[] hostRange = {0, 0};
  	/** Range of host addresses that can be receivers */
  	protected int[] toHostRange = null;
  	/** Next identifier for a message */
  	private int id = 0;
  	/** Prefix for the messages */
  	protected String idPrefix;
  	/** Size range of the messages (min, max) */
  	private int[] sizeRange;
  	/** Interval between messages (min, max) */
  	private int[] msgInterval;
  	/** Time range for message creation (min, max) */
  	protected double[] msgTime;
  
  	/** Random number generator for this Class */
  	protected Random rng;
  
  	/**
  	 * Constructor, initializes the interval between events,
  	 * and the size of messages generated, as well as number
  	 * of hosts in the network.
  	 * @param s Settings for this generator.
  	 */
  	public MessageEventGenerator(Settings s){
  		this.sizeRange = s.getCsvInts(MESSAGE_SIZE_S);
  		this.msgInterval = s.getCsvInts(MESSAGE_INTERVAL_S);
  		this.hostRange = s.getCsvInts(HOST_RANGE_S, 2);
  		this.idPrefix = s.getSetting(MESSAGE_ID_PREFIX_S);
  
  		if (s.contains(MESSAGE_TIME_S)) {
  			this.msgTime = s.getCsvDoubles(MESSAGE_TIME_S, 2);
  		}
  		else {
  			this.msgTime = null;
  		}
  		if (s.contains(TO_HOST_RANGE_S)) {
  			this.toHostRange = s.getCsvInts(TO_HOST_RANGE_S, 2);
  		}
  		else {
  			this.toHostRange = null;
  		}
  
  		/* if prefix is unique, so will be the rng's sequence */
  		this.rng = new Random(idPrefix.hashCode());
  
  		if (this.sizeRange.length == 1) {
  			/* convert single value to range with 0 length */
  			this.sizeRange = new int[] {this.sizeRange[0], this.sizeRange[0]};
  		}
  		else {
  			s.assertValidRange(this.sizeRange, MESSAGE_SIZE_S);
  		}
  		if (this.msgInterval.length == 1) {
  			this.msgInterval = new int[] {this.msgInterval[0],
  					this.msgInterval[0]};
  		}
  		else {
  			s.assertValidRange(this.msgInterval, MESSAGE_INTERVAL_S);
  		}
  		s.assertValidRange(this.hostRange, HOST_RANGE_S);
  
  		if (this.hostRange[1] - this.hostRange[0] < 2) {
  			if (this.toHostRange == null) {
  				throw new SettingsError("Host range must contain at least two "
  						+ "nodes unless toHostRange is defined");
  			}
  			else if (toHostRange[0] == this.hostRange[0] &&
  					toHostRange[1] == this.hostRange[1]) {
  				// XXX: teemuk: Since (X,X) == (X,X+1) in drawHostAddress()
  				// there's still a boundary condition that can cause an
  				// infinite loop.
  				throw new SettingsError("If to and from host ranges contain" +
  						" only one host, they can't be the equal");
  			}
  		}
  
  		/* calculate the first event's time */
  		this.nextEventsTime = (this.msgTime != null ? this.msgTime[0] : 0)
  			+ msgInterval[0] +
  			(msgInterval[0] == msgInterval[1] ? 0 :
  			rng.nextInt(msgInterval[1] - msgInterval[0]));
  	}
  
  
  	/**
  	 * Draws a random host address from the configured address range
  	 * @param hostRange The range of hosts
  	 * @return A random host address
  	 */
  	protected int drawHostAddress(int hostRange[]) {
  		if (hostRange[1] == hostRange[0]) {
  			return hostRange[0];
  		}
  		return hostRange[0] + rng.nextInt(hostRange[1] - hostRange[0]);
  	}
  
  	/**
  	 * Generates a (random) message size
  	 * @return message size
  	 */
  	protected int drawMessageSize() {
  		int sizeDiff = sizeRange[0] == sizeRange[1] ? 0 :
  			rng.nextInt(sizeRange[1] - sizeRange[0]);
  		return sizeRange[0] + sizeDiff;
  	}
  
  	/**
  	 * Generates a (random) time difference between two events
  	 * @return the time difference
  	 */
  	protected int drawNextEventTimeDiff() {
  		int timeDiff = msgInterval[0] == msgInterval[1] ? 0 :
  			rng.nextInt(msgInterval[1] - msgInterval[0]);
  		return msgInterval[0] + timeDiff;
  	}
  
  	/**
  	 * Draws a destination host address that is different from the "from"
  	 * address
  	 * @param hostRange The range of hosts
  	 * @param from the "from" address
  	 * @return a destination address from the range, but different from "from"
  	 */
  	protected int drawToAddress(int hostRange[], int from) {
  		int to;
  		do {
  			to = this.toHostRange != null ? drawHostAddress(this.toHostRange):
  				drawHostAddress(this.hostRange);
  		} while (from==to);
  
  		return to;
  	}
  
  	/**
  	 * Returns the next message creation event
  	 * @see input.EventQueue#nextEvent()
  	 */
  	public ExternalEvent nextEvent() {
  		int responseSize = 0; /* zero stands for one way messages */
  		int msgSize;
  		int interval;
  		int from;
  		int to;
  
  		/* Get two *different* nodes randomly from the host ranges */
  		from = drawHostAddress(this.hostRange);
  		to = drawToAddress(hostRange, from);
  
  		msgSize = drawMessageSize();
  		interval = drawNextEventTimeDiff();
  
  		/* Create event and advance to next event */
  		MessageCreateEvent mce = new MessageCreateEvent(from, to, this.getID(),
  				msgSize, responseSize, this.nextEventsTime);
  		this.nextEventsTime += interval;
  
  		if (this.msgTime != null && this.nextEventsTime > this.msgTime[1]) {
  			/* next event would be later than the end time */
  			this.nextEventsTime = Double.MAX_VALUE;
  		}
  
  		return mce;
  	}
  
  	/**
  	 * Returns next message creation event's time
  	 * @see input.EventQueue#nextEventsTime()
  	 */
  	public double nextEventsTime() {
  		return this.nextEventsTime;
  	}
  
  	/**
  	 * Returns a next free message ID
  	 * @return next globally unique message ID
  	 */
  	protected String getID(){
  		this.id++;
  		return idPrefix + this.id;
  	}
  }
  