"""Part 5: the cleaning robot on a floor with a twist.

The room is given as strings, as in Part 1, and its right edge is glued to its left edge upside
down: walking off the right end of row r brings the robot back on the left end of row H - 1 - r,
the mirror-image row. The top and bottom rows end at the strip's edges, which work like walls.
There is no wall round the room otherwise. '#' is furniture, '.' floor and '*' the start.

The robot's commands '^ > v <' are its own directions. It has no compass, only the arrow painted
on it, and it doesn't turn: after crossing the seam, its '^' points down the map.
"""
from surface import DIRS, depth_first, frames_reachable, mobius, move


def solution(room):
    """Part 1's walk, translated into the robot's own directions as it goes: 2(N - 1) commands."""
    s, start = mobius(room)
    return depth_first(s, start)


def map_walk(surface, start):
    """The same walk, written in the map's directions, as Part 1 would write it. Correct on a
    floor without a twist; on a Mobius strip the robot reads it in its own frame."""
    seen, moves, trail, field = {start}, [], [], start
    while True:
        for side in (0, 2, 3, 1):
            g = surface.glue[field][side]
            if g is not None and g[0] not in seen:
                seen.add(g[0])
                moves.append(DIRS[side])            # the map's direction for this side
                trail.append((DIRS[g[1]], field))   # and the map's direction back, from g
                field = g[0]
                break
        else:
            if not trail:
                return ''.join(moves)
            back, field = trail.pop()
            moves.append(back)


def first_mistake(surface, start, commands):
    """Replay commands the way the robot reads them. Returns (index, what went wrong) at the
    first command that hits a wall or lands somewhere the map didn't mean, or None."""
    field, frame = start, (0, False)
    for i, ch in enumerate(commands):
        meant = surface.glue[field][DIRS.index(ch)]  # where the map's direction leads
        step = move(surface, field, frame, ch)          # where the robot's direction leads
        if step is None:
            return i, 'wall'
        if meant is None or step[0] != meant[0]:
            return i, 'elsewhere'
        field, frame = step
    return None


def orientable(surface, start):
    """True unless the floor has a loop that brings the robot back mirrored: then the robot can
    stand on every field both ways round, 2N (field, mirrored) pairs instead of N."""
    pairs = {(f, m) for f, (_, m) in frames_reachable(surface, start)}
    return len(pairs) == len({f for f, _ in pairs})
