content: package tim.prune.data;
  
  
  /**
   * Represents a range of altitudes, taking units into account.
   * Values assumed to be >= 0.
   */
  public class AltitudeRange
  {
  	/** Range of altitudes in metres */
  	private final DoubleRange _range = new DoubleRange();
  	/** Tolerance value in metres */
  	private final int _wiggleLimit;
  	/** Flag for whether previous value exists or not */
  	private boolean _gotPreviousValue;
  	/** Previous metric value */
  	private double _previousValue;
  	/** Total climb in metres */
  	private int _climb;
  	/** Total descent in metres */
  	private int _descent;
  	/** Flags for whether minimum or maximum has been found */
  	private boolean _gotPreviousMinimum, _gotPreviousMaximum;
  	/** Value of previous minimum or maximum, if any */
  	private double _previousExtreme;
  
  
  	/**
  	 * Constructor
  	 * @param toleranceMetres tolerance (wiggle limit) in metres
  	 */
  	public AltitudeRange(int toleranceMetres)
  	{
  		_wiggleLimit = toleranceMetres;
  		_range.clear();
  		_climb = _descent = 0;
  		_gotPreviousValue = false;
  		_previousValue = 0;
  		_gotPreviousMinimum = _gotPreviousMaximum = false;
  		_previousExtreme = 0;
  	}
  
  
  	/**
  	 * Add a value to the range
  	 * @param inAltitude value to add, only positive values considered
  	 */
  	public void addValue(Altitude inAltitude)
  	{
  		if (inAltitude != null && inAltitude.isValid())
  		{
  			int altValue = (int) inAltitude.getMetricValue();
  			_range.addValue(altValue);
  			// Compare with previous value if any
  			if (_gotPreviousValue)
  			{
  				if (altValue != _previousValue)
  				{
  					// Got an altitude value which is different from the previous one
  					final boolean locallyUp = (altValue > _previousValue);
  					final boolean overallUp = _gotPreviousMinimum && _previousValue > _previousExtreme;
  					final boolean overallDn = _gotPreviousMaximum && _previousValue < _previousExtreme;
  					final boolean moreThanWiggle = Math.abs(altValue - _previousValue) > _wiggleLimit;
  					// Do we know whether we're going up or down yet?
  					if (!_gotPreviousMinimum && !_gotPreviousMaximum)
  					{
  						// we don't know whether we're going up or down yet - check limit
  						if (moreThanWiggle)
  						{
  							if (locallyUp) {_gotPreviousMinimum = true;}
  							else {_gotPreviousMaximum = true;}
  							_previousExtreme = _previousValue;
  							_previousValue = altValue;
  						}
  					}
  					else if (overallUp)
  					{
  						if (locallyUp) {
  							// we're still going up - do nothing
  							_previousValue = altValue;
  						}
  						else if (moreThanWiggle)
  						{
  							// we're going up but have dropped over a maximum
  							// Add the climb from _previousExtreme up to _previousValue
  							_climb += (_previousValue - _previousExtreme);
  							_previousExtreme = _previousValue;
  							_gotPreviousMinimum = false; _gotPreviousMaximum = true;
  							_previousValue = altValue;
  						}
  					}
  					else if (overallDn)
  					{
  						if (locallyUp) {
  							if (moreThanWiggle)
  							{
  								// we're going down but have climbed up from a minimum
  								// Add the descent from _previousExtreme down to _previousValue
  								_descent += (_previousExtreme - _previousValue);
  								_previousExtreme = _previousValue;
  								_gotPreviousMinimum = true; _gotPreviousMaximum = false;
  								_previousValue = altValue;
  							}
  						}
  						else {
  							// we're still going down - do nothing
  							_previousValue = altValue;
  						}
  					}
  					// Note: Behaviour when WIGGLE_LIMIT == 0 should be same as before, all differences cumulated
  				}
  			}
  			else
  			{
  				// we haven't got a previous value at all, so it's the start of a new segment
  				_previousValue = altValue;
  				_gotPreviousValue = true;
  			}
  		}
  	}
  
  	/**
  	 * Reset the climb/descent calculations starting from the given value
  	 * @param inAltitude altitude value
  	 */
  	public void ignoreValue(Altitude inAltitude)
  	{
  		// Process the previous value, if any, to update climb/descent as that's the end of the previous segment
  		if (_gotPreviousValue && _gotPreviousMinimum && _previousValue > _previousExtreme) {
  			_climb += (_previousValue - _previousExtreme);
  		}
  		else if (_gotPreviousValue && _gotPreviousMaximum && _previousValue < _previousExtreme) {
  			_descent += (_previousExtreme - _previousValue);
  		}
  		// Eliminate the counting values to start the new segment
  		_gotPreviousMinimum = _gotPreviousMaximum = false;
  		_gotPreviousValue = false;
  		// Now process this value if there is one
  		if (inAltitude != null && inAltitude.isValid())
  		{
  			final int altValue = (int) inAltitude.getMetricValue();
  			_range.addValue(altValue);
  			_previousValue = altValue;
  			_gotPreviousValue = true;
  		}
  	}
  
  	/**
  	 * @return true if altitude range found
  	 */
  	public boolean hasRange() {
  		return _range.hasData();
  	}
  
  	/**
  	 * @param inUnit altitude units to use
  	 * @return minimum value
  	 */
  	public int getMinimum(Unit inUnit)
  	{
  		return (int) (_range.getMinimum() * inUnit.getMultFactorFromStd());
  	}
  
  	/**
  	 * @param inUnit altitude units to use
  	 * @return maximum value
  	 */
  	public int getMaximum(Unit inUnit)
  	{
  		return (int) (_range.getMaximum() * inUnit.getMultFactorFromStd());
  	}
  
  	/**
  	 * @param inUnit altitude units to use
  	 * @return total climb
  	 */
  	public int getClimb(Unit inUnit)
  	{
  		// May need to add climb from last segment
  		double lastSegmentClimb = 0.0;
  		if (_gotPreviousValue && _gotPreviousMinimum && _previousValue > _previousExtreme) {
  			lastSegmentClimb = _previousValue - _previousExtreme;
  		}
  		return (int) ((_climb + lastSegmentClimb) * inUnit.getMultFactorFromStd());
  	}
  
  	/**
  	 * @param inUnit altitude units to use
  	 * @return total descent
  	 */
  	public int getDescent(Unit inUnit)
  	{
  		// May need to add descent from last segment
  		double lastSegmentDescent = 0.0;
  		if (_gotPreviousValue && _gotPreviousMaximum && _previousValue < _previousExtreme) {
  			lastSegmentDescent = _previousExtreme - _previousValue;
  		}
  		return (int) ((_descent + lastSegmentDescent) * inUnit.getMultFactorFromStd());
  	}
  
  	/**
  	 * @return overall height gain in metres
  	 */
  	public double getMetricHeightDiff() {
  		return getClimb(UnitSetLibrary.UNITS_METRES) - getDescent(UnitSetLibrary.UNITS_METRES);
  	}
  }
  