content: /*
   * Copyright 2010 Aalto University, ComNet
   * Released under GPLv3. See LICENSE.txt for details.
   */
  package util;
  
  import java.util.LinkedList;
  import java.util.Queue;
  
  import core.Settings;
  import core.SettingsError;
  import core.SimClock;
  
  /**
   * Object of this class tell the models when a node belonging
   * to a certain group is active and when not.
   */
  public class ActivenessHandler {
  
  	/**
  	 * <P>Active times -setting id ({@value})</P>
  	 * <P>Syntax: <CODE>start, end</CODE></P>
  	 * <P>Defines the simulation time ranges when the node is active. Multiple
  	 * timer ranges can be concatenated by repeating the sequence. Time
  	 * limits must be in order and must not overlap.</P>
  	 * <P>Example: 100,200,6000,6500<BR>
  	 * Here node is active from 100 to 200 and from 6000 to 6500 simulated
  	 * seconds from the start of the simulation.</P>
  	 */
  	public static final String ACTIVE_TIMES_S = "activeTimes";
  
  	/**
  	 * <P>Active periods -setting id ({@value}).</P>
  	 * <P>Syntax: <CODE>activeTime, inactiveTime</CODE></P>
  	 * <P>Defines the activity and inactivity periods. </P>
  	 * <P>Example: 2000,500<BR>
  	 * Here node is periodically first active for 2000 seconds, followed by 500
  	 * seconds of inactiveness, and 2000 seconds of activeness, 500 seconds of
  	 * inactiveness, etc.</P>
  	 */
  	public static final String ACTIVE_PERIODS_S = "activePeriods";
  
  	/**
  	 * <P>Active periods offset -setting id ({@value}).</P>
  	 * <P>Defines how much the activity periods are offset from sim time 0.
  	 * Value X means that the first period has been on for X seconds at sim
  	 * time 0. Default = 0 (i.e., first active period starts at 0) </P>
  	 */
  	public static final String ACTIVE_PERIODS_OFFSET_S = "activePeriodsOffset";
  
  	private Queue<TimeRange> activeTimes;
  	private int [] activePeriods;
  	private int activePeriodsOffset;
  
  	private TimeRange curRange = null;
  
  	public ActivenessHandler(Settings s) {
  		this.activeTimes = parseActiveTimes(s);
  
  		if (activeTimes != null) {
  			this.curRange = activeTimes.poll();
  		} else if (s.contains(ACTIVE_PERIODS_S)){
  			this.activePeriods = s.getCsvInts(ACTIVE_PERIODS_S, 2);
  			this.activePeriodsOffset = s.getInt(ACTIVE_PERIODS_OFFSET_S, 0);
  		} else {
  			this.activePeriods = null;
  		}
  	}
  
  	private Queue<TimeRange> parseActiveTimes(Settings s) {
  		double [] times;
  		String sName = s.getFullPropertyName(ACTIVE_TIMES_S);
  
  		if (s.contains(ACTIVE_TIMES_S)) {
  			times = s.getCsvDoubles(ACTIVE_TIMES_S);
  			if (times.length % 2 != 0) {
  				throw new SettingsError("Invalid amount of values (" +
  						times.length + ") for setting " + sName + ". Must " +
  						"be divisable by 2");
  			}
  		}
  		else {
  			return null; // no setting -> always active
  		}
  
  		Queue<TimeRange> timesList = new LinkedList<TimeRange>();
  
  		for (int i=0; i<times.length; i+= 2) {
  			double start = times[i];
  			double end = times[i+1];
  
  			if (start > end) {
  				throw new SettingsError("Start time (" + start + ") is " +
  						" bigger than end time (" + end + ") in setting " +
  						sName);
  			}
  
  			timesList.add(new TimeRange(start, end));
  		}
  
  		return timesList;
  	}
  
  	/**
  	 * Returns true if node should be active at the moment
  	 * @return true if node should be active at the moment
  	 */
  	public boolean isActive() {
  		return isActive(0);
  	}
  
  	/**
  	 * Returns true if node should be active after/before offset amount of
  	 * time from now
  	 * @param offset The offset
  	 * @return true if node should be active, false if not
  	 */
  	public boolean isActive(int offset) {
  		if (this.activeTimes == null) {
  			if (this.activePeriods == null) {
  				return true; // no inactive times nor periods -> always active
  			} else {
  				/* using active periods mode */
  				int timeIndex =
  					(SimClock.getIntTime() + this.activePeriodsOffset + offset)%
  					(this.activePeriods[0] + this.activePeriods[1]);
  				if (timeIndex <= this.activePeriods[0]) {
  					return true;
  				} else {
  					return false;
  				}
  			}
  		}
  
  		if (curRange == null) {
  			return false; // out of active times
  		}
  
  		double time = SimClock.getTime() + offset;
  
  		if (this.curRange.isOut(time)) { // time for the next time range
  			this.curRange = activeTimes.poll();
  			if (curRange == null) {
  				return false; // out of active times
  			}
  		}
  
  		return curRange.isInRange(time);
  	}
  
  	/**
  	 * Class for handling time ranges
  	 */
  	private class TimeRange {
  		private double start;
  		private double end;
  
  		/**
  		 * Constructor.
  		 * @param start The start time
  		 * @param end The end time
  		 */
  		public TimeRange(double start, double end) {
  			this.start = start;
  			this.end = end;
  		}
  
  		/**
  		 * Returns true if the given time is within start and end time
  		 * (inclusive).
  		 * @param time The time to check
  		 * @return true if the time is within limits
  		 */
  		public boolean isInRange(double time) {
  			if (time < start || time > end ) {
  				return false; // out of range
  			}
  			return true;
  		}
  
  		/**
  		 * Returns true if given time is bigger than end the end time
  		 * @param time The time to check
  		 * @return true if given time is bigger than end
  		 */
  		public boolean isOut(double time) {
  			return time > end;
  		}
  	}
  }
  