# Principled Programming / Tim Teitelbaum / Chapter 17.
from mrp import MRP
import random

class RunMaze:
    """# Rat running. See Chapter 15 of text."""

    # CLIENT CODE UNIQUE TO DEPTH-FIRST SEARCH SOLUTION
    @classmethod
    def _solve(cls) -> None:
        """Compute a direct path through the maze, if one exists."""
        MRP.search()

    # LEGACY CLIENT CODE (Chapter 15)
    @classmethod
    def main(cls) -> None:
            """Run a maze given as input, if possible."""
        # Input a maze of arbitrary size, or output “malformed input”
        #   and stop if the input is improper. Input format: TBD.
            RunMaze._input()
        # Compute a direct path through the maze, if one exists.
            RunMaze._solve()
        # Output the direct path found, or “unreachable” if there is
        #    none. Output format: TBD.
            RunMaze._output()
        # Stop execution if path found is not a solution.
            assert MRP.is_solution(), "internal program error"

    @classmethod
    def _input(cls) -> None:
        """# Input a maze of arbitrary size, or output “malformed input”
        and stop if the input is improper.
        """
        MRP.input()

    @classmethod
    def _output(cls) -> None:
        """Output the direct path found, or “unreachable” if there is none."""
        if not(MRP.is_at_cheese()): print ("Unreachable")
        else: MRP.print_maze()

    @classmethod
    def exhaustive_test(cls) -> None:
        """Generate/solve all mazes of sizes up through 3, and validate paths found."""
        for N in range(1, 5):
            for w in range(0, 2 ** (2 * N *(N - 1))):
                MRP.generate_input(N, w)
                RunMaze._solve()
                assert MRP.is_solution(), "internal program error"
                if (w > 0) and (w%100000 == 0): print(w) # Heartbeat.
            print("passed for size:", N)
        print("passed")

    @classmethod
    def fuzz_test(cls, f: int, n: int) -> None:
        """Fuzz F mazes of size N."""
        for k in range(0, f): RunMaze.random_test(n)

    @classmethod
    def random_test(cls, n: int) -> None:
            """Create a random maze of size N."""
        # Let w be random walls for the N-by-N maze.
            w = random.randint(0, 2 ** (2 * n * (n - 1)))
        # Generate maze of size N with walls w, solve, and validate (or abort).
            print("Size:", n, "Walls:", w) # (Comment out for serious test.)
            MRP.generate_input(n, w)       # Create N-by-N maze with walls w.
            RunMaze._solve()               # Attempt a solution.
            assert MRP.is_solution()       # Validate solution (or abort).
            MRP.print_maze()               # (Comment out for serious test.)

# Uncomment to run on builtin example.    
RunMaze.main()

# Uncomment to run on all mazes of size <= 4.
#RunMaze.exhaustive_test()

# Uncomment to run on 25 random mazes of size 5.
#RunMaze.fuzz_test(25, 5)