#!/usr/bin/env python

import sys
sys.path.append('..')
from libs.helpers import get_input
from collections import Counter
import numpy as np


def simulate(grid, neighbours, maxocc):
    changed = True
    steps = 0
    cache = {}
    while changed:
        steps += 1
        print(f'Step {steps}')
        grid,changed = change(grid, neighbours, maxocc, cache)
        # printgrid(grid)
    return grid


def neighbourssimple(grid, x,y):
    ns = [(x-1,y-1), (x, y-1), (x+1, y-1), (x-1, y), (x+1, y), (x-1, y+1), (x,y+1), (x+1,y+1)]
    return [(px,py) for px,py in filter(lambda p: 0 <= p[0] < len(grid[0]) and 0 <= p[1] < len(grid), ns)]


def change(grid, neighbours, maxocc, cache = {}):
    ng = [[c for c in l] for l in grid]
    changed = False
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            if grid [y][x] == '.':
                continue
            if (x,y) not in cache:
                nbs = neighbours(grid, x,y)
                cache[(x,y)] = nbs
            nbs = [grid[py][px] for px,py in cache[(x,y)]]
            nbs = Counter(nbs)
            if grid[y][x] == 'L' and '#' not in nbs:
                ng[y][x] = '#'
                changed = True
            elif grid[y][x] == '#' and '#' in nbs and nbs['#'] >= maxocc:
                ng[y][x] = 'L'
                changed = True
    return (ng, changed)


def printgrid(grid):
    for g in grid:
        print(''.join(g))

vecs = [
            [-1,-1],
            [0,-1],
            [+1,-1],
            [-1,0],
            [+1,0],
            [-1,+1],
            [0,+1],
            [+1,+1]]
vectors = [np.array(v) for v in vecs]

def nbscomplex(grid, x, y):
    nbs = []
    for v in vectors:
        for i in range(1,max(len(grid),len(grid[0]))):
            vx,vy = v * i
            nx = x + vx
            ny = y + vy
            if (nx not in range(len(grid[0]))) or (ny not in range(len(grid))):
                break
            c = grid[ny][nx]
            if c == 'L' or c == '#':
                nbs.append((nx,ny))
                break
    return nbs


lines = get_input('input.txt')
grid = [[c for c in l] for l in lines]
g = simulate(grid, neighbourssimple, 4)
s = sum(sum(1 if c == '#' else 0 for c in l) for l in g)
print(s)
g = simulate(grid, nbscomplex, 5)
s = sum(sum(1 if c == '#' else 0 for c in l) for l in g)
print(s)

#!/usr/bin/env python

import sys
sys.path.append('..')
from libs.helpers import get_input
from collections import Counter
import numpy as np


def simulate(grid, neighbours, maxocc):
    changed = True
    steps = 0
    cache = {}
    while changed:
        steps += 1
        print(f'Step {steps}')
        grid,changed = change(grid, neighbours, maxocc, cache)
        # printgrid(grid)
    return grid


def neighbourssimple(grid, x,y):
    ns = [(x-1,y-1), (x, y-1), (x+1, y-1), (x-1, y), (x+1, y), (x-1, y+1), (x,y+1), (x+1,y+1)]
    return [(px,py) for px,py in filter(lambda p: 0 <= p[0] < len(grid[0]) and 0 <= p[1] < len(grid), ns)]


def change(grid, neighbours, maxocc, cache = {}):
    ng = [[c for c in l] for l in grid]
    changed = False
    for y in range(len(grid)):
        for x in range(len(grid[y])):
            if grid [y][x] == '.':
                continue
            if (x,y) not in cache:
                nbs = neighbours(grid, x,y)
                cache[(x,y)] = nbs
            nbs = [grid[py][px] for px,py in cache[(x,y)]]
            nbs = Counter(nbs)
            if grid[y][x] == 'L' and '#' not in nbs:
                ng[y][x] = '#'
                changed = True
            elif grid[y][x] == '#' and '#' in nbs and nbs['#'] >= maxocc:
                ng[y][x] = 'L'
                changed = True
    return (ng, changed)


def printgrid(grid):
    for g in grid:
        print(''.join(g))

vecs = [
            [-1,-1],
            [0,-1],
            [+1,-1],
            [-1,0],
            [+1,0],
            [-1,+1],
            [0,+1],
            [+1,+1]]
vectors = [np.array(v) for v in vecs]

def nbscomplex(grid, x, y):
    nbs = []
    for v in vectors:
        for i in range(1,max(len(grid),len(grid[0]))):
            vx,vy = v * i
            nx = x + vx
            ny = y + vy
            if (nx not in range(len(grid[0]))) or (ny not in range(len(grid))):
                break
            c = grid[ny][nx]
            if c == 'L' or c == '#':
                nbs.append((nx,ny))
                break
    return nbs


lines = get_input('input.txt')
grid = [[c for c in l] for l in lines]
g = simulate(grid, neighbourssimple, 4)
s = sum(sum(1 if c == '#' else 0 for c in l) for l in g)
print(s)
g = simulate(grid, nbscomplex, 5)
s = sum(sum(1 if c == '#' else 0 for c in l) for l in g)
print(s)

#!/usr/bin/env python # printgrid(grid)