r"""
Synthetic race generator — stand-in for the real Race 1 export until ADC is
reauthenticated. Produces data/demo_race.json in the SAME schema the real
exporter emits, so everything downstream (scoring, render) is identical.

A 2x2 team race (Team Blue vs Team Yellow, 2 boats each) on a stand-in
"Digital 8" figure-of-eight course. Deterministic (no randomness). Designed so
the trailing boats decide the result and there is one lead change near the end.

Run:
  ..\.venv-demo\Scripts\python.exe export\synth_race.py
"""
from __future__ import annotations

import json
import math
from pathlib import Path

# ---- timing (relative to track start; gun offsets match the user's window) ----
TRACK_START_UTC_MS = 1_774_681_590_000          # arbitrary fixed anchor (deterministic)
GUN_OFFSET_S = 195                               # 15:06:30 -> 15:09:45
END_OFFSET_S = 555                               # 15:06:30 -> 15:19:00 (= 12m30s)
STEP_S = 1

# ---- local figure-8 course in metres (origin = course centre) ----
REF_LAT, REF_LON = 59.3210, 18.0090             # Stockholm-ish; only a backdrop anchor
M_PER_DEG_LAT = 111_320.0
M_PER_DEG_LON = 111_320.0 * math.cos(math.radians(REF_LAT))


def xy_to_latlon(x, y):
    return (REF_LAT + y / M_PER_DEG_LAT, REF_LON + x / M_PER_DEG_LON)


# course marks (local XY, metres). Non-self-intersecting windward/leeward with a
# single combined start/finish line (boats start north, round WM and LM, return
# south across the same line to finish). Clean stand-in; the real "Digital 8"
# geometry comes from the Firestore export.
LINE_PORT = (-45.0, -200.0)
LINE_STBD = (45.0, -200.0)
WM = (-60.0, 250.0)    # windward mark (round to port)
LM = (55.0, -110.0)    # leeward mark (round to starboard)
START_MID = ((LINE_PORT[0] + LINE_STBD[0]) / 2, (LINE_PORT[1] + LINE_STBD[1]) / 2)
FIN_MID = START_MID
FINISH_THROUGH = (0.0, -210.0)   # a touch past the line so boats cross it cleanly

# the sailed route: start -> WM -> LM -> through the finish line
ROUTE = [START_MID, WM, LM, FINISH_THROUGH]


def route_length(route):
    return sum(math.dist(route[i - 1], route[i]) for i in range(1, len(route)))


def pos_along(route, dist):
    """Point at arc-length `dist` along the polyline; clamps to ends.
    Returns (x, y, heading_deg)."""
    if dist <= 0:
        a, b = route[0], route[1]
        return (*a, _hdg(a, b))
    acc = 0.0
    for i in range(1, len(route)):
        a, b = route[i - 1], route[i]
        seg = math.dist(a, b)
        if acc + seg >= dist:
            t = (dist - acc) / seg
            return (a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, _hdg(a, b))
        acc += seg
    b, a = route[-1], route[-2]
    return (*route[-1], _hdg(a, b))


def _hdg(a, b):
    # compass heading (0=N, 90=E) from local XY where +x=E, +y=N
    return (math.degrees(math.atan2(b[0] - a[0], b[1] - a[1])) + 360.0) % 360.0


COURSE_LEN = route_length(ROUTE)


# ---- boats: each boat sails the route to an explicit finish time, so we can
# shape a close team race. The TRAILING boat of each team decides the result.
# Team Blue's both boats finish first (blue-2 at 205s) -> Blue wins; Team
# Yellow's trailing boat (yellow-2) finishes ~25s later, ~35 m back at the gun
# of Blue's win. Early on yellow-2 leads blue-2, then blue-2 passes -> one lead
# change between the decisive boats.
# Each boat: (deviceId, teamId, sailId, color, start_delay_s, finish_s)
BOATS = [
    # Team Blue
    ("dev-blue-1", "blue", "SWE 11", "#1565C0", 0.0, 188.0),
    ("dev-blue-2", "blue", "SWE 12", "#1E88E5", 3.0, 205.0),    # Blue trailing boat (decisive)
    # Team Yellow
    ("dev-yellow-1", "yellow", "SWE 21", "#F9A825", 1.0, 192.0),
    ("dev-yellow-2", "yellow", "SWE 22", "#FBC02D", 1.0, 230.0),  # Yellow trailing boat (decisive)
]
TEAMS = {
    "blue":   {"teamId": "blue",   "name": "Team Blue",   "boats": []},
    "yellow": {"teamId": "yellow", "name": "Team Yellow", "boats": []},
}

ROUTE_LEN = route_length(ROUTE)


def _smoothstep(x):
    x = max(0.0, min(1.0, x))
    return x * x * (3.0 - 2.0 * x)


def boat_distance(start_delay, finish_s, t_since_gun):
    """Arc-length along ROUTE at race-time t_since_gun (s). Smooth accel/decel,
    reaching the end of ROUTE exactly at finish_s."""
    if t_since_gun <= start_delay:
        return 0.0
    tau = (t_since_gun - start_delay) / max(1e-6, finish_s - start_delay)
    return ROUTE_LEN * _smoothstep(tau)


def prestart_pos(boat_idx, t_since_gun):
    """Loiter position before the gun: line up below the start line."""
    # spread the 4 boats along the start line, easing up to it as gun approaches
    x = -30.0 + boat_idx * 20.0
    approach = max(0.0, 1.0 + t_since_gun / GUN_OFFSET_S)  # 0 at track start -> 1 at gun
    y = START_MID[1] - 60.0 * (1.0 - min(1.0, approach))
    nxt = pos_along(ROUTE, 1.0)
    return (x, y, _hdg((x, y), (nxt[0], nxt[1])))


def main():
    frames: dict[str, dict] = {}
    for sec in range(0, END_OFFSET_S + 1, STEP_S):
        t_since_gun = sec - GUN_OFFSET_S
        frame: dict[str, dict] = {}
        for idx, (dev, _team, _sail, _color, delay, finish_s) in enumerate(BOATS):
            if t_since_gun < 0:
                x, y, hdg = prestart_pos(idx, t_since_gun)
                speed_now = 1.2
            else:
                d = boat_distance(delay, finish_s, t_since_gun)
                x, y, hdg = pos_along(ROUTE, d)
                # lane offset so the 4 boats don't overlap exactly
                lane = (idx - 1.5) * 6.0
                # offset perpendicular to heading
                ox = math.cos(math.radians(hdg)) * lane
                oy = -math.sin(math.radians(hdg)) * lane
                x, y = x + ox, y + oy
                d_prev = boat_distance(delay, finish_s, t_since_gun - 1)
                speed_now = max(0.0, d - d_prev)
            lat, lon = xy_to_latlon(x, y)
            frame[dev] = {
                "lat": round(lat, 7), "lon": round(lon, 7),
                "speed": round(speed_now, 2), "course": round(hdg, 1),
            }
        frames[str(sec)] = frame

    for dev, team, sail, color, _delay, _finish_s in BOATS:
        TEAMS[team]["boats"].append({
            "deviceId": dev, "sailId": sail, "visualId": f"{TEAMS[team]['name']} {sail}",
            "boatId": None, "color": color, "sternM": 1.5,
        })

    def wp(name, xy, typ="rounding", rounding="port"):
        lat, lon = xy_to_latlon(*xy)
        return {"name": name, "type": typ, "rounding": rounding,
                "lat": round(lat, 7), "lon": round(lon, 7)}

    def line(p, s):
        plat, plon = xy_to_latlon(*p)
        slat, slon = xy_to_latlon(*s)
        return {"port": {"lat": round(plat, 7), "lon": round(plon, 7)},
                "starboard": {"lat": round(slat, 7), "lon": round(slon, 7)}}

    feed = {
        "schema": "vr-broadcast-demo/1",
        "source": "synthetic",
        "meta": {
            "title": "Test före Sommarcupen — Race 1 (SYNTHETIC stand-in)",
            "user": "GSYS",
            "instanceId": None, "groupId": None,
            "openEnded": False,
            "raceType": "team_race_2",
            "windFromDeg": 350,
            "trackStartUtcMs": TRACK_START_UTC_MS,
            "gunUtcMs": TRACK_START_UTC_MS + GUN_OFFSET_S * 1000,
            "trackEndUtcMs": TRACK_START_UTC_MS + END_OFFSET_S * 1000,
            "frameStepMs": STEP_S * 1000,
        },
        "course": {
            "name": "Stand-in W/L (real Digital 8 from export)",
            "center": {"lat": REF_LAT, "lon": REF_LON},
            "startLine": line(LINE_PORT, LINE_STBD),
            "finishLine": line(LINE_PORT, LINE_STBD),
            "waypoints": [wp("WM", WM, rounding="port"), wp("LM", LM, rounding="starboard")],
        },
        "teams": list(TEAMS.values()),
        "frames": frames,
        "video": [],
    }

    out = Path(__file__).resolve().parent.parent / "data" / "demo_race.json"
    with open(out, "w", encoding="utf-8") as f:
        json.dump(feed, f, ensure_ascii=False, separators=(",", ":"))
    print(f"Wrote {out}")
    print(f"  course length ~{COURSE_LEN:.0f} m, frames={len(frames)}, boats={len(BOATS)}")


if __name__ == "__main__":
    main()
