#!/usr/bin/env python3
"""ft-eyes-replay: play a recording into the shared-frame layout, as ft-eyegrab --share
would, at the recorded pace, to test ft-eyes without the headset.

Usage: lab/py lab/ft-eyes-replay NAME [PATH] [--from S] [--for S] [--now]
NAME is a recording (a bare name is in eyes_lab.CAPTURES). PATH defaults to
/dev/shm/frametop-eyes-cams-replay; run ft-eyes with FT_EYES_CAMS=PATH (and FT_EYES_GAZE=...
so it doesn't overwrite the live output). Frames keep the recording's times, unless --now: then
they're stamped with today's CLOCK_MONOTONIC_RAW as they're written, as ft-eyegrab's are (for
tools that line them up with something live, such as the eye recorder's dots).
"""
import mmap
import os
import struct
import sys
import time
from pathlib import Path

import numpy as np

sys.path.insert(0, str(Path(__file__).resolve().parent))
from eyes_lab import capture  # noqa: E402

W, H, SLOTS = 512, 400, 8
ESIZE = 64 + W * H


def main(argv):
    cap = capture(argv[0])
    path = argv[1] if len(argv) > 1 and not argv[1].startswith("--") else "/dev/shm/frametop-eyes-cams-replay"
    start = float(argv[argv.index("--from") + 1]) if "--from" in argv else 0.0
    length = float(argv[argv.index("--for") + 1]) if "--for" in argv else 1e9
    idx = np.array([[float(v) for v in l.split()] for l in (cap / "index.txt").read_text().splitlines()
                    if len(l.split()) == 4])
    frames = np.memmap(cap / "frames.raw", dtype=np.uint8, mode="r").reshape(-1, H, W)
    size = 64 + 2 * SLOTS * ESIZE
    if os.path.exists(path):
        os.unlink(path)
    fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o600)
    os.ftruncate(fd, size)
    mm = mmap.mmap(fd, size)
    os.close(fd)
    struct.pack_into("<6I2QI", mm, 0, 0x31434546, 1, W, H, SLOTS, ESIZE, 0, 0, os.getpid())
    count = [0, 0]
    t0 = idx[0, 3] + start
    wall0 = time.monotonic()
    shift = time.clock_gettime(time.CLOCK_MONOTONIC_RAW) - t0 if "--now" in argv else 0.0
    try:
        for i in range(len(frames)):
            t = idx[i, 3]
            if t < t0:
                continue
            if t - t0 > length:
                break
            delay = (t - t0) - (time.monotonic() - wall0)
            if delay > 0:
                time.sleep(delay)
            cam = int(idx[i, 2])
            n = count[cam]
            off = 64 + (cam * SLOTS + n % SLOTS) * ESIZE
            seq = struct.unpack_from("<Q", mm, off)[0]
            struct.pack_into("<Q", mm, off, seq + 1)
            mm[off + 64:off + 64 + W * H] = frames[i].tobytes()
            struct.pack_into("<dQII", mm, off + 8, t + shift, n, cam, int(idx[i, 1]))
            struct.pack_into("<Q", mm, off, seq + 2)
            count[cam] = n + 1
            struct.pack_into("<Q", mm, 24 + 8 * cam, n + 1)
    finally:
        os.unlink(path)
    print(f"replayed {sum(count)} frames")


if __name__ == "__main__":
    main(sys.argv[1:])
