#include "bezier.hpp"

/* Bezier */
/* ====== */
Point3d Bezier::eval(double t) const {
	std::vector<Point3d> tmp{this->bezier};
	for(int j = tmp.size() -1; j >0 ; --j)
		for(int i = 0; i < j ; ++i) 
			tmp[i] = lerp(tmp[i], tmp[i+1], t);
	return tmp[0];
}

// Reduces a bezier to 4 control points (used to get the tangent correctly)
std::vector<Point3d> Bezier::reduce(double t) const {
	std::vector<Point3d> res{this->bezier};
	for(int j = res.size() -1; j > 4; --j)
		for(int i = 0; i < j ; ++i)
			res[i] = lerp(res[i], res[i+1], t);
	return res;
}

// Source - https://stackoverflow.com/31317254
// Posted by Fattie, modified by community. See post 'Timeline' for change history
// Retrieved 2026-04-07, License - CC BY-SA 4.0
Point3d Bezier::getTan(double t) const {
	std::vector<Point3d> redBez = this->reduce(t);
	Point3d c1 = (redBez[3] - (redBez[2] * 3.0) + (redBez[1] * 3.0) - redBez[0]);
	Point3d c2 = ((redBez[2] * 3.0) - (redBez[1] * 6.0) + (redBez[0] * 3.0));
	Point3d c3 = ((redBez[1] * 3.0) - (redBez[0] * 3.0));

	return normalize((c1*3.0*t*t) + (c2 * 2.0 *t) + c3);
}

void Bezier::affiche(void){
	for(auto it = bezier.begin() ; it != bezier.end() ; ++it)
		it->affiche();
}

void Bezier::set(unsigned int i, const Point3d& val){
	this->bezier[i] = val;
}

Bezier lerp(const Bezier& b1, const Bezier& b2, float t) {
	Bezier result;
	for(unsigned int i = 0 ; i < b1.bezier.size() ; ++i){
		result.bezier.push_back(lerp(b1.bezier[i], b2.bezier[i], t));
	}
	result.time = lerp(b1.time, b2.time, t);
	return result;
}

/* BAnim */
/* ===== */
// Constructeurs
BAnim::BAnim(void):totalTime{0}, evalFunc{baseEval} {};
BAnim::BAnim(std::vector<Bezier> b):bezs{b}, evalFunc{baseEval} {
	totalTime = 0.0f;
	for(auto it = b.begin(); it != b.end() ; ++it)
		totalTime += it->time;
}
BAnim::BAnim(float(f)(float,float)):totalTime{0}, evalFunc{f} {};
BAnim::BAnim(std::vector<Bezier> b, float(f)(float,float)): bezs{b}, evalFunc{f} {
	totalTime = 0.0f;
	for(auto it = b.begin() ; it != b.end() ; ++it)
		totalTime += it->time;
}

void BAnim::push_back(Bezier& b){
	bezs.push_back(b);
	totalTime += b.time;
}


float BAnim::baseEval(float t, float totalT){
	return t/totalT;
}

Bezier& BAnim::getBezier(float t){
	static Bezier defaultReturn = {{{0.0,0.0,0.0}},0.0};
	if(totalTime <= 0.0f) return defaultReturn;
	while(t > totalTime) { t -= totalTime;} // modulo
	for(auto it = bezs.begin(); it != bezs.end() ; ++it){
		if(t > it->time) {
			t -= it->time;
			continue;
		}

		return *it;
	}
	return defaultReturn;
}

Point3d BAnim::evalAnim(float t){
	if(totalTime <= 0.0f) return Point3d{0.0f, 0.0f, 0.0f};
	while(t > totalTime) { t -= totalTime;} // modulo
	for(auto it = bezs.begin(); it != bezs.end() ; ++it){
		if(t > it->time) {
			t -= it->time;
			continue;
		}

		return it->eval(evalFunc(t, it->time));
	}
	return Point3d{0.0f,0.0f,0.0f};
}


Point3d BAnim::getAnimTan(float t){
	if(totalTime <= 0.0f) return Point3d{0.0f, 0.0f, 0.0f};
	while(t > totalTime) { t -= totalTime;} // modulo
	for(auto it = bezs.begin(); it != bezs.end() ; ++it){
		if(t > it->time) {
			t -= it->time;
			continue;
		}

		return it->getTan(evalFunc(t, it->time));
	}
	return Point3d{0.0f,0.0f,0.0f};
}

float BAnim::reduceToTime(float t){
	if(totalTime <= 0.0f) return 0.0f;
	while(t > totalTime) {t -= totalTime;}
	for(auto it = bezs.begin(); it != bezs.end() ; ++it){
		if(t <= it->time){
			return t;
		}
		t -= it->time;
	}
	return t;
}
