/* easing 1.0 by renato messina. Implemented functions: 
Linear, 
easeInBack, 
easeInBounce, 
easeInCircular, 
easeInCubic, 
easeInElastic, 
easeInExponential, 
easeInOutBack, 
easeInOutBounce, 
easeInOutCircular, 
easeInOutCubic, 
easeInOutElastic, 
easeInOutExponential, 
easeInOutQuadratic, 
easeInOutQuartic, 
easeInOutQuintic, 
easeInOutSine, 
easeInQuadratic, 
easeInQuartic, 
easeInQuintic, 
easeInSine, 
easeOutBack, 
easeOutBounce, 
easeOutCircular, 
easeOutCubic, 
easeOutElastic, 
easeOutExponential, 
easeOutQuadratic, 
easeOutQuartic, 
easeOutQuintic, 
easeOutSine 
*/

import com.cycling74.max.*;
import java.lang.reflect.*;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class easing extends MaxObject{

	private static final String[] INLET_ASSIST = new String[]{
		"inlet 1 help"
	};
	private static final String[] OUTLET_ASSIST = new String[]{
		"outlet 1 help"
	};

	MaxClock nowClock;
	float begin;
	float change;
	float time;
	float duration=0;
	float result;

	float target = 0;
	float delta = 0;

	float outNow = 0;
	float diff = 0;
	float running=0;
	float runningNorm=0;

	double eased=0;

	String nomeFunzione = "Linear"; // default curve

	String funClass = "Curves";
	Class c = Class.forName(funClass);
	Object obj = c.newInstance();
	Method method;

	String alt=("stop");

	int stopResult = 0;

	public easing(Atom[] args)throws Exception
	{
		declareInlets(new int[]{DataTypes.ALL,DataTypes.ALL});
		declareOutlets(new int[]{DataTypes.ALL, DataTypes.ALL, DataTypes.ALL});

		setInletAssist(new String[] { "Destination Value", "Ramp Time"});
		setOutletAssist(new String[] { "Output Curve", "Normalized Output (0. to 1.)", "Bang When Curve Reaches Destination"}); // 

		createInfoOutlet(false);

		nowmetro();
		nowStop();	

		try {
		method = obj.getClass().getMethod(nomeFunzione, double.class);
		}
	    catch (Throwable e) {
        System.err.println(e);
	    }
		post("easing 1.0 by renato messina. Implemented functions: Linear, easeInBack, easeInBounce, easeInCircular, easeInCubic, easeInElastic, easeInExponential, easeInOutBack, easeInOutBounce, easeInOutCircular, easeInOutCubic, easeInOutElastic, easeInOutExponential, easeInOutQuadratic, easeInOutQuartic, easeInOutQuintic, easeInOutSine, easeInQuadratic, easeInQuartic, easeInQuintic, easeInSine, easeOutBack, easeOutBounce, easeOutCircular, easeOutCubic, easeOutElastic, easeOutExponential, easeOutQuadratic, easeOutQuartic, easeOutQuintic, easeOutSine");

	}

	public void bang()
	{		
		stopResult = 1;
		time=0;
		result--;
		begin = result;
		change  = result+1;
		nowBang(); //<-----------START 
		readOutput();
	}

    public void inlet (int i)
	{
	stopResult=0;
	int inlet_no;
	inlet_no = getInlet();

		if(inlet_no==0 & change!=i){
		time=0;
		begin = result;
		change = i;
		nowBang(); //<-----------START 
		}
		if(inlet_no==1 & i>=0){
		duration = i;
		}
	readOutput(); // pick the current output
	}

	public void inlet(float f)
	{
	stopResult=0;
	int inlet_no;
	inlet_no = getInlet();

		if(inlet_no==0 & change!=f){
		time=0;
		begin = result;
		change = f;
		nowBang();
		}
		if(inlet_no==1 & f>=0){
		duration = f;
		}
	readOutput();
	}

    //////////////////////////////////////////////////////
	public void readOutput(){
	outNow = result;      
	diff = change-outNow;
	}
	//////////////////////////////////////////////////////
	public void anything(String msg, Atom[] args){
		nomeFunzione = msg;
		try{
		updateCurve();
		}
		catch (Throwable e) {
	    System.err.println(e);
		}
	}

	public void list(Atom[] argsList){
	Atom atomA = argsList[0];
	Atom atomB = argsList[1];
	Atom atomC = argsList[2];

	if(argsList.length==4){
	 Atom atomD = argsList[3];
		if(atomD.isFloat()){
		result = atomD.getFloat();
		}
	}

		if(atomA.isFloat()){
		time=0;
		begin = result;
		change = atomA.getFloat();
		}
		if(atomA.isInt()){
		time=0;
		begin = result;
		change = atomA.getInt();
		}
		if(atomB.isInt()){
		duration = atomB.getInt();
		}
		if(atomB.isFloat()){
		duration = atomB.getFloat();
		}
		if(atomC.isString()){
		nomeFunzione = atomC.getString();
			try{
			updateCurve();
			}
			catch (Throwable e) {
		    System.err.println(e);
			}
		}

		stopResult=0;
		nowBang();
		readOutput();
	}

	public void stop(){
	nowStop();
	result=0;
	}	

	public void updateCurve()throws Exception {
		try {
		method = obj.getClass().getMethod(nomeFunzione, double.class);
		}
	    catch (Throwable e) {
        System.err.println(e);
	    }
	}
///	LINEAR INTERPOLATION START HERE (RIASSEMBLE THE MAX LINE OBJECT) /////////////////////

		public void nowmetro() {
		nowClock = new MaxClock(new Callback(this, "nowBang"));
		}
  		private void nowBang() {
		nowClock.delay(1.0);

		result = begin + (time/(((1/(change-begin)))*duration));//change-begin = target
		running = result-outNow;    //2
		runningNorm = running/diff; //3
	//interpolation 
				try {
				eased = ((Number)method.invoke(obj, runningNorm)).doubleValue();
			//	eased = (double)method.invoke(obj, runningNorm);
				}
			    catch (Throwable e) {
				System.err.println(e);
			    }
	//end interpolation

		outlet(1, eased);

				if(result==change){
				nowStop();//<-----------------STOP
				outletBang(2);
				outlet(1, 1);
				}
				//..to avoid overshhoot
				if( begin<change & result>change){
				nowStop();//<-----------------STOP 
				result=change;
				outletBang(2);
				outlet(1, 1);
				}
				if(begin>change & result<change){
				nowStop();//<-----------------STOP 
				result = change;
				outletBang(2);
				outlet(1, 1);
				}	

		time++;

		target=result;

		if(stopResult==0){
		outlet(0, result);
		}	

		}

		public void nowStart()	{
		nowClock.delay(1.0); //set the clock to execute immediately
		}

		public void nowStop(){
		nowClock.unset(); //stop the clock from executing
		}
}

/////////////////////////////////////////////////////
//()()()() ()()()() ()()()() ()()()() ()()()() ()()()
/////////////////////////////////////////////////////

class Curves {
    public void main (double val) throws Exception {
	}
	private static final double PI_TIMES_2 = Math.PI * 2.0d;
    private static final double PI_OVER_2 = Math.PI / 2.0d;

	//
	public double easeInBack(double value) {
	return value * value * ((1.70158d + 1.0d) * value - 1.70158d);
	}
	//
	public double easeInBounce (double value){
	return easeIn(value);
	}
	//
	public double easeInCircular (double value){
	return -1.0d * (Math.sqrt(1.0d - Math.pow(value, 2.0d)) - 1.0d);
	}
	//
	public double easeInCubic (double value){
	return Math.pow(value, 3.0d);
	}
	//
	public double easeInElastic (double value){
    double s = 0.3d / 4.0d;
    double q = value - 1.0d;
    return -1.0d * (Math.pow(2.0, 10.0d * q) * Math.sin((q - s) * (PI_TIMES_2) / 0.3d));
	}
	// 
	public double easeInExponential (double value){
	return Math.pow(2.0d, 10.0d * (value - 1.0d));
	}
	//
	public double easeInOutBack (double value){
	double v = 2.0d * value;
    double s = 1.70158d * 1.525d;
    if (v < 1.0d)
    {
     return 0.5d * (v * v * ((s + 1.0d) * v - s));
    }
    v -= 2.0d;
    return 0.5d * (v * v * ((s + 1.0d) * v + s) + 2.0d);
	}
	//
	public double easeInOutBounce (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5d * easeIn(v);
    }
    return 0.5d * easeOut(v - 1.0d) + 0.5d;
	}
	//
	public double easeInOutCircular (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return -0.5d * (Math.sqrt(1.0d - Math.pow(v, 2.0d)) - 1.0d);
    }
    v -= 2.0d;
    return 0.5d * (Math.sqrt(1.0d - Math.pow(v, 2.0d)) + 1.0d);
	}
	//
	public double easeInOutCubic (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5d * Math.pow(v, 3.0d);
    }
    v -= 2.0d;
    return 0.5d * (Math.pow(v, 3.0d) + 2.0d);
	}
	//
	public double easeInOutElastic (double value){
    double p = 0.3 * 1.5d;
    double s = p / 4.0d;
    double q = 2.0d * value;
    if (q < 1.0d)
    {
    q -= 1.0d;
    return -0.5d * (Math.pow(2.0d, 10.0d * q) * Math.sin((q - s) * (PI_TIMES_2) / p));
    }
    q -= 1.0d;
    return 0.5d * Math.pow(2.0, -10.0d * q) * Math.sin((q - s) * (PI_TIMES_2) / p) + 1.0d;
	}
	//
	public double easeInOutExponential (double value){
	double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5 * Math.pow(2.0d, 10.0d * (v - 1.0d));
    }
    v -= 1.0d;
    return 0.5d * (-1.0d * Math.pow(2.0d, -10.0d * v) + 2.0d);
	}
	//
	public double easeInOutQuadratic (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5d * Math.pow(v, 2.0d);
    }
    v -= 1.0d;
    return -0.5d * (v * (v - 2.0d) - 1.0d);
	}
	//
	public double easeInOutQuartic (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5d * Math.pow(v, 4.0d);
    }
    v -= 2.0d;
    return -0.5d * (Math.pow(v, 4.0d) - 2.0d);
	}
	//
	public double easeInOutQuintic (double value){
    double v = 2.0d * value;
    if (v < 1.0d)
    {
    return 0.5d * Math.pow(v, 5.0d);
    }
    v -= 2.0d;
    return 0.5d * (Math.pow(v, 5.0d) + 2.0d);
	}
	//
	public double easeInOutSine (double value){
	return -0.5d * (Math.cos(value * Math.PI) - 1.0d);
	}
	//
	public double easeInQuadratic (double value){
	return Math.pow(value, 2.0d);
	}
	//
	public double easeInQuartic (double value){
	return Math.pow(value, 4.0d);
	}
	//
	public double easeInQuintic (double value){
	return Math.pow(value, 5.0d);
	}
	//
	public double easeInSine (double value){
	return -1.0d * Math.cos(value * PI_OVER_2) + 1.0d;
	}
	//
	public double easeOutBack (double value){
    double v = value - 1.0d;
    return v * v * ((1.70158d + 1.0d) * v + 1.70158d) + 1.0d;
	}
	//
	public double easeOutBounce (double value){
	return easeOut(value);
	}
	//
	public double easeOutCircular (double value){
	return Math.sqrt(1.0d - Math.pow(value - 1.0d, 2.0));
	}
	//
	public double easeOutCubic (double value){
	return Math.pow(value - 1.0d, 3.0d) + 1.0d;
	}
	//
	public double easeOutElastic (double value){
    double s = 0.3d / 4.0d;
    return Math.pow(2.0, -10.0d * value) * Math.sin((value - s) * (PI_TIMES_2) / 0.3d) + 1.0d;
	}
	//
	public double easeOutExponential (double value){
    return -1.0d * Math.pow(2.0d, -10.0d * value) + 1.0d;
	}
	//
	public double easeOutQuadratic (double value){
    return -1.0d * value * (value - 2.0d);
	}
	//
	public double easeOutQuartic (double value){
    return -1.0d * (Math.pow(value - 1.0d, 4.0d) - 1.0d);
	}
	//
	public double easeOutQuintic (double value){
    return Math.pow(value - 1.0d, 5.0d) + 1.0d;
	}
	//
	public double easeOutSine (double value){
	return Math.sin(value * PI_OVER_2);
	}
	//
	public double Linear (double value){
	return value;
	}

	// easeIn
	public double easeIn (double value){
	return 1.0d - easeOut(1.0d - value);
	}
	// easeOut
	public double easeOut (double value){
	if (value < (1.0d / 2.75d))
        {
            return value * value * 7.5625d;
        }
        else if (value < (2.0d / 2.75d))
        {
            double v = value - (1.5d / 2.75d);
            return v * v * 7.5625d + 0.75d;
        }
        else if (value < (2.5d / 2.75d))
        {
            double v = value - (2.25d / 2.75d);
            return v * v * 7.5625d + 0.9375d;
        }
        double v = value - (2.625d / 2.75d);
        return v * v * 7.5625d + 0.984375d;
	}
}