/*
 * timing_manager.c
 *
 *  Created on: Oct 3, 2025
 *      Author: herli
 */
#include "timing_manager.h"
#include "toothed_wheel.h"
#include "tein_detection.h"
#include "injection.h"

#include "main.h"

volatile EdgeSample edgeBuf[TW_PERCYL_TEETH+1];

/*#define MT_BUF_SZ  16

typedef struct { float rpm; float acc; } MTSample;
static MTSample mtBuf[MT_BUF_SZ];
static uint8_t  mtHead = 0; // points to newest*/


float last_INJECTING_RPM = 0.0;
float last_non_Inj_RPM = 0.0;

float last_INJECTING_ALPHA = 0.0;

float last_accel = 0.0;

//static inline void MT_Push(float rpm_now, float sector_deg);
//static inline float rpm_acc_from_sector(float rpm_prev, float rpm_now, float sector_deg);
static inline float MT_AccelUpdate(float rpm_now);
static inline void MT_AccelReset();

static inline float PredictRPM(float rpm0, float accel_rpms, float dt_s);
static inline float TimeToAngleDeg(float rpm0, float accel_rpms, float dtheta_deg);
static inline float _rpm_from_dt(float deg, uint32_t dt_ticks);

static inline uint32_t ticks_diff(uint32_t newer, uint32_t older);
static inline uint32_t ticks_diff(uint32_t newer, uint32_t older)
{
    return (newer >= older) ? (newer - older) : (newer + (0xFFFFFFFFu - older) + 1u);
}

static inline uint8_t TM_GET_IN_BUFFER_ID(uint32_t C){
	uint8_t index = 0;
	for(uint8_t i = 1; i < TW_PERCYL_TEETH; i++){
		if(edgeBuf[i].ic < C && C < edgeBuf[i+1].ic){
			index = i;
			break;
		}
	}
	return index;
}

void UpdateEdgeBuffer(uint32_t tickNew, float rpm_est, uint8_t tooth, uint8_t isInj){
	if(tooth > TW_PERCYL_TEETH){
		return;
	}
	EdgeSample s;
	s.ic   	= tickNew;
	s.rpm   = rpm_est;
	s.isInj   = isInj;

	//uint32_t accel_Difference = tickNew >= edgeBuf[tooth].ic ? tickNew-edgeBuf[tooth].ic : tickNew-(edgeBuf[tooth].ic-0xffffffff);

	//float time = accel_Difference / refClock;
	float time = ticks_diff(tickNew, edgeBuf[tooth].ic) / refClock;

	//s.accel = USTODEG*(rpm_est - edgeBuf[tooth].rpm)/time; //in º/us^^2
	s.accel = (rpm_est - edgeBuf[tooth].rpm)/time; //in º/º^^2

	s.new   = 1;

	edgeBuf[tooth] = s;
}
void ClearEdgeBuffer(){
	EdgeSample s;
	s.ic   	= 0;
	s.rpm   = 0;
	s.accel = 0;
	s.isInj   = 0;
	s.new   = 0;

	for(int i = 0; i<TW_PERCYL_TEETH; i++){
		edgeBuf[i] = s;
	}
}
void MakeEdgeBufferOld(){
	for(int i = 0; i<TW_PERCYL_TEETH; i++){
		edgeBuf[i].new = 0;
	}
}
float TM_GET_RPM_FROM_NEAREST_TOOTH(uint32_t C_Ticks, uint8_t fw){
	uint8_t idx = TM_GET_IN_BUFFER_ID(C_Ticks);
	if(!idx){return 0;}
	return edgeBuf[idx + !fw].rpm;
}

float TM_TIME_FROM_NEAREST_TOOTH(uint32_t C_Ticks, uint8_t fw){
	uint8_t idx = TM_GET_IN_BUFFER_ID(C_Ticks);
	if(!idx){return 0;}
	idx += 1 - fw;

	uint32_t C_Difference = C_Ticks >= edgeBuf[idx].ic ? C_Ticks-edgeBuf[idx].ic : C_Ticks-(edgeBuf[idx].ic-0xffffffff);
	return C_Difference / 16;
}

static inline float _angle_from_dt(float rpm, uint32_t dt_ticks)
{
    // deg = 6 * rpm * seconds ; seconds = dt_ticks / refClock
    return 6.0f * rpm * ((float)dt_ticks / (float)refClock);
}
static inline float _rpm_from_dt(float deg, uint32_t dt_ticks)
{
    // deg = 6 * rpm * seconds ; seconds = dt_ticks / refClock
    return deg / (6.0f * ((float)dt_ticks / (float)refClock));

}

float TM_INTEGRATE_ANGLE_FROM_NEAREST_TOOTH(uint32_t C_Ticks, uint8_t fw)
{
    float int_angle = 0.0f;

    uint8_t idx = TM_GET_IN_BUFFER_ID(C_Ticks);
    if (!idx) return 0.0f;

    else if (fw == 1) {
        // from this tooth edge up to C_Ticks
        uint32_t dt = (uint32_t)(C_Ticks - edgeBuf[idx].ic);  // wrap-safe
        float    rpm = edgeBuf[idx].rpm;
        float    ddeg = _angle_from_dt(rpm, dt);

        // angle = tooth base + fraction forward in this tooth
        int_angle = (float)idx * TW_TOOTH_ALPHA + ddeg;
    } else {
        // from next tooth edge down to C_Ticks
        uint8_t  nx  = (uint8_t)(idx + 1);
        if (nx > TW_PERCYL_TEETH) return 0.0f;

        uint32_t dt  = (uint32_t)(edgeBuf[nx].ic - C_Ticks);  // wrap-safe
        float    rpm = edgeBuf[nx].rpm;                       // rpm of the tooth we are "in"
        float    ddeg = _angle_from_dt(rpm, dt);

        // angle = next tooth base minus the fraction we rewound
        int_angle = (float)nx * TW_TOOTH_ALPHA - ddeg;
    }

    return int_angle;
}

float INTEGRATE_ANGLE_FROM_REFERENCE(float time, float reference, uint8_t fw){
    float int_angle = 0.0f;
    float rem_time = time;

    uint8_t idx = reference / TW_TOOTH_ALPHA;
    float off_angle = reference - idx * TW_TOOTH_ALPHA;

    if (!idx) return 0.0f;

    /*else if (fw == 1) {
    	for(int i = idx; i < TW_TOOTH_ALPHA; i--){
			float dt_tooth = ticks_diff(edgeBuf[i].ic, edgeBuf[i-1].ic)/16;
    		if(i == nx){ //si es el primer valor (va a ser parcial)
                float alpha_dt = off_angle / TW_TOOTH_ALPHA * dt_tooth;  // wrap-safe
                if(alpha_dt > rem_time){
                	float diff = alpha_dt - rem_time;
                	int_angle += off_angle - TW_TOOTH_ALPHA * diff/dt_tooth;
					rem_time = 0;
                }else{
                	int_angle += off_angle;
                	rem_time -= alpha_dt;
                }
    		}else if(rem_time > dt_tooth){
            	int_angle += TW_TOOTH_ALPHA;
            	rem_time -= dt_tooth;
    		}else{
    			int_angle += TW_TOOTH_ALPHA * rem_time/dt_tooth;
    			rem_time = 0;
    		}

    		if(rem_time < 0.5f){
    			break;
    		}
    	}*/

    //} else {
        // from next tooth edge down to C_Ticks
        uint8_t  nx  = (uint8_t)(idx + 1);
        if (nx > TW_PERCYL_TEETH) return 0.0f;

    	for(int i = nx; i > 1; i--){
			float dt_tooth = ticks_diff(edgeBuf[i].ic, edgeBuf[i-1].ic)/16;
    		if(i == nx){ //si es el primer valor (va a ser parcial)
                float alpha_dt = off_angle / TW_TOOTH_ALPHA * dt_tooth;  // wrap-safe
                if(alpha_dt > rem_time){
                	float diff = alpha_dt - rem_time;
                	int_angle += off_angle - TW_TOOTH_ALPHA * diff/dt_tooth;
					rem_time = 0;
                }else{
                	int_angle += off_angle;
                	rem_time -= alpha_dt;
                }
    		}else if(rem_time > dt_tooth){
            	int_angle += TW_TOOTH_ALPHA;
            	rem_time -= dt_tooth;
    		}else{
    			int_angle += TW_TOOTH_ALPHA * rem_time/dt_tooth;
    			rem_time = 0;
    		}

    		if(rem_time < 0.5f){
    			break;
    		}
    	}

    //}

    return int_angle;
}
float TM_GET_REAL_BIP_ANGLE(uint32_t C_Ticks)
{
    float int_angle = 0.0f;

    uint8_t idx = TM_GET_IN_BUFFER_ID(C_Ticks);
    if (!idx) return 0.0f;

    uint8_t nx = (uint8_t)(idx + 1);
    if (nx > TW_PERCYL_TEETH) return 0.0f;

    uint32_t t0_ticks = edgeBuf[idx].ic;
    uint32_t t1_ticks = edgeBuf[nx].ic;
    uint32_t Dt_ticks = ticks_diff(t1_ticks, t0_ticks);

	float Dt = Dt_ticks/refClock;
	// Base angular velocities (deg/s)
	float w0 = edgeBuf[idx].rpm*6.0f;
	float w1 = edgeBuf[nx].rpm*6.0f;

	// Scale both w0,w1 so the *area* over the full tooth equals TW_TOOTH_ALPHA
	// Integral of linear ω over [0,Dt] is 0.5*(w0 + w1)*Dt
	float denom = 0.5f * (w0 + w1) * Dt;
	float s = (denom != 0.0f) ? (TW_TOOTH_ALPHA / denom) : 0.0f;
	float w0s = w0 * s;
	float w1s = w1 * s;

	float a_s = (w1s - w0s) / Dt;

	//if (fw == 1) {
		// Forward from t0 to C_Ticks
		uint32_t dt_ticks = ticks_diff(C_Ticks, t0_ticks);
		if (dt_ticks > Dt_ticks) dt_ticks = Dt_ticks; // clamp inside tooth
		float dt = (dt_ticks)/refClock;

		float theta_local = w0s * dt + 0.5f * a_s * dt * dt;  // deg
		int_angle = (float)idx * TW_TOOTH_ALPHA + theta_local;
	/*} else {
		// Backward from t1 down to C_Ticks
		uint32_t dt_ticks = u32_diff(t1_ticks, C_Ticks);
		if (dt_ticks > Dt_ticks) dt_ticks = Dt_ticks; // clamp
		float dt = TICKS_TO_SECONDS(dt_ticks);

		// Integrate backward: area from (t1 - dt) to t1 equals using end-velocity w1s
		// Symmetry gives: θ_local = w1s*dt - 0.5*a_s*dt^2
		float theta_local = w1s * dt - 0.5f * a_s * dt * dt;  // deg
		int_angle = (float)nx * TW_TOOTH_ALPHA - theta_local;
	}*/

	// Optional: keep in [0, total_per_cycle) if you need it
	return int_angle;
}

float TM_INTEGRATE_ANGLE_FROM_REFERENCE(uint32_t C_time, float refAngle, uint8_t fw){
	/*float int_angle = 0.0;
	uint8_t idx = TM_GET_IN_BUFFER_ID(C_Ticks);
	if(!idx){
		return int_angle;
	}
	idx = fw ? idx : idx + 1;
	if(idx > TW_PERCYL_TEETH){
		return int_angle;
	}*/

	float int_angle = refAngle;
	uint8_t id_ref = refAngle / TW_TOOTH_ALPHA;
	id_ref = fw ? id_ref : id_ref + 1;
	while(C_time){
		id_ref = int_angle / TW_TOOTH_ALPHA + !fw*1;
		int_angle += -1*!fw*edgeBuf[id_ref].rpm*USTODEG;

		C_time--;
	}

	/*if(fw){
		uint32_t C_Difference = C_Ticks >= edgeBuf[idx].ic ? C_Ticks-edgeBuf[idx].ic : C_Ticks-(edgeBuf[idx].ic-0xffffffff);
		int_angle = idx*TW_TOOTH_ALPHA + 6.0*edgeBuf[idx].rpm*C_Difference/refClock;
	}else{
		uint32_t C_Difference = edgeBuf[idx].ic >= C_Ticks ? edgeBuf[idx].ic-C_Ticks : edgeBuf[idx].ic-(C_Ticks-0xffffffff);
		int_angle = idx*TW_TOOTH_ALPHA - 6.0*edgeBuf[idx].rpm*C_Difference/refClock;
	}*/

	//int_angle = fw ? int_angle - refAngle : int_angle - refAngle;
	return int_angle;
}

float dalpha;
float TM_INTEGRATE_TIME_FROM_REFERENCE(float C_Angle, uint8_t teeth_f){
	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1

	if(idx < teeth_f){
		return 0.0f;
	}
	float C_Difference = C_Angle - idx*TW_TOOTH_ALPHA;


	int_time = C_Difference/(edgeBuf[idx+1].rpm*USTODEG);

	for(int i = idx; i > teeth_f; i--){
		if(edgeBuf[i].rpm < 20){
			break;
		}
		int_time += TW_TOOTH_ALPHA / (edgeBuf[i].rpm*USTODEG);
	}

	return int_time;
}
uint8_t accelbipenabled = 0;
float TM_INTEGRATE_TIME_BIP_PREDICTIVE(float C_Angle, uint8_t teeth_i){
	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	if(idx < teeth_i){
		return 0.0f;
	}
	float Remaining_Angle = C_Angle - idx*TW_TOOTH_ALPHA;

	uint8_t lastUpdatedTeeth = 0;
	for(int i = 1; i < idx + 1; i++){
		if(edgeBuf[i].new){
			lastUpdatedTeeth = i;
			break;
		}
	}
	if(!lastUpdatedTeeth || lastUpdatedTeeth > TW_PERCYL_TEETH){
		return TM_INTEGRATE_TIME_BIP(C_Angle, teeth_i);
	}
	float acc_factor_f = edgeBuf[lastUpdatedTeeth].rpm / edgeBuf[idx-1].rpm; //cuanto cambio desde la ultima
	float acc_factor_s = edgeBuf[idx+1].rpm / edgeBuf[idx-1].rpm; //cuanto se desacelera en la inyeccion

	acc_factor_f = accelbipenabled ? acc_factor_f : 1.0;
	acc_factor_s = accelbipenabled ? acc_factor_s : 1.0;

	float rpm = edgeBuf[lastUpdatedTeeth].rpm * acc_factor_s > MIN_RPM ? edgeBuf[lastUpdatedTeeth].rpm * acc_factor_s : TEETH_RPM;
	int_time = Remaining_Angle / (rpm*USTODEG);

	for(int i = teeth_i; i < idx; i++){
		if(edgeBuf[i+1].rpm < MIN_RPM){
			rpm = TEETH_RPM;
		}else{
			rpm = edgeBuf[i+1].isInj ? edgeBuf[lastUpdatedTeeth].rpm * acc_factor_s : edgeBuf[i+1].rpm * acc_factor_f;
		}
		int_time += TW_TOOTH_ALPHA / (rpm*USTODEG);
	}
	return int_time;
}
float correction_factor;
float bip_acc_K = 0;
float TM_INTEGRATE_TIME_BIP(float C_Angle, uint8_t teeth_i){
	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	if(idx < teeth_i){
		return 0.0f;
	}
	float Remaining_Angle = C_Angle - idx*TW_TOOTH_ALPHA;

	//float rpm = edgeBuf[idx+1].rpm > MIN_RPM ? edgeBuf[idx+1].rpm : TEETH_RPM;
	//int_time = Remaining_Angle / (rpm*USTODEG);
	int_time = Remaining_Angle/TW_TOOTH_ALPHA * ticks_diff(edgeBuf[idx+1].ic, edgeBuf[idx].ic)/16;

	for(int i = teeth_i; i < idx; i++){
		int_time += ticks_diff(edgeBuf[i+1].ic, edgeBuf[i].ic)/16;
	}

	/* acceleration factor */
	float dt_to_bip = TimeToAngleDeg(MT_RPM, last_accel, C_Angle);
	float rpm_at_bip = PredictRPM(MT_RPM, last_accel, dt_to_bip);

	correction_factor = fclamp(MT_RPM/rpm_at_bip,1,1.5);


	int_time *= 1 + (correction_factor-1)*bip_acc_K;

	return int_time;
}
/*float TM_INTEGRATE_TIME_BIP(float C_Angle, uint8_t teeth_i){
	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	if(idx < teeth_i){
		return 0.0f;
	}
	float Remaining_Angle = C_Angle - idx*TW_TOOTH_ALPHA;

	float rpm = edgeBuf[idx+1].rpm > MIN_RPM ? edgeBuf[idx+1].rpm : TEETH_RPM;
	int_time = Remaining_Angle / (rpm*USTODEG);

	for(int i = teeth_i; i < idx; i++){
		rpm = edgeBuf[i+1].rpm > MIN_RPM ? edgeBuf[i+1].rpm : TEETH_RPM;
		int_time += TW_TOOTH_ALPHA / (rpm*USTODEG);
	}

	return int_time;
}*/

uint8_t TM_GET_TRIGGER_TEETH_FROM_REFERENCE_AND_TIME(float C_Angle, float time){
	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1

	if(idx > TW_PERCYL_TEETH){
		return 0;
	}
	float C_Difference = C_Angle - idx*TW_TOOTH_ALPHA;


	int_time = C_Difference/(edgeBuf[idx+1].rpm*USTODEG);

	uint8_t idx_trigger = 0;
	for(int i = idx; i > 0; i--){
		if(edgeBuf[i].rpm < 20){
			break;
		}
		int_time += TW_TOOTH_ALPHA / (edgeBuf[i].rpm*USTODEG);
		if(int_time > time){
			return i; //seria -1 pero luego le restamos, asi puede encontrar el 1 tmb;
			break;
		}
	}
	return 0;
}
uint8_t dtein_mode = 13;
float dtein_rpm = 0;
float extra = 0;
float extra2 = 00;

extern float eq_rpm;
float rpmdif = 100;
int negative = 0;
float TM_GET_PHIAD_dTEIN(float EOI_Angle, float MT_RPM){
	float d_alpha = 0.0f;

	uint8_t idx = EOI_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	float Remaining_Angle = EOI_Angle - idx*TW_TOOTH_ALPHA;


	float rpm0   = MT_RPM;   // your base RPM
	float accel  = last_accel;           // RPM/s from your filter


	if(accel > -200 && accel < 200){
		// A) Adjust a time computed from an angle (e.g., 31° to EOI)
		float dt_to_eoi = TimeToAngleDeg(rpm0, accel, EOI_Angle);
		// B) Predict the RPM at that firing time, if you need it
		float rpm_at_eoi = PredictRPM(rpm0, accel, dt_to_eoi);

		//d_alpha = - (TEIN_NOMINAL+!T_ein_status*PH_PEAK_DEF)*USTODEG*rpm_at_eoi;
		d_alpha = - (TEIN_NOMINAL+!T_ein_status*PH_PEAK_DEF)*USTODEG*MT_RPM;

	}else{
		d_alpha = - (TEIN_NOMINAL+!T_ein_status*PH_PEAK_DEF)*USTODEG*MT_RPM;
	}
	//no compensations
	float rpm = 0;

    float rpm_inj_sum = 0.0f;
    uint8_t n_inj = 0;

	switch(dtein_mode){ //something between 1 and 2, but non are working properly
		case 0:
			rpm = MT_RPM;
			break;
		case 1:
			rpm = edgeBuf[idx].rpm;
			break;
		case 2:
			rpm = TEETH_RPM;
			break;
		case 3:
		    // --- 2) Build avg RPM pools (slowed vs free) ---
		    for (int i = 0; i < TW_PERCYL_TEETH; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        if (edgeBuf[i].isInj) { rpm_inj_sum += r; n_inj++; }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 4:
		    for (int i = 1; i < TW_PERCYL_TEETH+1; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        rpm_inj_sum += r;
		        n_inj++;
		    }
		    //rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
		    rpm  = n_inj ? ((rpm_inj_sum + extra * edgeBuf[TW_PERCYL_TEETH].rpm)  / (n_inj + extra))  : TEETH_RPM;
			break;
		case 5:
		    for (int i = 1; i < TW_PERCYL_TEETH; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        rpm_inj_sum += r; n_inj++;
		        if (edgeBuf[i].isInj) { break; }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 6:
		    for (int i = 1; i < TW_PERCYL_TEETH; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        rpm_inj_sum += r; n_inj++;
		        if (edgeBuf[i].isInj && !edgeBuf[i+1].isInj) { break; }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 7:
		    // --- 2) Build avg RPM pools (slowed vs free) ---
		    uint8_t flag = 0;
		    for (int i = 1; i < TW_PERCYL_TEETH; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        if (edgeBuf[i].isInj) { flag = 1; }
		        if(flag){
		        	rpm_inj_sum += r; n_inj++;
		        }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 8:
		    // --- 2) Build avg extended RPM pools (slowed vs free) ---
		    for (int i = 0; i < TW_PERCYL_TEETH; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        if (edgeBuf[i].isInj){
		        	rpm_inj_sum += r; n_inj++;
		        }else if(edgeBuf[i-1].isInj) { rpm_inj_sum += r; n_inj++; }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 9:
		    for (int i = 1; i < idx + 2; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        if (edgeBuf[i].isInj){
		        	rpm_inj_sum += r; n_inj++;
		        }else if(edgeBuf[i-1].isInj) { rpm_inj_sum += r; n_inj++; }
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
			break;
		case 10:
			uint32_t ticks_eoi = edgeBuf[idx].ic + Remaining_Angle/TW_TOOTH_ALPHA * ticks_diff(edgeBuf[idx+1].ic, edgeBuf[idx].ic) - TEIN_NOMINAL*16;
			uint8_t id_end = TM_GET_IN_BUFFER_ID(ticks_eoi);
			if(id_end){
				return TM_INTEGRATE_ANGLE_FROM_NEAREST_TOOTH(ticks_eoi, 0)- EOI_Angle;
				//float angle = 1.0f * TW_TOOTH_ALPHA * ticks_diff(ticks_eoi, edgeBuf[id_end].ic) / ticks_diff(edgeBuf[id_end+1].ic, edgeBuf[id_end].ic);
				//return -(angle + TW_TOOTH_ALPHA*(idx - id_end - 1) + Remaining_Angle);
			}
			//uint32_t ic_nom_eoi = IC_EOI + TEIN_NOMINAL*16;
			break;
		case 11:
		    for (int i = 1; i < TW_PERCYL_TEETH+1; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        rpm_inj_sum += r;
		        n_inj++;
		    }
		    rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;

			float dt_to_eoi = TimeToAngleDeg(MT_RPM, last_accel, EOI_Angle); //tendria q ser mt rpm no las last injecting rpm.
			// B) Predict the RPM at that firing time, if you need it
			float rpm_next = PredictRPM(rpm0, last_accel, dt_to_eoi);

			rpm *= rpm_next / MT_RPM;
			break;
		case 12:
			// A) Adjust a time computed from an angle (e.g., 31° to EOI)
			float dt_to_eoi2 = TimeToAngleDeg(MT_RPM, accel, 360/CYLINDERS);//EOI_Angle
			// B) Predict the RPM at that firing time, if you need it
			float rpm_at_eoi = fclamp(PredictRPM(MT_RPM, last_accel, dt_to_eoi2),MT_RPM-rpmdif,MT_RPM+rpmdif);
			//if((rpm_at_eoi - MT_RPM)*last_accel > 0){
				rpm = rpm_at_eoi;
			break;

			//}else{
			//	rpm = MT_RPM;
			//}
		case 13:
		    for (int i = 1; i < TW_PERCYL_TEETH+1; i++) {
		        float r = edgeBuf[i].rpm;
		        if (r < 20.0f) continue;
		        rpm_inj_sum += r;
		        n_inj++;
		    }
		    //rpm  = n_inj ? (rpm_inj_sum  / n_inj)  : TEETH_RPM;
		    rpm  = n_inj ? ((rpm_inj_sum + extra * edgeBuf[TW_PERCYL_TEETH].rpm)  / (n_inj + extra))  : TEETH_RPM;
			// A) Adjust a time computed from an angle (e.g., 31° to EOI)
			float dt_to_eoi3 = TimeToAngleDeg(rpm, accel, extra2);//EOI_Angle //360/CYLINDERS
			if (negative){ dt_to_eoi3 *= -1;}
			// B) Predict the RPM at that firing time, if you need it
			float rpm_at_eoi2 = fclamp(PredictRPM(rpm, last_accel, dt_to_eoi3),rpm-rpmdif,rpm+rpmdif);
			//if((rpm_at_eoi - MT_RPM)*last_accel > 0){
			rpm = rpm_at_eoi2;
			//}else{
			//	rpm = MT_RPM;
			//}
			break;

		default:
			break;
	}
	dtein_rpm = rpm;
	d_alpha = - (TEIN_NOMINAL)*USTODEG*dtein_rpm; //bien, usa las slowed -!T_ein_status*PH_PEAK_DEF*USTODEG*TEETH_RPM
	if(!T_ein_status){
		//d_alpha -= INTEGRATE_ANGLE_FROM_REFERENCE(PH_PEAK_DEF, 	EOI_Angle + d_alpha, 0);
		d_alpha -= PH_PEAK_DEF*USTODEG*dtein_rpm ;

	}



	return d_alpha;
}

uint8_t current_inj_rpm_available = 0;
float inj_accel;

//UNUSED
float TM_INTEGRATE_TIME_FROM_REFERENCE_Forward(float C_Angle, uint8_t teeth_i, float InitInj){

	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	if(idx < teeth_i){
		return 0.0f;
	}
	float Remaining_Angle = C_Angle - idx*TW_TOOTH_ALPHA;

	/* calcular la current accel con MT_RPM hasta teeth rpm (solo vale antes de empezar a inyectar)  y comparar signo*/

    /*uint32_t dt = (uint32_t)(edgeBuf[trigger_teeth].ic - edgeBuf[1].ic);  // wrap-safe

	float rpm_now = _rpm_from_dt(trigger_teeth * TW_TOOTH_ALPHA, dt);

	float seconds = dt/refClock;
    float acc_raw = (rpm_now - edgeBuf[1].rpm) / (seconds);*/

	/*if(acc_raw * accel < 0){
		accel = acc_raw;
	}*/
	float calc_rpm = 0;
	/*if(!current_inj_rpm_available || current_inj_rpm_available < teeth_i){
		for(int i = 1; i < TW_PERCYL_TEETH; i++){
			if(!edgeBuf[i].isInj){
				continue;
			}
			if(edgeBuf[i].rpm < 20 || !edgeBuf[i].new){
				break;
			}
			current_inj_rpm_available = i;
			calc_rpm = edgeBuf[i].rpm;
		}
		if(!current_inj_rpm_available){
			float rpm0   = last_INJECTING_RPM;   // your base RPM
			float accel  = last_accel;           // RPM/s from your filter

			// A) Adjust a time computed from an angle (e.g., 31° to EOI)
			float dt_to_eoi = TimeToAngleDeg(MT_RPM, inj_accel, C_Angle); //tendria q ser mt rpm no las last injecting rpm.
			// con last inj rpm, que es mas lento, da dt mayor, y tambien unas rpm menor y dt todavia mayor...

			// B) Predict the RPM at that firing time, if you need it
			float rpm_at_eoi = PredictRPM(rpm0, inj_accel, dt_to_eoi);

			calc_rpm = last_INJECTING_RPM;
		}
	}else{
		calc_rpm = edgeBuf[current_inj_rpm_available].rpm;
	}*/
	calc_rpm = TEETH_RPM;

	int_time += Remaining_Angle / (calc_rpm*USTODEG);
	//int_time += Remaining_Angle / (edgeBuf[idx].rpm*USTODEG);

	for(int i = teeth_i; i < idx; i++){
		if(edgeBuf[i].rpm < 20){
			break;
		}
		if(edgeBuf[i].isInj){

		}
		int_time += TW_TOOTH_ALPHA / (calc_rpm*USTODEG);
		//int_time += TW_TOOTH_ALPHA / (edgeBuf[i].rpm*USTODEG);

	}
	return int_time;
}
extern float target_eoi;
extern float real_eoi;

float TM_INTEGRATE_TIME_TO_EOI(float C_Angle, uint8_t teeth_i){

	float int_time = 0.0;

	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1
	if(idx < teeth_i){
		return 0.0f;
	}
	float Remaining_Angle = C_Angle - idx*TW_TOOTH_ALPHA;


	/*uint8_t id = 0;
    for (int i = 1; i < TW_PERCYL_TEETH; i++) {
        if (edgeBuf[i].rpm < 20.0f) break;
        if (!edgeBuf[i].isInj) continue;
        if(!edgeBuf[i+1].isInj){
        	id = i;
        }
    }
    if(!id){id = teeth_i;}
	uint32_t t_diff_fast = ticks_diff(edgeBuf[2].ic, edgeBuf[1].ic);
	uint32_t t_diff_slow = ticks_diff(edgeBuf[id].ic, edgeBuf[id-1].ic);*/

	uint32_t t_diff_fast = ticks_diff(edgeBuf[idx+2].ic, edgeBuf[idx+1].ic);
	int_time = Remaining_Angle/TW_TOOTH_ALPHA * ticks_diff(edgeBuf[idx+1].ic, edgeBuf[idx].ic)/16;
	//int_time = Remaining_Angle/TW_TOOTH_ALPHA * t_diff_fast/16;

	for(int i = teeth_i; i < idx; i++){
		int_time += ticks_diff(edgeBuf[i+1].ic, edgeBuf[i].ic)/16;
		//int_time += t_diff_fast/16;
	}
	/*float dt_to_eoi = TimeToAngleDeg(MT_RPM, last_accel, C_Angle); //tendria q ser mt rpm no las last injecting rpm.
	// B) Predict the RPM at that firing time, if you need it
	float rpm_next = PredictRPM(rpm0, last_accel, dt_to_eoi);

	int_time *= rpm_next / MT_RPM;*/

	return int_time;
}
float lpf_rpm = 1;
void TM_UPDATE_INJECTION_RPM_AND_ALPHA(float EOI_angle, float BIP_angle, uint32_t IC_EOI, uint32_t IC_BIP){
    uint8_t idx2 = BIP_angle / TW_TOOTH_ALPHA + 1; // target tooth index

    float eq_rpm = 0.0f;
    uint8_t count = 0;

    for (int i = idx2; i < TW_PERCYL_TEETH; i++) {
        if (edgeBuf[i].rpm < 20.0f) break;
        if (!edgeBuf[i].isInj) continue;
        eq_rpm += edgeBuf[i].rpm;
        count++;
        if (!edgeBuf[i+1].isInj) {
        	eq_rpm += edgeBuf[i+1].rpm;
            count++;
        }
    }
    if (count) {
        eq_rpm /= count; // averaged slowed rpm
    } else {
        eq_rpm = edgeBuf[idx2].rpm;
    }
    //last_INJECTING_RPM = eq_rpm;

    uint8_t idx = TM_GET_IN_BUFFER_ID(IC_BIP);

	uint32_t inj_Difference = IC_EOI >= IC_BIP ? IC_EOI-IC_BIP : IC_EOI-(IC_BIP-0xffffffff);
	float alpha = fclamp(EOI_angle - BIP_angle, 0, 90);

	eq_rpm = fclamp(60.0 * refClock/inj_Difference * alpha / 360, MIN_RPM, 4000);
	//Otra forma de calcular las rpm
	//seria calcular donde esta bip real y eoi real
	//(en angulos, que ya lo tenemos, sacar el periodo en angulo y dividir entre el periodo en tiempo...

	//por ahora calculamos rpm promedio y ya

    uint8_t idx_eoi = TM_GET_IN_BUFFER_ID(IC_EOI);

    last_INJECTING_RPM = edgeBuf[idx_eoi].rpm;
	//last_INJECTING_RPM = alpha < 0.2 ? edgeBuf[idx-1].rpm : eq_rpm; voy a usarla para la aceleracion

	//last_INJECTING_RPM += lpf_rpm*(edgeBuf[idx_eoi].rpm - last_INJECTING_RPM);
	/*if(last_INJECTING_RPM < 50){
		last_INJECTING_RPM++;
	}*/
	last_INJECTING_ALPHA = alpha;

	last_non_Inj_RPM = edgeBuf[idx-2].rpm;

	current_inj_rpm_available = 0;

	//MT_Push(edgeBuf[0].rpm, TW_THEETHS * TW_TOOTH_ALPHA / CYLINDERS);

	//uint32_t acc_Difference = edgeBuf[idx-1].ic >= edgeBuf[0].ic ? edgeBuf[idx-1].ic-edgeBuf[0].ic : edgeBuf[idx-1].ic-(edgeBuf[0].ic-0xffffffff);

}
float rpm_error_accel = 0.0;
float TM_UPDATE_ACCELERATION(float last_MT_RPM, float now_MT_RPM, uint32_t dt_mt){
	//esto se da con la MT_RPM en su periodo, si usamos el siguiente enviemos last_rpm (despues de actualizar)


	// A) Adjust a time computed from an angle (e.g., 31° to EOI)
	float dt_seconds = dt_mt / refClock;
	// con last inj rpm, que es mas lento, da dt mayor, y tambien unas rpm menor y dt todavia mayor...

	// B) Predict the RPM at that firing time, if you need it
	float theory_new_rpm = PredictRPM(last_MT_RPM, last_accel, dt_seconds);

	rpm_error_accel = theory_new_rpm - now_MT_RPM;


	if(rpm_error_accel > 50 || rpm_error_accel < -50){
		MT_AccelReset();
		//last_accel = MT_AccelUpdate(now_MT_RPM); //la aceleracion
	}
	last_accel = MT_AccelUpdate(now_MT_RPM); //la aceleracion

	//last_accel = (now_MT_RPM - last_MT_RPM)/dt_seconds; //la aceleracion
	float next_RPM = last_accel * dt_seconds + now_MT_RPM;

	return next_RPM;
}

float eoi_acc_K = 0.18; //-0.2 - 0.08 0.18, last rover 0.25
float eoi_acc_K_1 = 0; //-0.2 - 0.08 0.18, last rover 0.25
float eoi_acc_K_2 = 0; //-0.2 - 0.08 0.18, last rover 0.25

float lpf_eoi_acc_k = 1; //probar,
float correction_eoi_accel = 0.0f;

float limit_1 = -1.5;
float limit_2 = 1; //en otro setting 0 mehor
float TM_GET_ACCEL_CORRECTION(float last_MT_RPM, float now_MT_RPM, float TEETH_RPM, uint8_t triggerTeeth){
	//esto se da con la MT_RPM en su periodo, si usamos el siguiente enviemos last_rpm (despues de actualizar)
    if(now_MT_RPM < 1){
    	correction_eoi_accel = 0;
    	return 0.0;
    }
	float rp_sum = now_MT_RPM + last_MT_RPM;
    float dt = (1.0f / CYLINDERS) * (120.0f /  rp_sum);

    inj_accel = (edgeBuf[triggerTeeth].rpm - last_non_Inj_RPM)/dt;
    ////inj_accel = (now_MT_RPM - last_MT_RPM)/dt;
    //inj_accel = (TEETH_RPM - last_INJECTING_RPM)/dt;
    //inj_accel = (TEETH_RPM - edgeBuf[currentTooth].rpm)/dt;
    //inj_accel = last_accel;

	float comp = fclamp(-inj_accel * eoi_acc_K / now_MT_RPM, limit_1, limit_2); //-10, 10
	/*if(inj_accel>0){
		comp = inj_accel * eoi_acc_K_1/ now_MT_RPM;
	}else{
		comp = inj_accel * eoi_acc_K_2/ now_MT_RPM;
	}*/
	//last accel
	correction_eoi_accel += lpf_eoi_acc_k*(comp-correction_eoi_accel);
	//correction_eoi_accel = fclamp(comp, 0, -2000);
	correction_eoi_accel = comp;
	return correction_eoi_accel;
}

float last_to_inj_degs= 0;
float now_to_inj_degs = 0;
float newaccel = 0;
float TM_GET_INJ_ACCEL(uint8_t currentTooth){
	uint16_t alpha = currentTooth * TW_TOOTH_ALPHA;
	uint32_t ticks = ticks_diff(IC_RPM_Val2, edgeBuf[0].ic);
	float dt = ticks / refClock;

	last_to_inj_degs = now_to_inj_degs;
	now_to_inj_degs = alpha / dt;

	newaccel = (now_to_inj_degs - last_to_inj_degs)/dt;

	float comp = fclamp(-newaccel * eoi_acc_K / MT_RPM, limit_1, limit_2); //-10, 10
	correction_eoi_accel += lpf_eoi_acc_k*(comp-correction_eoi_accel);

	return correction_eoi_accel;
}

//float bip_acc_K = 0.055;
float lpf_bip_acc_k = 1;
float correction_bip_accel = 0.0f;
float last_teeth_rpm_bip = 0.0;
void TM_PREPARE_ACCEL_CORRECTION_BIP(uint8_t triggerteeth){ //should be triggerteeth-1, and then evaluate comp 1 teeth before triggerteeth.
	uint8_t idx = triggerteeth - 1;
	last_teeth_rpm_bip = (idx && idx < TW_PERCYL_TEETH) ? edgeBuf[idx].rpm : -1;
}
float TM_UPDATE_ACCEL_CORRECTION_BIP(float last_MT_RPM, float now_MT_RPM, float TEETH_RPM){
    float rp_sum = now_MT_RPM + last_MT_RPM;
    float dt = (1.0f / CYLINDERS) * (120.0f /  rp_sum);

    float bip_accel = (TEETH_RPM - last_non_Inj_RPM)/dt;
	float comp = fclamp(-bip_accel * bip_acc_K / now_MT_RPM, -10, 10);
	correction_bip_accel += lpf_bip_acc_k*(comp-correction_bip_accel);

}
float TM_GET_ACCEL_CORRECTION_BIP(){
	return correction_bip_accel;
}

uint16_t T_delayEnd = 20;
//float lpf_end_delay = 0.2;
void TM_UPDATE_END_DELAY_CORRECTION(){
	uint32_t t = TM_TIME_FROM_NEAREST_TOOTH(IC_EOI, 1);

	if(t - (T_integrated - T_delayEnd) < 100){
		//T_delayEnd = t - (T_integrated - T_delayEnd);
		//we were using this but bugging on borders

		//T_delayEnd -= lpf_end_delay*(T_delayEnd - (t - T_integrated));

	}else{
		T_delayEnd = 20;
	}
}
uint16_t TM_GET_T_DELAY_END(){
	return T_delayEnd;
}

float TM_GET_LOCAL_ACC_TO_ANGLE(float C_Angle, uint8_t roundingMode){
	uint8_t idx = C_Angle/TW_TOOTH_ALPHA; //empiezo diente despues de phi1

	if(roundingMode > 1){
		idx++;
	}else if(roundingMode){
		idx = (C_Angle+1.5f)/TW_TOOTH_ALPHA;
	}

	uint32_t accel_Difference = edgeBuf[idx].ic >= edgeBuf[0].ic ? edgeBuf[idx].ic-edgeBuf[0].ic : edgeBuf[idx].ic-(edgeBuf[0].ic-0xffffffff);
	float now_rpm = idx * 60.0 * refClock/accel_Difference / TW_THEETHS;
	return (now_rpm - edgeBuf[0].rpm)/idx;
}

/*
// ---- Tunables ----
#define SECTOR_DEG   90.0f   // angle between your MT RPM samples (90° in your case)
#define EMA_ALPHA    0.010f   // accel smoothing [0..1]0.015
#define RPM_MIN      50.0f   // ignore junk below this
#define DT_MIN_S     (SECTOR_DEG/360.0f * (120.0f / (2.0f*10000.0f))) // ~safety floor

// ---- State ----
static float s_prev_rpm = -1.0f;   // negative => not seeded
static float s_acc_ema  = 0.0f;    // filtered accel (RPM/s)


// Call once per new missing-tooth RPM; returns latest accel in RPM/s
static inline float MT_AccelUpdate(float rpm_now)
{
    if (rpm_now < RPM_MIN) return s_acc_ema;        // ignore bogus
    if (s_prev_rpm < 0.0f) { s_prev_rpm = rpm_now; return s_acc_ema; } // seed

    // Δt from sector angle and average RPM between samples:
    float rp_sum = rpm_now + s_prev_rpm;
    if (rp_sum <= 1e-3f) rp_sum = 1e-3f;
    float dt = (SECTOR_DEG / 360.0f) * (120.0f / rp_sum);
    if (dt < DT_MIN_S) dt = DT_MIN_S;

    // raw acceleration and simple clamp for sanity
    float acc_raw = (rpm_now - s_prev_rpm) / dt;
    if (acc_raw >  8000.0f) acc_raw =  8000.0f;
    if (acc_raw < -8000.0f) acc_raw = -8000.0f;

    // smooth it
    s_acc_ema += EMA_ALPHA * (acc_raw - s_acc_ema);

    s_prev_rpm = rpm_now;
    return s_acc_ema;
}*/
// ---- Tunables ----
#define SECTOR_DEG   90.0f     // angle between MT samples
float TAU_S      =  0.1f;     // smoothing time-constant (seconds) -> adjust to taste
#define RPM_MIN      50.0f
#define ACC_ABS_LIM  8000.0f

// ---- State ----
static float s_prev_rpm = -1.0f;
static float s_acc_ema  = 0.0f;

// Call once per new missing-tooth RPM; returns filtered accel in RPM/s
static inline float MT_AccelUpdate(float rpm_now)
{
    if (rpm_now < RPM_MIN) return s_acc_ema;
    if (s_prev_rpm < 0.0f) { s_prev_rpm = rpm_now; return s_acc_ema; }

    // Δt from sector angle and avg RPM between samples
    float rp_sum = rpm_now + s_prev_rpm;
    if (rp_sum <= 1e-3f) rp_sum = 1e-3f;
    float dt = (SECTOR_DEG / 360.0f) * (120.0f / rp_sum);  // seconds

    // Raw accel (RPM/s)
    float acc_raw = (rpm_now - s_prev_rpm) / dt;
    if (acc_raw >  ACC_ABS_LIM) acc_raw =  ACC_ABS_LIM;
    if (acc_raw < -ACC_ABS_LIM) acc_raw = -ACC_ABS_LIM;

    // ---- Time-constant EMA ----
    // Exact-discrete 1st-order low-pass: alpha = dt / (tau + dt)
    float alpha = dt / (TAU_S + dt);

    // Optional: keep alpha within sane bounds (helps extreme RPMs)
    if (alpha < 0.01f) alpha = 0.01f;   // very high RPM -> don't freeze
    if (alpha > 0.6f)  alpha = 0.6f;    // very low RPM -> don't overshoot

    s_acc_ema += alpha * (acc_raw - s_acc_ema);

    s_prev_rpm = rpm_now;
    return s_acc_ema;
}
static inline void MT_AccelReset()
{
    s_acc_ema = 0;
}

// last_injecting_rpm: RPM0  (current)
// last_accel:         a     (RPM/s)

/* 1) Predict RPM after dt seconds */
static inline float PredictRPM(float rpm0, float accel_rpms, float dt_s)
{
    return rpm0 + accel_rpms * dt_s;
}

/* 2) Time to sweep dtheta_deg with constant accel.
      Returns seconds. Falls back to constant-speed if accel ~ 0. */
static inline float TimeToAngleDeg(float rpm0, float accel_rpms, float dtheta_deg)
{
    const float eps = 1e-6f;
    float w0 = rpm0 * 6.0f;          // deg/s
    float a  = accel_rpms * 6.0f;    // deg/s^2

    if (dtheta_deg <= 0.0f || w0 <= eps) return 0.0f;

    if (fabsf(a) < 1e-4f) {
        // ~constant speed
        return dtheta_deg / w0;
    }

    // Solve 0.5*a*t^2 + w0*t - dtheta = 0
    float disc = w0*w0 + 2.0f*a*dtheta_deg;
    if (disc <= 0.0f) {
        // acceleration too negative to reach angle; fallback to constant-speed
        return dtheta_deg / fmaxf(w0, eps);
    }

    float t = (-w0 + sqrtf(disc)) / a;   // physical root for forward motion
    if (t < 0.0f) {
        // numerical/edge-case guard
        return dtheta_deg / fmaxf(w0, eps);
    }
    return t;
}
