#pragma once
#include <iostream>
#include <math.h>
#include <assert.h>


//enum of interpolator to create the right one
enum interpolatorType{
    linear,
	inQuad,
	outQuad,
	inOutQuad,
	inSine,
	inCube,
	inExpo,
};



class IInterpolator{
protected:
		IInterpolator(){};
public:

	virtual ~IInterpolator(){};
};


template <class T>
class interpolator : public IInterpolator{
protected:
	T &interpolant;
public:
	interpolator(T &_interpolant) : 
	  interpolant(_interpolant){};

	virtual ~interpolator(){};

	virtual void Interpolate() = 0;
	virtual bool Done() = 0;
};


template <class T>
class nullInterpolator : public interpolator<T>{
private:
	T value;
public:
	nullInterpolator(T &_interpolant, T _value) : interpolator(_interpolant), value(_value){};
	void Interpolate(){
		interpolant = value;
	}

	bool Done(){	return true; }

	~nullInterpolator(){};
};


//any class that requires this kind of structure can use IeasingInterpolator as the base class 
//to simplify life :P most easing interpolators have the same structure
template <class T, class stepType = double>
class IeasingInterpolator : public interpolator<T>{
protected:
	stepType currentTime;
	const stepType deltaTime;
	const stepType totalTime;
	T begin, delta;
	const  double pi;

	virtual void _Interpolate() = 0;
public:
	IeasingInterpolator(T &_interpolant, T begin, T end, stepType _totalTime, stepType _deltaTime = 1)
		: interpolator(_interpolant), pi(3.14159265), totalTime(_totalTime), deltaTime(_deltaTime){
	
		
		this->currentTime = 0;
		this->begin = begin;
		this->delta = end - begin;
		this->interpolant = begin;
	};

	void Interpolate(){
		currentTime += deltaTime;
		currentTime = ::std::max(::std::min(currentTime, totalTime), (stepType)0.0);
		
		_Interpolate();
	};

	virtual bool Done(){ 
		int x = 0;
		return currentTime >=	totalTime;
	}

	virtual ~IeasingInterpolator(){};
};



template <class T, class stepType = double>
class linearInterpolator : public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		interpolant = ((delta * currentTime) / totalTime) + begin;	
	}

public:
	linearInterpolator(T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1)
		:  IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	
	~linearInterpolator(){};
};



template <class T, class stepType = double>
class inQuadInterpolator : public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration
		//t /= d; return c*t*t + b;
		T newTime = currentTime / totalTime;
		interpolant = (delta * newTime * newTime) + begin;
		
	}
public:
	inQuadInterpolator(T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~inQuadInterpolator(){};
	
};



template <class T, class stepType = double>
class outQuadInterpolator : public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration
		//-c*t*t/(d*d) + 2*c*t/d + b;
		interpolant = (-1 * delta * currentTime * currentTime / (totalTime * totalTime)) + 
			((2 * delta * currentTime) / totalTime) + begin ;
		
	}

public:
	outQuadInterpolator (T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~outQuadInterpolator(){};
};


template <class T, class stepType = double>
class inOutQuadInterpolator : public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration

		/*
		if (t < d/2) return 2*c*t*t/(d*d) + b;
		var ts = t - d/2;
		return -2*c*ts*ts/(d*d) + 2*c*ts/d + c/2 + b;
		*/

		if(currentTime < (totalTime / 2.0)){
			interpolant = (2 * delta * currentTime * currentTime /(totalTime * totalTime)) + begin;
		}else{
			stepType ts = currentTime - (totalTime / 2.0);

			interpolant = (-2 * delta * ts * ts / (totalTime * totalTime)) + 
				(2 * delta * (ts / totalTime)) + begin;
		
		}
	}
public:
	inOutQuadInterpolator (T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~inOutQuadInterpolator(){};
};



template <class T, class stepType = double>
class inSineInterpolator: public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration

		/*
		return -c * Math.cos(t/d * Math.PI/2) + c + b;
		*/
		interpolant = (-delta * cos( (currentTime / totalTime) * (pi / 2.0))) + delta + begin; 
	}
public:
	inSineInterpolator(T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~inSineInterpolator(){};
};

template <class T, class stepType = double>
class inCubeInterpolator: public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration

		/*
		t /= d;
			return c*t*t*t + b;
		*/
		T newT = currentTime / totalTime;

		interpolant = delta * pow(newT, 3) + begin;
		//interpolant = (delta * cos( (currentTime / totalTime) * (pi / 2.0))) + delta + begin; 
	}
public:
	inCubeInterpolator(T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~inCubeInterpolator(){};
};


template <class T, class stepType = double>
class inExpoInterpolator: public IeasingInterpolator<T, stepType>{
protected:
	void _Interpolate(){
		// t: current time, b: beginning value, c: change in value, d: duration

		/*
		return c * Math.pow( 2, 10 * (t/d - 1) ) + b;
		*/
		interpolant = delta * pow(2, 10 * (currentTime / totalTime - 1)) + begin;
		//interpolant = (delta * cos( (currentTime / totalTime) * (pi / 2.0))) + delta + begin; 
	}
public:
	inExpoInterpolator(T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = 1) : 
	IeasingInterpolator(_interpolant, begin, end, totalTime, deltaTime){};

	~inExpoInterpolator(){};
};


//helper functions--------------------------------------------------------------------------------------

template<typename T, typename stepType>
IeasingInterpolator<T, stepType>* createEasingInterpolator(interpolatorType type, 
	T &_interpolant, T begin, T end, stepType totalTime, stepType deltaTime = (double)1.0){

		switch(type)
		{
		case linear:
			return new linearInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;

		case inQuad:
			return new inQuadInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;

		case outQuad:
			return new outQuadInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;

		case inOutQuad:
			return new inOutQuadInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;
		case inSine:
			return new inSineInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;

		case inCube:
			return new inCubeInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;
		case inExpo:
			return new inExpoInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
			break;
		default:
			assert(false && "wrong interpolant type given to CreateInterpolator");
			return new linearInterpolator<T, stepType>(_interpolant, begin, end, totalTime, deltaTime);
		}
}