#!/usr/bin/env python3
"""Print the six map examples from 'rooms and corridors'. Python 3 only."""

import random
from dataclasses import dataclass

WIDTH, HEIGHT = 28, 17


@dataclass
class Room:
    x: int
    y: int
    width: int
    height: int

    @property
    def centre(self):
        return self.x + self.width // 2, self.y + self.height // 2

    def too_close(self, other):
        return (self.x < other.x + other.width + 1
                and self.x + self.width + 1 > other.x
                and self.y < other.y + other.height + 1
                and self.y + self.height + 1 > other.y)


def blank():
    return [["#"] * WIDTH for _ in range(HEIGHT)]


def carve_room(grid, room):
    for y in range(room.y, room.y + room.height):
        for x in range(room.x, room.x + room.width):
            grid[y][x] = "."


def tunnel(grid, start, end, rng):
    x, y = start
    ex, ey = end
    axes = ["x", "y"]
    rng.shuffle(axes)
    for axis in axes:
        while (x if axis == "x" else y) != (ex if axis == "x" else ey):
            if axis == "x":
                x += 1 if ex > x else -1
            else:
                y += 1 if ey > y else -1
            grid[y][x] = "."


def labelled(grid, rooms):
    result = [row[:] for row in grid]
    for i, room in enumerate(rooms):
        x, y = room.centre
        result[y][x] = chr(ord("A") + i)
    return result


def scattered_rooms(seed=13):
    rng = random.Random(seed)
    rooms = []
    for _ in range(120):
        w, h = rng.randint(4, 7), rng.randint(3, 5)
        candidate = Room(rng.randint(1, WIDTH - w - 1),
                         rng.randint(1, HEIGHT - h - 1), w, h)
        if any(candidate.too_close(other) for other in rooms):
            continue
        rooms.append(candidate)
        if len(rooms) == 6:
            break
    if not rooms:
        raise RuntimeError("No rooms fit")
    grid = blank()
    for room in rooms:
        carve_room(grid, room)
    before = labelled(grid, rooms)
    for i in range(1, len(rooms)):
        x, y = rooms[i].centre
        nearest = min(rooms[:i], key=lambda r: abs(r.centre[0] - x)
                      + abs(r.centre[1] - y))
        tunnel(grid, (x, y), nearest.centre, rng)
    return before, labelled(grid, rooms), rooms


def partitioned_rooms(seed=7):
    rng = random.Random(seed)
    guide = [[" "] * WIDTH for _ in range(HEIGHT)]
    for x in range(WIDTH):
        guide[0][x] = guide[-1][x] = "-"
    for y in range(HEIGHT):
        guide[y][0] = guide[y][-1] = "|"
    for y in (0, HEIGHT - 1):
        for x in (0, WIDTH - 1):
            guide[y][x] = "+"
    grid, rooms = blank(), []

    def split(x, y, w, h, depth):
        if depth == 2:
            rw, rh = rng.randint(3, min(7, w - 2)), rng.randint(3, h - 2)
            room = Room(rng.randint(x + 1, x + w - rw - 1),
                        rng.randint(y + 1, y + h - rh - 1), rw, rh)
            letter = chr(ord("A") + len(rooms))
            guide[y + h // 2][x + w // 2] = letter
            rooms.append(room)
            carve_room(grid, room)
            return room
        if depth == 0:
            cut = rng.randint(9, w - 10)
            for yy in range(y, y + h):
                guide[yy][x + cut] = "|"
            a = split(x, y, cut, h, depth + 1)
            b = split(x + cut + 1, y, w - cut - 1, h, depth + 1)
        else:
            cut = rng.randint(6, h - 7)
            for xx in range(x, x + w):
                guide[y + cut][xx] = "-"
            a = split(x, y, w, cut, depth + 1)
            b = split(x, y + cut + 1, w, h - cut - 1, depth + 1)
        tunnel(grid, a.centre, b.centre, rng)
        return rng.choice([a, b])

    split(1, 1, WIDTH - 2, HEIGHT - 2, 0)
    return guide, labelled(grid, rooms), rooms


def template_room():
    # + is a door socket. Keep it attached to the room when rotating.
    return [list(row) for row in (
        "######+######",
        "#...........#",
        "#..#.....#..#",
        "#...........#",
        "+.....#.....#",
        "#...........#",
        "#..#.....#..#",
        "#...........#",
        "#############",
    )]


def rotate_clockwise(grid):
    return [list(row) for row in zip(*grid[::-1])]


def examples():
    scattered, connected, _ = scattered_rooms()
    partitions, bsp, _ = partitioned_rooms()
    template = template_room()
    return {
        "scattered rooms": scattered,
        "scattered rooms, connected": connected,
        "partition guide": partitions,
        "rooms in the partitions": bsp,
        "a room template": template,
        "the template turned clockwise": rotate_clockwise(template),
    }


if __name__ == "__main__":
    for label, grid in examples().items():
        print(label)
        print("\n".join("".join(row) for row in grid))
        print()
