"""Trip computer + performance timers (computed from live data, no serial).

TripComputer estimates fuel economy and trip totals from vehicle speed (km/h)
and MAF (g/s) -- the same MAF-based estimate Torque/etc. use. PerformanceMeter
measures 0-60 mph and 1/4-mile times, auto-detecting a launch from a near-stop.

Both are fed time-stamped samples and are unit tested without hardware.
"""

KMH_PER_MPH = 1.609344
GAS_AFR = 14.7            # stoichiometric air:fuel for gasoline
GAS_G_PER_GAL = 2835.0   # ~6.25 lb/gal


def _gph(maf_gps, afr=GAS_AFR, g_per_gal=GAS_G_PER_GAL):
    return (maf_gps / afr) * 3600.0 / g_per_gal


class TripComputer:
    def __init__(self, afr=GAS_AFR, g_per_gal=GAS_G_PER_GAL):
        self.afr, self.g_per_gal = afr, g_per_gal
        self.reset()

    def reset(self):
        self.dist_km = 0.0
        self.fuel_gal = 0.0
        self.moving_s = 0.0
        self.elapsed_s = 0.0
        self._t = None

    def update(self, t, speed_kmh, maf_gps):
        if self._t is None:
            self._t = t
            return
        dt = t - self._t
        self._t = t
        if dt <= 0 or dt > 5:            # ignore gaps / first sample
            return
        self.elapsed_s += dt
        if speed_kmh:
            self.dist_km += speed_kmh * dt / 3600.0
            if speed_kmh > 1.0:
                self.moving_s += dt
        if maf_gps:
            self.fuel_gal += _gph(maf_gps, self.afr, self.g_per_gal) * dt / 3600.0

    def instant_mpg(self, speed_kmh, maf_gps):
        if not speed_kmh or not maf_gps:
            return 0.0
        gph = _gph(maf_gps, self.afr, self.g_per_gal)
        return (speed_kmh / KMH_PER_MPH) / gph if gph > 0 else 0.0

    def avg_mpg(self):
        miles = self.dist_km / KMH_PER_MPH
        return miles / self.fuel_gal if self.fuel_gal > 0 else 0.0

    def stats(self):
        return {
            "distance_mi": round(self.dist_km / KMH_PER_MPH, 2),
            "fuel_gal": round(self.fuel_gal, 3),
            "avg_mpg": round(self.avg_mpg(), 1),
            "elapsed_s": round(self.elapsed_s, 1),
            "moving_s": round(self.moving_s, 1),
        }


class PerformanceMeter:
    """0-60 mph and 1/4-mile timers. A run starts when the car pulls away from a
    near-stop and ends at the 1/4 mile or when it stops; best times are kept."""

    QUARTER_M = 402.336

    def __init__(self):
        self.best_0_60 = None
        self.best_quarter = None
        self.last_0_60 = None
        self.last_quarter = None
        self._reset_run()

    def _reset_run(self):
        self._running = False
        self._t0 = None
        self._dist_m = 0.0
        self._last_t = None
        self._stop_t = None
        self._did60 = False

    def update(self, t, speed_kmh):
        if speed_kmh is None:
            return
        mph = speed_kmh / KMH_PER_MPH
        if not self._running:
            if mph < 1.0:
                self._stop_t = t               # parked, ready to launch
            elif self._stop_t is not None:
                self._running = True           # launch detected
                self._t0 = self._stop_t
                self._dist_m = 0.0
                self._last_t = t
                self._did60 = False
            return
        dt = t - self._last_t
        self._last_t = t
        if dt <= 0 or dt > 5:
            self._reset_run()
            return
        self._dist_m += (speed_kmh / 3.6) * dt
        if mph >= 60 and not self._did60:
            self._did60 = True
            self.last_0_60 = round(t - self._t0, 2)
            if self.best_0_60 is None or self.last_0_60 < self.best_0_60:
                self.best_0_60 = self.last_0_60
        if self._dist_m >= self.QUARTER_M:
            self.last_quarter = round(t - self._t0, 2)
            if self.best_quarter is None or self.last_quarter < self.best_quarter:
                self.best_quarter = self.last_quarter
            self._reset_run()
        elif mph < 1.0:
            self._reset_run()
