"""Link the 7-8 April segment again, this time without the filters that threw
eclipse afternoon away.

The first pass wanted tracks of at least 25 points at 20+ points an hour. That is
a reasonable floor for a clear March night and the wrong floor for this segment,
because 8 April afternoon over Erie is patchy cloud AND it contains the one event
that removes the Sun from the sky entirely. The track fragments, every fragment
falls under the floor, and the whole afternoon disappears -- which is why the page
has 7 April and nothing from the day it is actually about.

So: same movement-only rule, looser floor, and a gap tolerance wide enough to
survive cloud. Nothing here consults an ephemeris, and no fragment is stitched to
another -- the totality gap in particular is left exactly as the camera recorded
it, because that hole is evidence and closing it would destroy it.

    python3 apr_track2.py

Writes apr_tracks2.pkl and prints every track it found.
"""
import pickle

import numpy as np

from apr8_build import merge, timebase

GATE = 18.0      # px per frame-step a real body may move
MAX_GAP = 14     # frames a track may go unseen (cloud, and totality) and survive
MIN_LEN = 12     # points
MIN_TRAVEL = 60  # px end to end, so streetlights and hot pixels cannot qualify


def link(c, tv):
    live, tracks = [], []
    for i, o in enumerate(c):
        pool = merge([d for d in o['cands'] if d['npx'] >= 8 and d['aspect'] < 3.0])
        used, nxt = set(), []
        for tr in live:
            if i - tr['last'] > MAX_GAP:
                tracks.append(tr)
                continue
            pts = tr['pts']
            if len(pts) >= 3:
                (i0, x0, y0, _), (i1, x1, y1, _) = pts[-3], pts[-1]
                dt = max(1, i1 - i0)
                px = x1 + (x1 - x0) / dt * (i - i1)
                py = y1 + (y1 - y0) / dt * (i - i1)
            else:
                px, py = pts[-1][1], pts[-1][2]
            best, bd = None, GATE * max(1, i - tr['last'])
            for j, d in enumerate(pool):
                if j in used:
                    continue
                dd = np.hypot(d['x'] - px, d['y'] - py)
                if dd < bd:
                    best, bd = j, dd
            if best is not None:
                used.add(best)
                d = pool[best]
                tr['pts'].append((i, d['x'], d['y'], d['npx']))
                tr['last'] = i
            nxt.append(tr)
        for j, d in enumerate(pool):
            if j not in used:
                nxt.append(dict(pts=[(i, d['x'], d['y'], d['npx'])], last=i))
        live = nxt
    tracks.extend(live)
    keep = [tr['pts'] for tr in tracks
            if len(tr['pts']) >= MIN_LEN
            and np.hypot(tr['pts'][-1][1] - tr['pts'][0][1],
                         tr['pts'][-1][2] - tr['pts'][0][2]) >= MIN_TRAVEL]
    return sorted(keep, key=len, reverse=True)


def hhmm(m):
    d, r = divmod(m, 1440)
    return f"{7 + int(d)} Apr {int(r) // 60:02d}:{int(r) % 60:02d}"


def main():
    A, B, tv, c, res = timebase()
    print(f"timebase: raw_min = {A:.2f} + {B:.4f} * t_video "
          f"(ladder residual rms {res.std() * B:.2f} camera-min)")
    tracks = link(c, tv)
    print(f"\n{len(tracks)} moving tracks:")
    for p in tracks:
        m0, m1 = A + B * tv[p[0][0]], A + B * tv[p[-1][0]]
        span = (p[-1][0] - p[0][0] + 1)
        print(f"  n={len(p):4d}  {hhmm(m0)} -> {hhmm(m1)}  "
              f"({len(p)}/{span} frames present)  "
              f"px ({p[0][1]:6.0f},{p[0][2]:5.0f}) -> ({p[-1][1]:6.0f},{p[-1][2]:5.0f})")
    pickle.dump(dict(A=A, B=B, tracks=tracks), open('apr_tracks2.pkl', 'wb'))
    print('\nwrote apr_tracks2.pkl')


if __name__ == '__main__':
    main()
