"""Open hunt.py's window for real, with nobody at the mouse, and print what it does.

    python window_runs.py hunt     the model is on: the gecko creeps up on a resting
                                   dragonfly and catches it, leaves a flying one alone,
                                   and misses one that flies off as the jump begins
    python window_runs.py off      the model cannot be reached: "no answer", no crash
    python window_runs.py mouse    Animal("mouse"): no hunting parts, it walks to the mouse
    python window_runs.py main     the file started through its own main(), for 6 seconds

One scenario for each run: tkinter wants one window system in a process. The mouse is
moved with Tk's own events (event_generate), so keep the real mouse out of the window.
"hunt" takes about 20 seconds and asks the real model about 17 times.
"""
import json
import math
import pathlib
import runpy
import sys
import time
import tkinter as tk

ASSETS = pathlib.Path(__file__).resolve().parent.parent      # the tutorial's files: one folder up from checks/
NOWHERE = "http://127.0.0.1:9/v1/systemone"      # nothing listens on port 9


def scenario_hunt():
    import hunt
    animal = hunt.Animal("model")
    started = time.time()
    log = {"events": [], "asks": []}
    state = {"phase": "idle", "last_doing": None, "asking": False, "flight": 0,
             "escaped": False, "caught_before_escape": None, "jump_seen": 0}

    def note(text):
        log["events"].append(f"{time.time() - started:6.2f}s frame {animal.frames:4d}  {text}")

    def finish():
        gaps = [later - earlier for earlier, later in zip(log["asks"], log["asks"][1:])]
        seconds = time.time() - started
        print(json.dumps({"events": log["events"], "asks": len(log["asks"]),
                          "seconds_between_asks": [round(gap, 2) for gap in gaps],
                          "frames": animal.frames, "seconds": round(seconds, 1),
                          "frames_per_second": round(animal.frames / seconds, 1)}, indent=1))
        animal.root.destroy()

    def watch():
        now = time.time() - started
        if animal.asking and not state["asking"]:
            log["asks"].append(now)
        state["asking"] = animal.asking
        if animal.doing != state["last_doing"]:
            far = math.dist(animal.points[0], animal.mouse)
            note(f"doing -> {animal.doing:9}  far {far:5.0f}  still {animal.still:4d}  sees: {animal.sees()}")
            state["last_doing"] = animal.doing
        if state["phase"] == "idle":
            # The dragonfly rests at its start place. The gecko should creep, jump and catch it.
            if animal.target and not state["jump_seen"]:
                state["jump_seen"] = animal.frames
                note(f"jump starts, target {[round(v) for v in animal.target]}")
            if animal.gone:
                note(f"CAUGHT: caught={animal.caught}, jump took {animal.frames - state['jump_seen']} frames")
                state["phase"] = "fly"
                state["flight"] = now
        elif state["phase"] == "fly":
            # A dragonfly that flies in a circle for 6 seconds. The gecko should leave it alone.
            t = now - state["flight"]
            x = 400 + 250 * math.cos(t * 2.0)
            y = 300 + 200 * math.sin(t * 2.0)
            animal.canvas.event_generate("<Motion>", x=int(x), y=int(y))
            if t > 6.0:
                note(f"flying for 6 s: caught={animal.caught}, gone={animal.gone}, doing={animal.doing}")
                animal.canvas.event_generate("<Motion>", x=150, y=120)      # it lands far away
                state["phase"] = "rest"
                state["caught_before_escape"] = animal.caught
        elif state["phase"] == "rest":
            if animal.target and not state["escaped"]:
                # The gecko has just started its jump: the dragonfly flies off.
                state["escaped"] = True
                note(f"jump starts, target {[round(v) for v in animal.target]}: the dragonfly flies off")
                animal.canvas.event_generate("<Motion>", x=700, y=500)
            if state["escaped"] and animal.target is None:
                note(f"jump over: caught={animal.caught} (was {state['caught_before_escape']}), gone={animal.gone}")
                finish()
                return
        if now > 75:
            note("TIMED OUT")
            finish()
            return
        animal.root.after(20, watch)

    animal.frame()
    animal.root.after(20, watch)
    animal.root.mainloop()


def scenario_off():
    import hunt
    hunt.MODEL = NOWHERE
    animal = hunt.Animal("model")
    started = time.time()

    def finish():
        print(json.dumps({"doing": animal.doing, "frames": animal.frames, "caught": animal.caught,
                          "head": [round(v) for v in animal.points[0]],
                          "seconds": round(time.time() - started, 1)}))
        animal.root.destroy()

    animal.frame()
    animal.root.after(7000, finish)
    animal.root.mainloop()


def scenario_mouse():
    import hunt
    animal = hunt.Animal("mouse")
    start = [round(v) for v in animal.points[0]]
    animal.root.after(200, lambda: animal.canvas.event_generate("<Motion>", x=600, y=440))

    def finish():
        head = animal.points[0]
        print(json.dumps({"has_doing": hasattr(animal, "doing"), "has_caught": hasattr(animal, "caught"),
                          "head_started": start, "head_now": [round(v) for v in head],
                          "mouse": animal.mouse, "to_mouse": round(math.dist(head, animal.mouse))}))
        animal.root.destroy()

    animal.frame()
    animal.root.after(4000, finish)
    animal.root.mainloop()


def scenario_main():
    real_mainloop = tk.Tk.mainloop

    def short_mainloop(self, n=0):
        self.after(6000, self.destroy)          # close the window by itself after 6 seconds
        real_mainloop(self, n)

    tk.Tk.mainloop = short_mainloop
    runpy.run_path(str(ASSETS / "hunt.py"), run_name="__main__")
    print(json.dumps({"main": "ran for 6 s and closed"}))


def main():
    scenarios = {"hunt": scenario_hunt, "off": scenario_off, "mouse": scenario_mouse, "main": scenario_main}
    if len(sys.argv) != 2 or sys.argv[1] not in scenarios:
        print(__doc__)
        return 2
    sys.path.insert(0, str(ASSETS))
    scenarios[sys.argv[1]]()
    return 0


if __name__ == "__main__":
    sys.exit(main())
