"""measure.py - where the numbers on the page come from.

    python measure.py

Put this file in the same folder as the nine tutorial files. It builds the
real classes, moves them frame by frame with no person at the mouse, and counts.
Windows flash open and shut while it runs. Nothing here is part of the lesson.
It asks the model nothing: in the hunt, the moves are set by hand.
"""
import math

import arm
import hunt
import lizard
import snake


def reach_test():
    """Step 6: how close is the hand after 1, 2, 3 ... calls to reach()?"""
    joints = [[400, 300 - i * 90] for i in range(4)]    # straight up from the shoulder
    target = [520, 380]
    print("reach(): pixels from the hand to the target")
    for calls in range(1, 7):
        arm.reach(joints, [400, 300], target, 90)
        print(f"  after {calls}: {math.dist(joints[3], target):.2f}")


def snake_test(turn):
    """Step 4: let the mouse jump between four corners, and watch the spine."""
    snake.TURN = turn
    animal = snake.Snake()
    corners = [[650, 120], [150, 480], [650, 480], [150, 120]]
    biggest_jump = 0
    at_limit = 0
    joint_frames = 0
    for frame in range(900):
        animal.mouse = corners[frame // 90 % 4]
        before = [point[:] for point in animal.points]
        animal.move()
        for i in range(len(animal.points)):
            biggest_jump = max(biggest_jump, math.dist(before[i], animal.points[i]))
        if before[0] == animal.points[0]:
            continue                    # it was waiting at the mouse, so no joint moved
        for i in range(1, len(animal.angles)):
            bend = animal.angles[i] - animal.angles[i - 1]
            bend = (bend + math.pi) % (2 * math.pi) - math.pi     # the short way round
            joint_frames += 1
            if abs(bend) > snake.BEND - 0.000001:
                at_limit += 1
    animal.root.destroy()
    share = 100 * at_limit / joint_frames
    print(f"  TURN = {turn:<5} biggest jump of one point in one frame: {biggest_jump:6.1f} px"
          f"   joints at the BEND limit: {share:4.1f}%")


def lizard_test(stagger):
    """Step 7: on a straight walk, in which frames does each leg take a step?"""
    animal = lizard.Animal("lizard")
    if not stagger:
        for leg in animal.legs:         # put every foot where a new step lands
            hip, ahead = animal.leg_places(leg)
            leg["ground"] = ahead
    animal.mouse = [100000, 300]        # far away to the right, so it walks straight
    steps = [[], [], [], []]
    for frame in range(60):
        before = [leg["ground"] for leg in animal.legs]
        animal.move()
        animal.move_legs()
        for i in range(4):
            if animal.legs[i]["ground"] != before[i]:
                steps[i].append(frame)
    animal.root.destroy()
    for i in range(4):
        print(f"  leg {i} steps at frames {steps[i]}")


def hunt_frames(animal):
    """Run hunt() until the head stops moving and no jump is on. Count the frames."""
    frames = 0
    while True:
        before = animal.points[0]
        animal.hunt()
        if animal.points[0] == before and animal.target is None:
            return frames
        frames += 1


def hunt_test():
    """Step 9: creep, jump, catch and miss, with each move set by hand."""
    animal = hunt.Animal("model")
    animal.asking = True                # as if a question is always on its way: it never asks
    head = animal.points[0]
    print(f"  at the start the dragonfly is {math.dist(head, animal.mouse):.0f} px away")
    print("  sees:", animal.sees())
    animal.still = hunt.REST
    print("  after", hunt.REST, "still frames, sees:", animal.sees())
    animal.doing = "creep"
    frames = hunt_frames(animal)
    far = math.dist(animal.points[0], animal.mouse)
    print(f"  creep: the head stops after {frames} frames, {far:.1f} px away")
    print("  sees:", animal.sees())
    animal.doing = "jump"
    frames = hunt_frames(animal)
    print(f"  jump: over after {frames} frames, caught {animal.caught}, gone {animal.gone}")

    # A new dragonfly, 300 px away, and a jump from too far.
    head = animal.points[0]
    animal.mouse = [head[0] - 300, head[1]]
    animal.gone = False
    animal.doing = "jump"
    frames = hunt_frames(animal)
    far = math.dist(animal.points[0], animal.mouse)
    print(f"  jump from 300 px: over after {frames} frames, lands {far:.1f} px short,"
          f" caught {animal.caught}")

    # Creep into reach, start a jump, and let the dragonfly fly off in the first frame.
    animal.doing = "creep"
    hunt_frames(animal)
    animal.doing = "jump"
    animal.hunt()                       # this frame picks the target
    animal.mouse = [animal.mouse[0], animal.mouse[1] + 200]
    frames = 1 + hunt_frames(animal)
    print(f"  jump at a dragonfly that flies off: over after {frames} frames,"
          f" caught {animal.caught}")
    animal.root.destroy()
    walker = hunt.Animal("mouse")
    print("  a 'mouse' gecko has the hunter's parts:", hasattr(walker, "doing"))
    walker.root.destroy()


def main():
    reach_test()
    print("snake.py: the mouse jumps between four corners for 900 frames")
    for turn in [0.05, 0.15, 99]:       # 99 is "no limit": straight at the mouse
        snake_test(turn)
    print("lizard.py: a straight walk, as the file is")
    lizard_test(True)
    print("lizard.py: a straight walk, with all four feet starting together")
    lizard_test(False)
    fish = lizard.Animal("fish")
    print("a fish has a legs list:", hasattr(fish, "legs"))
    fish.root.destroy()
    try:
        lizard.Animal("fsh")
    except AttributeError as problem:
        print("Animal('fsh') ->", type(problem).__name__ + ":", problem)
    print("hunt.py: the moves set by hand, one after another")
    hunt_test()


if __name__ == "__main__":
    main()
