"""Record actual AMS flight state and motion without losing case-sensitive minified field names."""
import argparse
import datetime as dt
import json
from pathlib import Path
import subprocess
import sys
import time

# Each mapping is checked against that exact minifier output. Never accept a
# new hash merely because a previous build happened to use the same names.
BASE = dict(state="Ԙ", flight="أ", job="ا", entry="ŏ", report="ب", progress="ؾ",
            command="ψ", attitude="χ", clearance="φ", scanIndex="Ͼ", blocked="ϭ",
            problem="ſ", camera="Ѕ", cameraDetail="І", obstacle="Ї")
MAPPINGS = {
    "2cc105a720ecd1552791816a0edf106cfab91b501a1e8048421b7f23382d333a": BASE,
    "b191430cf9ab7fe2b21569344ebcdc927bfa52847b84632440bdc68f3e5abc73": BASE,
    "2aed54ad323c588852f626455df0bb61c0e6565970c173dd5ed7007d832d259f":
        dict(state="Ԛ", flight="إ", job="ة", entry="Ő", report="ت", progress="ـ",
             command="ω", attitude="ψ", clearance="χ", scanIndex="Ͽ", blocked="Ϯ",
             problem="ƀ", camera="І", cameraDetail="Ї", obstacle="Ј"),
    "1146850d83e9d165d436828c2f526c212aa9e2928abe6eb447102cc4d3e7f2e3":
        dict(state="Ԝ", flight="ا", job="ث", entry="Ő", report="ج", progress="ق",
             command="ύ", attitude="ό", clearance="ϋ", scanIndex="Ѓ", blocked="ϲ",
             problem="ƅ", camera="Њ", cameraDetail="Ћ", obstacle="Ќ"),
}
# Capture extension changes no inspected declarations; checked against the new export.
MAPPINGS["9553e0ec92de8fc93c814852f37ff088f579c038ca8bdcf6c06b57e0f44d28ab"] = MAPPINGS[
    "1146850d83e9d165d436828c2f526c212aa9e2928abe6eb447102cc4d3e7f2e3"]
STATES = {"î": "Boot", "ï": "Docked", "ð": "Servicing", "ñ": "Ready",
          "ò": "Departing", "ó": "Transit", "ô": "Survey", "õ": "Align",
          "ö": "Drilling", "ø": "Retreat", "ù": "Returning", "ú": "Holding",
          "û": "DockAlign", "ü": "DockApproach", "ý": "DockRetreat",
          "þ": "Paused", "ÿ": "Manual", "u": "Fault"}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--block", required=True)
    parser.add_argument("--grid", required=True)
    parser.add_argument("--seconds", type=float, default=30)
    parser.add_argument("--interval", type=float, default=1)
    parser.add_argument("--out", type=Path, required=True)
    parser.add_argument("--pipe")
    args = parser.parse_args()
    if not 0 < args.seconds <= 600 or not .1 <= args.interval <= 30:
        parser.error("seconds must be 0..600 and interval must be 0.1..30")
    cli = Path(__file__).resolve().parents[1] / "src/XFE.SeAgent.Cli/bin/Release/net10.0/xfe-se.exe"

    def call(method, parameters):
        command = [str(cli), "call", method, "--params", json.dumps(parameters)]
        if args.pipe:
            command += ["--pipe", args.pipe]
        process = subprocess.run(command, capture_output=True, encoding="utf-8", timeout=35)
        response = json.loads(process.stdout)
        if process.returncode or "error" in response:
            raise RuntimeError(response.get("error", process.stderr))
        return response["result"]

    program = call("pb.read", {"entityId": args.block})
    if program["sha256"] not in MAPPINGS:
        raise RuntimeError("Unrecognized AMS export " + program["sha256"] + "; derive a new field mapping for this source before tracing.")
    mapping = MAPPINGS[program["sha256"]]
    args.out.parent.mkdir(parents=True, exist_ok=True)
    started = time.monotonic()
    samples, previous = 0, None
    with args.out.open("x", encoding="utf-8") as output:
        while time.monotonic() - started < args.seconds:
            fields = call("pb.inspect", {"entityId": args.block, "fields": ["E", "H"], "depth": 3})["fields"]
            grid = call("grids.get", {"entityId": args.grid})
            role = fields["H"]
            flight = role[mapping["flight"]]
            job = role[mapping["job"]]
            state = role[mapping["state"]]
            sample = {"utc": dt.datetime.now(dt.timezone.utc).isoformat(), "time": fields["E"],
                      "state": state, "stateName": STATES.get(state, state),
                      "sourceSha256": program["sha256"], "reason": role["l"], "position": grid["pose"]["position"],
                      "actualSpeed": grid["speed"], "commandSpeed": flight[mapping["command"]],
                      "clearance": flight[mapping["clearance"]], "attitudeError": flight[mapping["attitude"]],
                      "scanIndex": flight[mapping["scanIndex"]], "scanBlocked": flight[mapping["blocked"]],
                      "problem": flight[mapping["problem"]], "camera": flight[mapping["camera"]],
                      "cameraDetail": flight[mapping["cameraDetail"]], "obstacle": flight[mapping["obstacle"]],
                      "jobId": job.get("g") if job else None, "jobEntry": job.get(mapping["entry"]) if job else None,
                      "progressAge": fields["E"] - role[mapping["progress"]],
                      "lastReport": role[mapping["report"]], "integrity": grid.get("integrity"), "fields": fields}
            output.write(json.dumps(sample, ensure_ascii=False, separators=(",", ":")) + "\n")
            output.flush()
            state = (sample["state"], sample["reason"])
            if state != previous:
                print(json.dumps({k: sample[k] for k in ("time", "state", "reason", "actualSpeed", "commandSpeed", "scanIndex")}, ensure_ascii=False), flush=True)
                previous = state
            samples += 1
            time.sleep(args.interval)
    print(json.dumps({"samples": samples, "output": str(args.out.resolve())}), flush=True)


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    main()
