from kandinsky import fill_rect, draw_string
from ion import keydown
from random import randint
from time import monotonic, sleep

# A 32 x 20 board leaves room for a status line on the NumWorks screen.
WIDTH = 32
HEIGHT = 20
CELL = 10
TOP = 22
BACKGROUND = (234, 240, 235)
ALIVE = (28, 105, 76)
CURSOR = (227, 132, 47)
INK = (29, 48, 40)
PAPER = (250, 252, 249)
INTERVAL = 0.4


def empty_grid():
  return [[0 for x in range(WIDTH)] for y in range(HEIGHT)]


def random_grid():
  return [[1 if randint(0, 3) == 0 else 0 for x in range(WIDTH)] for y in range(HEIGHT)]


def next_grid(board):
  # Cells beyond the edge are dead; they do not wrap around the board.
  result = empty_grid()
  for y in range(HEIGHT):
    for x in range(WIDTH):
      neighbors = 0
      for dy in (-1, 0, 1):
        for dx in (-1, 0, 1):
          nx, ny = x + dx, y + dy
          if (dx or dy) and 0 <= nx < WIDTH and 0 <= ny < HEIGHT:
            neighbors += board[ny][nx]
      result[y][x] = 1 if neighbors == 3 or (board[y][x] and neighbors == 2) else 0
  return result


def pattern_grid(index):
  board = empty_grid()
  patterns = [
    # Glider, blinker, lightweight spaceship, R-pentomino, pulsar.
    [(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)],
    [(0, 0), (1, 0), (2, 0)],
    [(1, 0), (4, 0), (0, 1), (0, 2), (4, 2), (0, 3), (1, 3), (2, 3), (3, 3)],
    [(1, 0), (2, 0), (0, 1), (1, 1), (1, 2)],
  ]
  if index == 4:
    points = []
    for a in (2, 3, 4, 8, 9, 10):
      for b in (0, 5, 7, 12):
        points.append((a, b))
        points.append((b, a))
  else:
    points = patterns[index]
  max_x = max(point[0] for point in points)
  max_y = max(point[1] for point in points)
  left = (WIDTH - max_x - 1) // 2
  top = (HEIGHT - max_y - 1) // 2
  for x, y in points:
    board[top + y][left + x] = 1
  return board


def paint_cell(board, x, y, cursor):
  px, py = x * CELL, TOP + y * CELL
  fill_rect(px, py, CELL, CELL, CURSOR if cursor else BACKGROUND)
  fill_rect(px + 1, py + 1, CELL - 2, CELL - 2, ALIVE if board[y][x] else PAPER)


def paint_board(board, cursor_x, cursor_y, editing):
  for y in range(HEIGHT):
    for x in range(WIDTH):
      paint_cell(board, x, y, editing and x == cursor_x and y == cursor_y)


def paint_status(board, generation, running):
  population = sum(sum(row) for row in board)
  label = ("RUN " if running else "EDIT") + " G:" + str(generation) + " N:" + str(population)
  fill_rect(0, 0, 320, TOP, PAPER)
  draw_string(label[:32], 0, 2, INK, PAPER)


board = random_grid()
generation = 0
cursor_x, cursor_y = WIDTH // 2, HEIGHT // 2
running = True
pattern = -1
held = set()
last_step = monotonic()
paint_board(board, cursor_x, cursor_y, not running)
paint_status(board, generation, running)

while True:
  # Arrow keys move the cursor; other keys act once per press.
  keys = [0, 1, 2, 3, 4, 12, 13, 14, 16, 17]
  down = set(key for key in keys if keydown(key))
  pressed = down - held
  held = down
  old_x, old_y = cursor_x, cursor_y
  if not running:
    if 0 in pressed: cursor_x = max(0, cursor_x - 1)
    if 3 in pressed: cursor_x = min(WIDTH - 1, cursor_x + 1)
    if 1 in pressed: cursor_y = max(0, cursor_y - 1)
    if 2 in pressed: cursor_y = min(HEIGHT - 1, cursor_y + 1)
    if (old_x, old_y) != (cursor_x, cursor_y):
      paint_cell(board, old_x, old_y, False)
      paint_cell(board, cursor_x, cursor_y, True)
    if 4 in pressed:
      board[cursor_y][cursor_x] = 1 - board[cursor_y][cursor_x]
      paint_cell(board, cursor_x, cursor_y, True)
      paint_status(board, generation, running)
  if 14 in pressed:
    running = not running
    paint_cell(board, cursor_x, cursor_y, not running)
    paint_status(board, generation, running)
    last_step = monotonic()
  if 13 in pressed or 16 in pressed or 17 in pressed:
    if 13 in pressed:
      pattern = (pattern + 1) % 5
      board = pattern_grid(pattern)
    elif 16 in pressed:
      board = empty_grid()
    else:
      board = random_grid()
    generation = 0
    running = False
    paint_board(board, cursor_x, cursor_y, True)
    paint_status(board, generation, running)
  if (12 in pressed and not running) or (running and monotonic() - last_step >= INTERVAL):
    new_board = next_grid(board)
    for y in range(HEIGHT):
      for x in range(WIDTH):
        if new_board[y][x] != board[y][x]:
          paint_cell(new_board, x, y, not running and x == cursor_x and y == cursor_y)
    board = new_board
    generation += 1
    paint_status(board, generation, running)
    last_step = monotonic()
  sleep(0.02)
