import jas.hist.*;

public class SineFunction extends Fittable1DFunction implements HasHandles
{
	private double offset;
	private double amplitude; 
	private double period;
	
	private static final String[] parameters = { "offset", "amplitude", "period" };
	/**
	 * Create the SineFunction. This constructor is required for automatic creation of the
	 * function through the GUI. The parameters passed in specify the range of the X and Y
	 * axes at the time the function is created, and may be used to specify sensible initial
	 * values so that the function is visible on the plot
	 */
	public SineFunction(double xmin, double xmax, double ymin, double ymax)
	{
		offset = 0;
		amplitude = (ymax-ymin)/2;
		period = (xmax-xmin)/2;
	}
	/**
	 * Create the SineFunction.
	 * A constructor for use by users wishing to directly create the function.
	 */
	public SineFunction(double offset, double amplitude, double period)
	{
		this.offset = offset;
		this.amplitude = amplitude;
		this.period = period;
	}
	/**
	 * Calculates the value of the function at the given X coordinate.
	 */
	public double valueAt(double x)
	{
		return amplitude*Math.sin(2*Math.PI*(x-offset)/period);
	}
	/**
	 * get the Title for this function. Used for selecting the function in the GUI.
	 */
	public String getTitle()
	{
		return "Sine Curve";
	}
	/**
	 * Get the names of the function parameters, returned as a String array.
	 */
	public String[] getParameterNames()
	{
		return parameters;
	}
	/**
	 * Get the current values for the function parameters, as a double array.
	 * The parameters must be returned in the same order as for the getParameterNames()
	 * function.
	 */
	public double[] getParameterValues()
	{
		return new double[]{ offset, amplitude, period };
	}
	/**
	 * Called to set the value of a parameter.
	 * The index passed in is the index of the function in the getParameterNames()
	 * method.
	 */
	public void setParameter(int index, double value)
	{
		if      (index == 0) offset = value;
		else if (index == 1) amplitude = value;
		else if (index == 2) period = value;
		else throw new IllegalArgumentException("index="+index);
		
		clearFit(); // Invalidate any fits using this function
		setChanged(); // Tell the GUI the function has changed (so it can be redrawn)
	}
	// Methods for Fittable1DFunction
	/**
	 * Caculate the value of the function at X, if the parameters were set to p.
	 * This function should NOT modify the actual values of the parameters
	 */
	public double valueAt(double x, double[] p)
	{
		return p[1]*Math.sin(2*Math.PI*(x-p[0])/p[2]);
	}
	/**
	 * Called at the end of a fit, set the parameters to the given values.
	 */
	public void setFit(Fitter fit, double[] p)
	{
		offset = p[0];
		amplitude = p[1];
		period = p[2];

		setFit(fit); // Mark the function as being associated with the fit
		setChanged(); // Tell the GUI the function has changed (so it can be redrawn)
	}
	/**
	 * This method is only needed if the function implements HasHandles. It allows the
	 * user to adjust the function parameters by dragging on handles on the function itself.
	 * This is not so easy to implement in general, so should only be attempted by advanced
	 * users! The values passed in are the current ranges on the X and Y axes, so that 
	 * appropriate initial positions can be chosen for the handles.
	 */
    public Handle[] getHandles(double xLow, double xHigh, double yLow, double yHigh)
	{
		Handle[] result = new Handle[2];
		// We choose the number of cycles so that the minimu will be in the visible region
		// of the plot.
		final int cycles = (int) Math.floor(xLow-offset/period)+1;
		// handle at first minimum
		// used to adjust offset		
		result[0] = new Handle()
		{
			public void moveTo(double x, double y)
			{
				offset = x - cycles * period;
				clearFit();
				setChanged();
			}
			public double getX()
			{
				return cycles*period + offset;
			}
			public double getY()
			{
				return 0;
			}
		};
		// handle at first maximum
		// used to adjust offset and period
		result[1] = new Handle()
		{
			public void moveTo(double x, double y)
			{
				double old = cycles*period + offset; 
				amplitude = y;
				period = 4*(x - old);
				offset = old - cycles*period; 
				clearFit();
				setChanged();
			}
			public double getY()
			{
				return amplitude;
			}
			public double getX()
			{
				return cycles * period + offset + period/4;
			}
		};
		return result;
	}
}