package bsearch.space;

import org.nlogo.util.MersenneTwisterFast;

public strictfp class DoubleDiscreteSpec extends ParameterSpec {
	private double dMin;
	private double dStep;
	private double dMax;	
	
	public DoubleDiscreteSpec(String name, double min, double step, double max) {
		super(name);
		dMin = min;
		dStep = step;
		// change dMax to be the actual largest value allowed, given the step size
		dMax = min + step * StrictMath.floor((max-min)/step);
	}

	private double enforceValidRange(double d)
	{
		if (d < dMin)
			d = dMin;
		else if (d > dMax)
			d = dMax;
		return  dMin + StrictMath.round((d - dMin) / dStep) * dStep; 
	}

	@Override
	public Double generateRandomValue(MersenneTwisterFast rng) {
		return rng.nextInt(1 + (int)StrictMath.floor((dMax - dMin)/dStep)) * dStep + dMin;
	}

	/**
	 * @param obj - Double - parameter value to be mutated 
	 * @param mutStrength - controls the magnitude of the Gaussian mutation.
	 * (not to be confused with the mutation-rate, which controls the likelihood of mutation).
		For example, mutStrength=0.1 corresponds to a stdDev of 10% of the parameter's range
		This means that 68% of the time, the mutation will fall within +/- 10%
		and 95% of the time, the mutation will fall within +/- 20%.
	 * @param rng - random number generator
	 * @returns the result of adding Gaussian noise to the given parameter value, 
	 *     clamped to be one of the possible discrete double values in this parameter's range.
	 */
	@Override
	public Double mutate(Object obj, double mutStrength, MersenneTwisterFast rng) {
		double mutStdDev = (dMax - dMin) * mutStrength;	
		return enforceValidRange((Double)obj + mutStdDev * rng.nextGaussian());
	}

	@Override
	public int choiceCount()
	{
		return 1 + (int)StrictMath.floor((dMax - dMin)/dStep) ;
	}	

	@Override
	public String toString()
	{
		return "[ \"" + name + "\" [ " + dMin + " " + dStep + " " + dMax + " ]]";
	}

	@Override
	public Object getValueFromChoice( long choice , long maxNumChoices)
	{
		if (choice < choiceCount())
		{
			return new Double(dMin + dStep * choice);
		}
		else
		{
			// If the choice is greater than the choices, we assume this is because with bitstring representations, 
			// the maxNumChoices is the next power of 2 above choiceCount().
			// In this case, we try to spread out these "extra" choice settings across the whole range, since
			// this seems more fair than choosing all low settings.  
			// (Of course, some numbers are still more likely to be chosen than others, but that's unavoidable.) 
			long wrappedChoice = (long) StrictMath.round((double) (choice % choiceCount()) / (maxNumChoices - choiceCount()) * choiceCount());
			return new Double(dMin + dStep * wrappedChoice);
		}	
	}
	@Override
	public long getChoiceIndexFromValue(Object val, long maxNumChoices) {
		if (!(val instanceof Number))
		{
			System.out.println(val);
			throw new IllegalStateException("Type mismatch: can't represent a non-number using this double-valued parameter specification.");
		}
		double dVal = ((Number) val).doubleValue();
		
		return (long) StrictMath.round((dVal - dMin) / dStep);
	}

	
}
