import tkinter as tk
import math
import random

# --- Configuration ---
CANVAS_SIZE = 800
MAZE_LEVELS = 15  # Number of concentric rings in the maze
BACKGROUND_COLOR = '#F0F0F0'
WALL_COLOR = '#333333'
WALL_WIDTH = 2
SOLUTION_COLOR = '#E74C3C' # Color for the solution path (optional)

class CircularMaze:
    """
    A class to generate and hold the data for a circular maze.
    The maze is represented as a grid of cells, where each cell is a sector
    of a concentric ring.
    """

    def __init__(self, levels, sectors_in_first_level=8):
        """
        Initializes the circular maze structure.

        Args:
            levels (int): The number of concentric rings (levels).
            sectors_in_first_level (int): The number of cells in the innermost ring.
                                          Each subsequent ring will have more sectors.
        """
        self.levels = levels
        self.grid = []
        self.solution = {}

        # Create a grid where outer rings have more cells to keep cell sizes more uniform.
        # The number of sectors in a ring is proportional to its radius.
        for r in range(levels):
            sectors = sectors_in_first_level * (r + 1)
            # Each cell has two walls: 'cw' (clockwise) and 'out' (outward).
            # A 'True' value means the wall exists.
            self.grid.append([{'cw': True, 'out': True} for _ in range(sectors)])

    def get_neighbors(self, r, c):
        """
        Finds all valid neighbors for a given cell (r, c).
        This handles the complexity of connecting cells between rings of
        different sector counts.
        """
        neighbors = []
        sectors_curr = len(self.grid[r])
        
        # Clockwise neighbor
        neighbors.append(((r, (c + 1) % sectors_curr), 'cw'))
        # Counter-clockwise neighbor
        neighbors.append(((r, (c - 1 + sectors_curr) % sectors_curr), 'ccw'))

        # Outward neighbors
        if r + 1 < self.levels:
            sectors_next = len(self.grid[r+1])
            ratio = sectors_next / sectors_curr
            for i in range(int(ratio)):
                neighbors.append(((r + 1, int(c * ratio) + i), 'out'))

        # Inward neighbors
        if r > 0:
            sectors_prev = len(self.grid[r-1])
            ratio = sectors_curr / sectors_prev
            neighbors.append(((r - 1, int(c / ratio)), 'in'))
            
        return neighbors

    def generate(self):
        """
        Generates the maze using a randomized depth-first search (DFS) algorithm.
        It carves paths by removing walls between cells.
        """
        start_r, start_c = random.randint(0, self.levels - 1), 0
        stack = [(start_r, start_c)]
        visited = set([(start_r, start_c)])

        while stack:
            current_r, current_c = stack[-1]
            
            # Find unvisited neighbors
            unvisited_neighbors = []
            for (nr, nc), direction in self.get_neighbors(current_r, current_c):
                if 0 <= nr < self.levels and 0 <= nc < len(self.grid[nr]):
                    if (nr, nc) not in visited:
                        unvisited_neighbors.append(((nr, nc), direction))

            if unvisited_neighbors:
                # Choose a random unvisited neighbor
                (next_r, next_c), direction = random.choice(unvisited_neighbors)
                
                # Remove the wall between the current cell and the chosen neighbor
                if direction == 'cw':
                    self.grid[current_r][current_c]['cw'] = False
                elif direction == 'ccw':
                    self.grid[next_r][next_c]['cw'] = False
                elif direction == 'out':
                    self.grid[current_r][current_c]['out'] = False
                elif direction == 'in':
                    self.grid[next_r][next_c]['out'] = False

                self.solution[(next_r, next_c)] = (current_r, current_c)
                visited.add((next_r, next_c))
                stack.append((next_r, next_c))
            else:
                # Backtrack if there are no unvisited neighbors
                stack.pop()

        # Create an entrance and an exit
        self.grid[0][0]['cw'] = False # Entrance at the center
        self.grid[self.levels-1][0]['out'] = False # Exit at the outer edge


    def draw(self, canvas):
        """
        Draws the entire maze on the provided tkinter canvas.
        """
        canvas.delete("all")
        width = int(canvas.cget("width"))
        height = int(canvas.cget("height"))
        center_x, center_y = width / 2, height / 2
        
        # The first ring is a gap, so we start drawing from an offset
        ring_thickness = (min(width, height) / 2) / (self.levels + 1)
        
        # Iterate through each cell to draw its walls
        for r, level in enumerate(self.grid):
            sectors = len(level)
            angle_step = 360 / sectors
            
            for c, cell in enumerate(level):
                radius_inner = (r + 1) * ring_thickness
                radius_outer = (r + 2) * ring_thickness
                
                angle_start = c * angle_step
                angle_end = (c + 1) * angle_step
                
                # Draw the outward wall (an arc)
                if cell['out']:
                    canvas.create_arc(
                        center_x - radius_outer, center_y - radius_outer,
                        center_x + radius_outer, center_y + radius_outer,
                        start=angle_start, extent=angle_step,
                        style=tk.ARC, outline=WALL_COLOR, width=WALL_WIDTH
                    )

                # Draw the clockwise wall (a radial line)
                if cell['cw']:
                    x1 = center_x + radius_inner * math.cos(math.radians(angle_end))
                    y1 = center_y + radius_inner * math.sin(math.radians(angle_end))
                    x2 = center_x + radius_outer * math.cos(math.radians(angle_end))
                    y2 = center_y + radius_outer * math.sin(math.radians(angle_end))
                    canvas.create_line(x1, y1, x2, y2, fill=WALL_COLOR, width=WALL_WIDTH)


def generate_and_draw_maze():
    """
    Function to be called by the button to generate and draw a new maze.
    """
    maze = CircularMaze(MAZE_LEVELS)
    maze.generate()
    maze.draw(canvas)

# --- Main Application Setup ---
if __name__ == "__main__":
    root = tk.Tk()
    root.title("Circular Maze Generator")
    root.configure(bg=BACKGROUND_COLOR)

    # --- UI Frame ---
    ui_frame = tk.Frame(root, bg=BACKGROUND_COLOR)
    ui_frame.pack(pady=10)

    title_label = tk.Label(ui_frame, text="Circular Maze Generator", font=("Helvetica", 16), bg=BACKGROUND_COLOR)
    title_label.pack(pady=(0, 10))

    generate_button = tk.Button(
        ui_frame, 
        text="Generate New Maze", 
        font=("Helvetica", 12),
        command=generate_and_draw_maze
    )
    generate_button.pack()

    # --- Canvas for Maze ---
    canvas_frame = tk.Frame(root)
    canvas_frame.pack(expand=True, fill=tk.BOTH, padx=20, pady=20)
    
    canvas = tk.Canvas(
        canvas_frame, 
        width=CANVAS_SIZE, 
        height=CANVAS_SIZE, 
        bg='white', 
        highlightthickness=0
    )
    canvas.pack(expand=True)

    # Generate the first maze on startup
    generate_and_draw_maze()

    root.mainloop()
