"""Synthetic COCO-17 numeric fixtures, NOT real-video accuracy evidence."""
import math
from dataclasses import replace

from app.contracts.frames import FrameEnvelope, PoseObservation
from app.fall_config import FallConfig
from app.fall_scenario import FallEngine
from app.pose_config import POSE_MODEL


def make_frame(seq, seconds, width=1000, height=1000, **changes):
    frame = FrameEnvelope('test-server', '77', 'test-stream', 'run-a', 1, seq,
                          None if seconds is None else round(seconds * 1e9), 1_700_000_000_000,
                          width, height)
    return replace(frame, **changes)


def make_pose(frame, *, angle=0., hip=(500., 400.), span=400., missing=(),
              score=.9, joint_score=.9, knee_bent=False, box=(.1, .1, .9, .9)):
    theta = math.radians(angle)
    shoulder = (hip[0] - 200 * math.sin(theta), hip[1] - 200 * math.cos(theta))
    points = [(0., 0., 0.)] * 17
    for sign, s, h, k, a in ((-1, 5, 11, 13, 15), (1, 6, 12, 14, 16)):
        x = hip[0] + sign * 20
        points[s] = (shoulder[0] + sign * 20, shoulder[1], joint_score)
        points[h] = (x, hip[1], joint_score)
        ankle_y = shoulder[1] + span
        points[k] = (x + (120 if knee_bent else 0), (hip[1] + ankle_y) / 2, joint_score)
        points[a] = (x, ankle_y, joint_score)
    for index in missing:
        points[index] = (*points[index][:2], 0.)
    normalized = tuple((x / frame.frame_width, y / frame.frame_height, c) for x, y, c in points)
    return PoseObservation(frame, POSE_MODEL, score, box, normalized)


class Sequence:
    def __init__(self, **config):
        self.engine = FallEngine(FallConfig(enabled=True, **config))
        self.seq = 0

    def step(self, seconds, *, present=True, track_id=1, gap=False, frame_changes=None, **options):
        frame = make_frame(self.seq, seconds, **(frame_changes or {}))
        self.seq += 1
        poses = (make_pose(frame, **options),) if present else ()
        ids = (track_id,) if present else ()
        return self.engine.evaluate(frame, poses, ids, gap_before=gap)

    def arm(self):
        for i in range(7):
            self.step(i / 10)
        assert self.engine.inspect(1)['state'] == 'UPRIGHT'

    def down(self, t, **options):
        return self.step(t, angle=80., hip=(500., 520.), span=200., **options)

    def confirm(self):
        self.arm()
        results = [self.down(i / 10) for i in range(7, 15)]
        events = [e for r in results for e in r.event_candidates]
        assert len(events) == 1, [(r.frame.pts_ns, r.decisions[0].state,
                                  r.decisions[0].down_hold_ns) for r in results]
        return events[0]
