"""Step the Python animals by hand and write down every number, for compare_with_page.js.

    python make_traces.py        -> traces.json, next to this file (about 3 MB, not for git)

The mouse path and the gecko's moves are fixed, so the page's JavaScript can be stepped
with the same ones and compared number by number. Nothing asks the model: in the hunt,
`asking` is held True and each move is set by hand. Windows flash open and shut.
"""
import ast
import json
import math
import pathlib
import sys
import types

HERE = pathlib.Path(__file__).resolve().parent
ASSETS = HERE.parent          # the tutorial's files: one folder up from checks/
FRAMES = 400              # for the three walkers of lizard.py
HUNT_FRAMES = 1000

# Frame -> what happens just before that frame's step of the hunt.
HUNT_EVENTS = {
    10: {"doing": "creep"},                     # creep to the dragonfly at the start place
    200: {"doing": "jump"},                     # in reach by now: jump, and catch it
    260: {"mouse": [150, 120]},                 # a new dragonfly, far away at the top left
    262: {"doing": "creep"},
    300: {"mouse": [180, 470]},                 # it flies somewhere else while the gecko creeps
    330: {"doing": "wait"},
    360: {"doing": "creep"},
    640: {"doing": "jump"},                     # a jump ...
    642: {"mouse": [700, 500]},                 # ... and it flies off: a miss
    700: {"doing": "jump"},                     # a jump from much too far: it lands short
    760: {"doing": "creep"},
    820: {"mouse": [705.5, 498.25]},            # a small move: it is flying again
    900: {"doing": "jump"},
}


def decide_words():
    """decide.py's state and question, read from its text: running it would load the model."""
    tree = ast.parse((ASSETS / "decide.py").read_text(encoding="utf-8"))
    question = [node.value for node in tree.body if isinstance(node, ast.Assign)
                and getattr(node.targets[0], "id", "") == "question"][0]
    call = [node for node in ast.walk(tree) if isinstance(node, ast.Call)
            and getattr(node.func, "attr", "") == "system_one"][0]
    return {"state": ast.literal_eval(call.args[0]), "question": ast.literal_eval(question)}


def mouse_path(frame):
    """The same wandering mouse for every walker: a slow loop that sometimes jumps."""
    if frame % 97 == 0:
        return [120 + (frame * 37) % 560, 90 + (frame * 53) % 420]
    t = frame / 60
    return [400 + 250 * math.cos(t * 0.9), 300 + 170 * math.sin(t * 1.7)]


def walker_trace(lizard, kind):
    animal = lizard.Animal(kind)
    rows = []
    for frame in range(FRAMES):
        animal.mouse = mouse_path(frame)
        animal.move()
        if hasattr(animal, "legs"):
            animal.move_legs()
        row = {"points": [p[:] for p in animal.points], "angles": animal.angles[:]}
        if hasattr(animal, "legs"):
            row["legs"] = [[joint[:] for joint in leg["joints"]] for leg in animal.legs]
        rows.append(row)
    animal.root.destroy()
    return rows


def hunt_trace(hunt):
    animal = hunt.Animal("model")
    animal.asking = True                # it never asks: every move comes from HUNT_EVENTS
    rows = []
    for frame in range(HUNT_FRAMES):
        event = HUNT_EVENTS.get(frame, {})
        if "mouse" in event:
            x, y = event["mouse"]
            animal.mouse_moved(types.SimpleNamespace(x=x, y=y))      # the file's own method
        if "doing" in event:
            animal.doing = event["doing"]
        animal.hunt()
        animal.move_legs()
        rows.append({"points": [p[:] for p in animal.points], "angles": animal.angles[:],
                     "legs": [[joint[:] for joint in leg["joints"]] for leg in animal.legs],
                     "target": animal.target[:] if animal.target else None,
                     "doing": animal.doing, "still": animal.still, "frames": animal.frames,
                     "gone": animal.gone, "caught": animal.caught, "sees": animal.sees()})
    animal.root.destroy()
    return rows


def main():
    sys.path.insert(0, str(ASSETS))
    import hunt
    import lizard
    from agent_file import load_agent
    out = {"mouse": [mouse_path(frame) for frame in range(FRAMES)]}
    for kind in ["lizard", "fish", "snake"]:
        out[kind] = walker_trace(lizard, kind)
    out["hunt"] = hunt_trace(hunt)
    out["hunt_events"] = {str(frame): event for frame, event in HUNT_EVENTS.items()}
    out["words"] = {"MODEL": hunt.MODEL, "WHO": hunt.WHO, "RULES": hunt.RULES, "MOVES": hunt.MOVES}
    agent, how = load_agent(stand_in=True)      # only its words are read: no model, no library
    out["agent_words"] = {"who": agent["WHO"], "rules": agent["RULES"], "moves": agent["MOVES"]}      # step 11
    out["decide_words"] = decide_words()                                                              # step 10
    target = HERE / "traces.json"
    target.write_text(json.dumps(out), encoding="utf-8")
    rows = out["hunt"]
    jumps = [i for i in range(1, HUNT_FRAMES) if rows[i]["target"] and not rows[i - 1]["target"]]
    catches = [i for i in range(1, HUNT_FRAMES) if rows[i]["caught"] != rows[i - 1]["caught"]]
    print(f"hunt: {HUNT_FRAMES} frames, jumps start at frames {jumps}, catches at frames {catches}")
    print(f"wrote {target.name}: {target.stat().st_size:,} bytes")


if __name__ == "__main__":
    main()
