"""Collect steady cloak (open-loop constant rotation).

Usage:
    conda run -n pycuda_3_10 python scripts/collect_steady_cloak.py --device 2

Output: data/steady_cloak/steady_cloak/
"""
from __future__ import annotations

import argparse
import json
import os
import sys
import time

import numpy as np

_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
if _REPO not in sys.path:
    sys.path.insert(0, _REPO)
_ANALYSIS = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if _ANALYSIS not in sys.path:
    sys.path.insert(0, _ANALYSIS)

from LegacyCelerisLab import FlowField

from CCD_analysis.configs import get_scene, data_dir_for_scene, LEGACY_CFG_DIR, L0, CENTER_Y
from CCD_analysis.utils.cfd_interface import (
    load_legacy_configs, get_velocity_field, save_vorticity_png, vorticity_from_ddf,
)


def collect():
    ap = argparse.ArgumentParser()
    ap.add_argument("--device", type=int, default=2)
    ap.add_argument("--tune", action="store_true", help="scan rear omega")
    args = ap.parse_args()

    cfg = get_scene("steady_cloak")
    out_dir = data_dir_for_scene("steady_cloak")
    cuda_cfg, field_cfg = load_legacy_configs(LEGACY_CFG_DIR)
    field_cfg = field_cfg._replace(viscosity=float(cfg["nu"]))

    ff = FlowField(field_cfg, cuda_cfg, device_id=args.device)
    l0 = L0

    for y_off in [2.0, 0.0, -2.0]:
        ff.add_sensor((40.0 * l0, CENTER_Y + y_off * l0, 0.0), l0 / 4.0)
    ff.add_cylinder((30.0 * l0, CENTER_Y, 0.0), l0 / 2.0)
    ff.add_cylinder((31.3 * l0, CENTER_Y + 0.75 * l0, 0.0), l0 / 2.0)
    ff.add_cylinder((31.3 * l0, CENTER_Y - 0.75 * l0, 0.0), l0 / 2.0)

    n_obj = 6
    stabilize = int(4 * 1280 / cfg["u0"])
    ff.run(stabilize, np.zeros(n_obj, dtype=np.float32))

    rear_scale = cfg["omega_rear_scale"]
    if args.tune:
        candidates = [4.7, 4.9, 5.1, 5.3, 5.5]
    else:
        candidates = [rear_scale]

    for scale in candidates:
        rear_val = scale * cfg["u0"]
        temp = np.zeros(n_obj, dtype=np.float32)
        temp[3] = cfg["omega_front"]
        temp[4] = rear_val
        temp[5] = -rear_val
        ff.run(stabilize, np.zeros(n_obj, dtype=np.float32))
        ff.run(stabilize, temp)

        sens_list = []
        for _ in range(30):
            ff.run(cfg["sample_interval"], temp)
            sens_list.append(ff.obs.copy()[0:6])
        std = float(np.std(np.array(sens_list), axis=0).mean())
        print(f"  rear={scale:.1f}xU0 -> sensor std={std:.6f}")

    # Save with best (or single) value
    rear_val = candidates[-1] * cfg["u0"]
    temp = np.zeros(n_obj, dtype=np.float32)
    temp[3] = cfg["omega_front"]
    temp[4] = rear_val
    temp[5] = -rear_val

    ff.run(stabilize, temp)
    sens_list, forc_list, ux_list, uy_list = [], [], [], []
    for _ in range(30):
        ff.run(cfg["sample_interval"], temp)
        obs = ff.obs.copy()
        sens_list.append(obs[0:6])
        forc_list.append(obs[6:12])
        ux, uy = get_velocity_field(ff, u0=cfg["u0"])
        ux_list.append(ux)
        uy_list.append(uy)

    np.savez_compressed(os.path.join(out_dir, "fields.npz"),
                        ux=np.stack(ux_list), uy=np.stack(uy_list))
    np.savez(os.path.join(out_dir, "sensors.npz"),
             sensors=np.array(sens_list, dtype=np.float32),
             forces=np.array(forc_list, dtype=np.float32))

    omega = vorticity_from_ddf(ff, u0=cfg["u0"])
    save_vorticity_png(os.path.join(out_dir, "vorticity.png"),
                       omega, title="Steady Cloak Re=100")
    del ff

    meta = dict(cfg, rear_scale=candidates[-1], n_samples=30)
    with open(os.path.join(out_dir, "meta.json"), "w") as f:
        json.dump(meta, f, indent=2)
    print(f"Done, saved to {out_dir}")


if __name__ == "__main__":
    t0 = time.time()
    collect()
    print(f"Time: {time.time() - t0:.1f}s")
