"""第 43 章：采样、短时频谱、视频顺序和延迟队列；无模型下载。"""

from argparse import ArgumentParser
from hashlib import sha256
import json
from pathlib import Path
import platform

import numpy as np
from sklearn.linear_model import LogisticRegression


def tone_signal(rate=16000):
    t = np.arange(rate) / rate
    frequency = np.where(t < 0.5, 440.0, 880.0)
    return 0.6 * np.sin(2 * np.pi * frequency * t)


def spectrum(values, window=400, hop=160):
    frames = np.lib.stride_tricks.sliding_window_view(values, window)[::hop]
    taper = np.hanning(window)
    return np.abs(np.fft.rfft(frames * taper, axis=1)).T ** 2


def moving_video(rng):
    # 一个发亮的圆点；背景恒为零，不使用外部视频。
    start, end = rng.uniform(5, 10), rng.uniform(22, 27)
    yy = rng.uniform(8, 24)
    progress = np.linspace(0, 1, 8) ** rng.uniform(0.7, 1.5)
    cx = start + (end - start) * progress
    y, x = np.mgrid[:32, :32]
    return np.exp(-((x[None] - cx[:, None, None]) ** 2
                    + (y[None] - yy) ** 2) / (2 * 1.5 ** 2))


def centroids(frames):
    yy, xx = np.mgrid[:frames.shape[-2], :frames.shape[-1]]
    mass = frames.sum(axis=(-2, -1))
    if np.any(mass <= 0):
        raise ValueError("空白帧不能计算亮度质心")
    return np.stack([(frames * xx).sum(axis=(-2, -1)) / mass,
                     (frames * yy).sum(axis=(-2, -1)) / mass], axis=-1) / 31


def video_data(split):
    size, seed = {"train": (80, 2026), "validation": (20, 2027), "test": (40, 2028)}[split]
    rng = np.random.default_rng(seed)
    videos, labels, ids = [], [], []
    for i in range(size):
        forward = moving_video(rng)
        for direction, frames in [(1, forward), (0, forward[::-1].copy())]:
            videos.append(frames)
            labels.append(direction)
            ids.append(f"{split}-{i:03d}-{'right' if direction else 'left'}")
    pixels = np.stack(videos)
    return pixels, centroids(pixels), np.array(labels), ids


def features(points, kind):
    if kind == "mean":
        # 舍去不影响位置含义的浮点累加尾差，使正反序列得到完全相同特征。
        return np.round(points.mean(axis=1), 12)
    return np.concatenate([points[:, 0], points[:, -1]], axis=1)


def queue_schedule(process_ms, count=10, interval_ms=200):
    finished = 0
    rows = []
    for i in range(1, count + 1):
        arrived = i * interval_ms
        started = max(arrived, finished)
        finished = started + process_ms
        rows.append({"chunk": i, "arrived_ms": arrived, "started_ms": started,
                     "finished_ms": finished, "delay_ms": finished - arrived})
    return rows


def run():
    rate = 1000
    t = np.arange(20) / rate
    low = np.cos(2 * np.pi * 300 * t)
    high = np.cos(2 * np.pi * 700 * t)
    power = spectrum(tone_signal())
    frequencies = np.fft.rfftfreq(400, 1 / 16000)
    data = {split: video_data(split) for split in ["train", "validation", "test"]}
    models = {}
    for kind in ["mean", "ordered"]:
        model = LogisticRegression(C=1, max_iter=1000).fit(
            features(data["train"][1], kind), data["train"][2])
        row = {}
        for split, (_, points, labels, ids) in data.items():
            predicted = model.predict(features(points, kind))
            row[split] = {"accuracy": float(np.mean(predicted == labels)),
                          "predicted": predicted.tolist(), "labels": labels.tolist(), "ids": ids}
        row["weights"], row["bias"] = model.coef_.tolist(), model.intercept_.tolist()
        models[kind] = row
    signal = np.zeros(20)
    signal[[3, 8, 15]] = 1
    delayed = np.concatenate([np.zeros(2), signal[:-2]])
    lag = int(np.argmax(np.correlate(delayed, signal, mode="full")) - (len(signal) - 1))
    return {
        "source_sha256": sha256(Path(__file__).read_bytes()).hexdigest(),
        "environment": {"python": platform.python_version(), "numpy": np.__version__},
        "alias_max_error": float(np.max(np.abs(low - high))),
        "spectrum": {"shape": list(power.shape), "window": 400, "hop": 160,
                     "first_peak_hz": float(frequencies[power[:, :40].mean(1).argmax()]),
                     "second_peak_hz": float(frequencies[power[:, -40:].mean(1).argmax()])},
        "videos": models,
        "data_sha256": {name: sha256(v[0].tobytes()).hexdigest() for name, v in data.items()},
        "sample_video": data["test"][0][:2].tolist(),
        "sample_points": data["test"][1][:2].tolist(),
        "pair_mean_difference": float(np.max(np.abs(
            features(data["test"][1][::2], "mean") - features(data["test"][1][1::2], "mean")))),
        "alignment": {"video": signal.tolist(), "audio": delayed.tolist(),
                      "grid_seconds": 0.1, "audio_lag_steps": lag},
        "queues": {str(ms): queue_schedule(ms) for ms in [120, 260]},
    }


def show(report):
    print("1 kHz 采样：300 Hz 与 700 Hz 余弦的样本相同（数值误差 < 1e-12）。")
    spec = report["spectrum"]
    print(f"短时频谱：{spec['shape']}；前后主频={spec['first_peak_hz']:.0f}/{spec['second_peak_hz']:.0f} Hz")
    print("视频训练/验证/测试：160 / 40 / 80；每对正反序列在同一划分。")
    for kind in ["mean", "ordered"]:
        row = report["videos"][kind]
        print(f"{kind}：验证准确率={row['validation']['accuracy']:.3f}，"
              f"测试准确率={row['test']['accuracy']:.3f}")
    print(f"正反视频的平均位置特征差={report['pair_mean_difference']:.1f}")
    print(f"模拟音频比视频晚 {report['alignment']['audio_lag_steps'] * .1:.1f} 秒。")
    for key, rows in report["queues"].items():
        print(f"假设每块处理 {key} ms：第 10 块完成延迟={rows[-1]['delay_ms']} ms")
    print("视频只使用亮点质心；队列和同步是教学模拟，不是真实视频大模型或在线性能。")


if __name__ == "__main__":
    parser = ArgumentParser(description=__doc__)
    parser.add_argument("--out", type=Path)
    args = parser.parse_args()
    result = run()
    if args.out:
        args.out.parent.mkdir(parents=True, exist_ok=True)
        args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n")
    show(result)
