#! /usr/bin/env python3

import time
import random
import copy

print_chars = [' ', 'o', '+', 'o']

def main():
    nx = 78
    ny = 23
    n_states = 2
    n_iterations = 1000
    pause_seconds = 2.0/24.0

    board = new_board(nx, ny)
    set_initial_board_random(board, n_states)
    # set_initial_specific_cells(board, n_states,
    #                            [(8, 13), (9, 13), (10, 13)])
    # set_initial_board_glider(board, 40, 12)
    draw_board_text(board)
    for i in range(n_iterations):
        (board, chanaged_cells) = apply_rule_to_board(board, rule_conway, 3, 3)
        draw_board_text(board)
        time.sleep(pause_seconds)

def new_board(nx, ny):
    board = []
    for row_no in range(nx):
        row = []
        for cell_no in range(ny):
            row.append(0)
        board.append(row)
    return board

def draw_board_text(board):
    nx, ny = len(board), len(board[0])
    print('')
    print('-'*nx)
    for j in range(ny):
        for i in range(nx):
            print('o' if board[i][j] else ' ', end="")
        print()

def apply_rule_to_board(old_board, rule_func, neigh_x, neigh_y):
    changed_cells = []
    board = copy.deepcopy(old_board)
    nx, ny = len(board), len(board[0])
    for i in range(nx):
        for j in range(ny):
            old_cell = old_board[i][j]
            neighborhood = extract_neighborhood(old_board, i, j, neigh_x, neigh_y)
            board[i][j] = rule_func(neighborhood, neigh_x, neigh_y)
            new_cell = board[i][j]
            if new_cell != old_cell:
                changed_cells.append((i, j))
    return (board, changed_cells)

def rule_conway(neighborhood, neigh_x, neigh_y):
    assert(neigh_x == 3 and neigh_y == 3)
    assert(len(neighborhood) == neigh_x and len(neighborhood[0]) == neigh_y)
    old_cell = neighborhood[1][1]
    n_neighbors = 0
    for i in range(neigh_x):
        for j in range(neigh_y):
            if i == 1 and j == 1: # don't count yourself
                continue
            n_neighbors += neighborhood[i][j]
            if old_cell == 1 and n_neighbors in (2, 3):
                new_cell = 1
            elif old_cell == 0 and n_neighbors == 3:
                new_cell = 1
            else:
                new_cell = 0
    return new_cell

def rule_snowflake(neighborhood, neigh_x, neigh_y):
    """Apply a rule that should yield a snowflake, like that at page 371
    of Wolfram's "A New Kind of Science

    """
    assert(neigh_x == 3 and neigh_y == 3)
    assert(len(neighborhood) == neigh_x and len(neighborhood[0]) == neigh_y)
    old_cell = neighborhood[1][1]
    n_neighbors = 0
    n_neighbors += old_cell
    for (i, j) in ((0, 1), (1, 0), (2, 1), (1, 2)):
        n_neighbors += neighborhood[i][j]
    if old_cell == 1 or n_neighbors >= 1:
        new_cell = 1
    else:
        new_cell = 0
    return new_cell

def extract_neighborhood(board, x, y, nx, ny):
    board_nx = len(board)
    board_ny = len(board[0])
    neighborhood = []
    for i in range(nx):
        neighborhood.append([])
        for j in range(ny):
            neighborhood[i].append(board[(x - int(nx/2) + i + board_nx) % board_nx]
                                   [(y - int(ny/2) + j + board_ny) % board_ny])
    return neighborhood

def set_initial_board_random(board, n_states):
    nx, ny = len(board), len(board[0])
    for row_no in range(nx):
        for col_no in range(ny):
            board[row_no][col_no] = random.randint(0, n_states-1)

def set_initial_specific_cells(board, n_states, list_of_points):
    nx, ny = len(board), len(board[0])
    for pt in list_of_points:
        board[pt[0]][pt[1]] = 1

def set_initial_board_glider(board, centerx, centery):
    positions = [(centerx-1, centery+1), (centerx, centery+1),
                 (centerx+1, centery+1), (centerx-1, centery),
                 (centerx, centery-1)]
    set_initial_specific_cells(board, 2, positions)

def set_initial_board_lwss(board, centerx, centery):
    """puts a light weight spaceship on the board"""
    positions = [(centerx-2, centery-2),
                 (centerx-2, centery),
                 (centerx-1, centery+1),
                 (centerx, centery+1),
                 (centerx+1, centery-2),
                 (centerx+1, centery+1),
                 (centerx+2, centery-1),
                 (centerx+2, centery),
                 (centerx+2, centery+1)]
    set_initial_specific_cells(board, 2, positions)

def set_initial_board_one_cell_thick(board, startx, starty, direction_x, length):
    positions = []
    ## FIXME: must make things wrap around
    for i in range(length):
        x, y = startx, starty
        if direction_x:
            x += i
        else:
            y += i
        positions.append((x,y))
    set_initial_specific_cells(board, 2, positions)


if __name__ == '__main__':
    main()
