# OID_analysis/scripts/collect_steady_cloak.py
"""
Collect steady cloak open-loop (q_ctl). Constant rear-cylinder rotation.

Usage:
    conda run -n pycuda_3_10 python src/OID_analysis/scripts/collect_steady_cloak.py \
        --device 3 --steps 500 --omega-rear 5.1
"""
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)
_SRC = os.path.join(_REPO, "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from LegacyCelerisLab import FlowField  # noqa: E402
from OID_analysis.utils.cfd_interface import (  # noqa: E402
    load_legacy_configs, get_velocity_field, save_vorticity_png, vorticity_from_ddf,
)
from OID_analysis.configs import get_scene, data_dir_for_scene, LEGACY_CFG_DIR  # noqa: E402

DATA_TYPE = np.float32
L0 = 20.0
CENTER_Y = (512 - 1) / 2.0


def collect(device_id: int, n_steps: int, omega_rear: float) -> str:
    cfg = get_scene("steady_cloak")
    u0 = cfg["u0"]
    si = cfg["sample_interval"]
    out_dir = data_dir_for_scene("steady_cloak")
    os.makedirs(out_dir, exist_ok=True)

    cuda_cfg, field_cfg = load_legacy_configs(LEGACY_CFG_DIR)

    ff = FlowField(field_cfg, cuda_cfg, device_id=device_id)
    print(f"  Steady cloak: {ff.FIELD_SHAPE}")

    # Reuse pinball baseline env (we'll re-add geometry)
    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
    n_stab = int(4 * ff.FIELD_SHAPE[0] / u0)
    print(f"  Stabilising ({n_stab} steps)...")
    ff.run(n_stab, np.zeros(n_obj, dtype=DATA_TYPE))

    # Steady cloak: constant rear-cylinder rotation
    # Front cylinder = 0, bottom = -omega_rear*U0, top = omega_rear*U0
    action_arr = np.zeros(n_obj, dtype=DATA_TYPE)
    action_arr[3] = 0.0
    action_arr[4] = -omega_rear * u0
    action_arr[5] = omega_rear * u0
    print(f"  Steady omega: front=0, bottom={-omega_rear*u0:.4f}, top={omega_rear*u0:.4f}")

    # Let steady cloak stabilize for a few steps before recording
    for _ in range(100):
        ff.run(si, action_arr)

    # Record
    sens_list, forc_list, ux_list, uy_list = [], [], [], []
    for step in range(n_steps):
        ff.run(si, action_arr)
        obs_slice = ff.obs.copy()[0:12]
        sens_list.append(obs_slice[0:6])
        forc_list.append(obs_slice[6:12])
        ux, uy = get_velocity_field(ff, u0=u0)
        ux_list.append(ux)
        uy_list.append(uy)

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

    # Save config noting that this is the steady cloak q_ctl
    cfg_ctl = dict(cfg)
    cfg_ctl["omega_front"] = 0.0
    cfg_ctl["omega_rear_scale"] = omega_rear
    with open(os.path.join(out_dir, "config_ctl.json"), "w") as f:
        json.dump({k: str(v) if not isinstance(v, (int, float, list, bool)) else v
                   for k, v in cfg_ctl.items()}, f, indent=2)

    omega = vorticity_from_ddf(ff, u0=u0)
    save_vorticity_png(os.path.join(out_dir, "vorticity_steady_cloak.png"),
                       omega, title=f"Steady cloak q_ctl (rear={omega_rear}U0)")

    del ff
    print(f"  Saved {n_steps} snapshots to {out_dir}")
    return out_dir


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--device", type=int, default=3)
    ap.add_argument("--steps", type=int, default=500)
    ap.add_argument("--omega-rear", type=float, default=5.1,
                    help="Rear cylinder rotation magnitude (U0 multiples)")
    args = ap.parse_args()

    t0 = time.time()
    out = collect(args.device, args.steps, args.omega_rear)
    print(f"Done in {time.time() - t0:.1f}s -> {out}")


if __name__ == "__main__":
    main()
