import enum
import random
from dataclasses import dataclass
from typing import List, Tuple

# Python translation by Frank Palazzolo - 2/2021


class Maze:
    def __init__(self, width: int, length: int) -> None:
        assert width >= 2 and length >= 2
        used: List[List[int]] = []
        walls: List[List[int]] = []
        for _ in range(length):
            used.append([0] * width)
            walls.append([0] * width)

        # Pick a random entrance, mark as used
        enter_col = random.randint(0, width - 1)
        used[0][enter_col] = 1

        self.used = used
        self.walls = walls
        self.enter_col = enter_col
        self.width = width
        self.length = length

    def add_exit(self) -> None:
        """Modifies 'walls' to add an exit to the maze."""
        col = random.randint(0, self.width - 1)
        row = self.length - 1
        self.walls[row][col] = self.walls[row][col] + 1

    def display(self) -> None:
        for col in range(self.width):
            if col == self.enter_col:
                print(".  ", end="")
            else:
                print(".--", end="")
        print(".")
        for row in range(self.length):
            print("I", end="")
            for col in range(self.width):
                if self.walls[row][col] < 2:
                    print("  I", end="")
                else:
                    print("   ", end="")
            print()
            for col in range(self.width):
                if self.walls[row][col] == 0 or self.walls[row][col] == 2:
                    print(":--", end="")
                else:
                    print(":  ", end="")
            print(".")


class Direction(enum.Enum):
    LEFT = 0
    UP = 1
    RIGHT = 2
    DOWN = 3


@dataclass
class Position:
    row: int
    col: int


# Give Exit directions nice names
EXIT_DOWN = 1
EXIT_RIGHT = 2


def main() -> None:
    print_intro()
    width, length = get_maze_dimensions()
    maze = build_maze(width, length)
    maze.display()


def print_intro() -> None:
    print(" " * 28 + "AMAZING PROGRAM")
    print(" " * 15 + "CREATIVE COMPUTING  MORRISTOWN, NEW JERSEY\n\n\n")


def build_maze(width: int, length: int) -> Maze:
    """Build two 2D arrays."""
    #
    # used:
    #   Initially set to zero, unprocessed cells
    #   Filled in with consecutive non-zero numbers as cells are processed
    #
    # walls:
    #   Initially set to zero, (all paths blocked)
    #   Remains 0 if there is no exit down or right
    #   Set to 1 if there is an exit down
    #   Set to 2 if there is an exit right
    #   Set to 3 if there are exits down and right
    assert width >= 2 and length >= 2

    maze = Maze(width, length)
    position = Position(row=0, col=maze.enter_col)
    count = 2

    while count != width * length + 1:
        possible_dirs = get_possible_directions(maze, position)

        # If we can move in a direction, move and make opening
        if len(possible_dirs) != 0:
            position, count = make_opening(maze, possible_dirs, position, count)
        # otherwise, move to the next used cell, and try again
        else:
            while True:
                if position.col != width - 1:
                    position.col += 1
                elif position.row != length - 1:
                    position.row, position.col = position.row + 1, 0
                else:
                    position.row, position.col = 0, 0
                if maze.used[position.row][position.col] != 0:
                    break

    maze.add_exit()
    return maze


def make_opening(
    maze: Maze,
    possible_dirs: List[Direction],
    pos: Position,
    count: int,
) -> Tuple[Position, int]:
    """
    Attention! This modifies 'used' and 'walls'
    """
    direction = random.choice(possible_dirs)
    if direction == Direction.LEFT:
        pos.col = pos.col - 1
        maze.walls[pos.row][pos.col] = EXIT_RIGHT
    elif direction == Direction.UP:
        pos.row = pos.row - 1
        maze.walls[pos.row][pos.col] = EXIT_DOWN
    elif direction == Direction.RIGHT:
        maze.walls[pos.row][pos.col] = maze.walls[pos.row][pos.col] + EXIT_RIGHT
        pos.col = pos.col + 1
    elif direction == Direction.DOWN:
        maze.walls[pos.row][pos.col] = maze.walls[pos.row][pos.col] + EXIT_DOWN
        pos.row = pos.row + 1
    maze.used[pos.row][pos.col] = count
    count = count + 1
    return pos, count


def get_possible_directions(maze: Maze, pos: Position) -> List[Direction]:
    """
    Get a list of all directions that are not blocked.

    Also ignore hit cells that we have already processed
    """
    possible_dirs = list(Direction)
    if pos.col == 0 or maze.used[pos.row][pos.col - 1] != 0:
        possible_dirs.remove(Direction.LEFT)
    if pos.row == 0 or maze.used[pos.row - 1][pos.col] != 0:
        possible_dirs.remove(Direction.UP)
    if pos.col == maze.width - 1 or maze.used[pos.row][pos.col + 1] != 0:
        possible_dirs.remove(Direction.RIGHT)
    if pos.row == maze.length - 1 or maze.used[pos.row + 1][pos.col] != 0:
        possible_dirs.remove(Direction.DOWN)
    return possible_dirs


def get_maze_dimensions() -> Tuple[int, int]:
    while True:
        input_str = input("What are your width and length?")
        if input_str.count(",") == 1:
            width_str, length_str = input_str.split(",")
            width = int(width_str)
            length = int(length_str)
            if width > 1 and length > 1:
                break
        print("Meaningless dimensions. Try again.")
    return width, length


if __name__ == "__main__":
    main()
