#!/usr/bin/env python3
import spidev
from time import sleep
import random
import json

def toMhz(hz):
    return hz * 1000 * 1000

class LedMatrix:
    def __init__(self, count=4, open=0, port=0):
        self.spi = spidev.SpiDev()

        self.count = count
        # Initiailize spi and open spi
        self.spi.open(open, port)

        # Configure spi
        self.spi.max_speed_hz = toMhz(6)

    def bootstrap(self):
        self.scan_limit(0x07)
        self.power(0x01)
        self.display_test(0x00)
        self.decode_mode(0x00)
        self.brightness(0x01)
        
    def brightness(self, level):
        self.data([0x0a,level] * self.count)

    def data(self, data):
        #print(f"data = {data}")
        self.spi.xfer(data)
        
    def power(self, mode):
        self.data([0x0c, mode] * self.count)

    def display_test(self, mode):
        self.data([0x0f, mode] * self.count)

    def scan_limit(self, range):
        self.data([0x0b, range] * self.count)

    def decode_mode(self, mode):
        self.data([0x09, mode] * self.count)

    def setrow(self, row, vals):
        data = []
        for i in range(self.count):
            data.append(row + 1)
            data.append(vals[i])
            
        self.data(data)

            
    def clear(self):
        for row in range(8):
            self.setrow(row, [0,0,0,0])

    def fill(self):
        for row in range(8):
            self.setrow(row, [0xff,0xff,0xff,0xff])
            
    def display(self, screen):
        for row in range(8):
            self.setrow(row, screen[row])

    def close(self):

        return self.spi.close()


from copy import deepcopy
class Board:
    empty = [
        [ 0 for i in range(32) ]
        for i in range(8)
    ]
    def __init__(self):
        self.board = deepcopy(self.empty)
        self.rows = 8
        self.columns = 32

    def wrap(self, x, d):
        if (x < 0):
            x += d
        return x % d

    def r(self, x):
        return self.wrap(x, 8)

    def c(self, x):
        return self.wrap(x, 32)

    def b(self, row, column):
        return self.board[self.r(row)][self.c(column)]

    def sb(self, row, column, v):
        self.board[self.r(row)][self.c(column)] = v

    def packbyte(self, seq):
        ret = 0
        for bit in seq:
            ret <<= 1
            ret |= bit & 0x01

        return ret

    def packrow(self, row):
        return [self.packbyte(x) for x in [row[0:8],
                                           row[8:16],
                                           row[16:24],
                                           row[24:32]]]

    def pack(self):
        return [
            self.packrow(x) for x in self.board
        ]

    def free(self):
        res = []
        for r in range(self.rows):
            for c in range(self.columns):
                if not self.board[r][c]:
                    res.append((r,c))
        return res

def inclife(cur):
    n = Board()
    def v(x, y):
        return cur.b(x, y)

    def adjacent(x,y):
        return sum([
            v(x, y-1),
            v(x, y+1),
            v(x+1, y-1),
            v(x+1, y),
            v(x+1, y+1),
            v(x-1, y-1),
            v(x-1, y),
            v(x-1, y+1)])

    def live(x,y):
        a = adjacent(x,y)
        if v(x,y):
#            print(f"x={x}, y={y}, a={a}")
            return int(a == 2 or a == 3)
        else:
            return int(a == 3)
    
    for x in range(len(cur.board)):
        for y in range(len(cur.board[x])):
            n.board[x][y] = live(x,y)

    return n


def conway():
    def rand(b):
        for row in range(8):
            for col in range(32):
                v = random.randint(0,1)
                b.sb(row,col,v)

    def glider(b,x,y):
        b.sb(y,x,1)
        b.sb(y+1,x,1)
        b.sb(y+2,x,1)
        b.sb(y+2,x-1,1)
        b.sb(y+1,x-2,1)

    def cross(b,x,y):
        b.sb(y,x,1)
        b.sb(y,x+1,1)
        b.sb(y,x+2,1)

    b = Board()
    start = 1
    if 0:
        cross(b,6,5)
    elif start == 0:
        glider(b,1,1)
    elif start == 1:
        rand(b)
    elif start == 2:
        glider(b,1,1)
        glider(b,5,5)
        glider(b,10,5)
        glider(b,20,5)


    def done(ob,b,nb):
        return nb.board == b.board or nb.board == ob.board
        
    iter = 10000
    fb = ob = b
    while iter > 0:
        m.display(b.pack())
        sleep(.1)
        nb = inclife(b)
        # found terminal state
        if done(ob,b,nb):
            if iter > 50:
                iter = 50
        ob = b
        b = nb
        iter = iter -1
    sleep(5)
    if not done(ob,b,nb):
        with open("/home/gno/coolboards", "a") as cb:
            cb.write(json.dumps(fb.board))
            cb.write("\n")


def clock():
    numbers = {
        '1': [[0,0,0,0,1,0,0,0],
              [0,0,0,1,1,0,0,0],
              [0,0,1,0,1,0,0,0],
              [0,0,0,0,1,0,0,0],
              [0,0,0,0,1,0,0,0],
              [0,0,0,0,1,0,0,0],
              [0,0,0,0,1,0,0,0],
              [0,0,0,1,1,1,0,0]],
        '2': [[0,0,1,1,1,0,0,0],
              [0,1,0,0,0,1,0,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,1,1,0,0,0],
              [0,0,1,0,0,0,0,0],
              [0,1,0,0,0,0,0,0],
              [0,1,0,0,0,0,0,0],
              [0,1,1,1,1,1,0,0]],
        '3': [[0,0,0,1,1,1,0,0],
              [0,0,1,0,0,0,1,0],
              [0,0,0,0,0,0,1,0],
              [0,0,0,0,1,1,0,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,0,0,0,1,0],
              [0,0,1,0,0,0,1,0],
              [0,0,0,1,1,1,0,0]],
        '4': [[0,0,0,0,0,1,0,0],
              [0,0,0,0,1,1,0,0],
              [0,0,0,1,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,1,1,1,1,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,0,0,1,0,0]],
        '5': [[0,1,1,1,1,0,0,0],
              [0,1,0,0,0,0,0,0],
              [0,1,0,0,0,0,0,0],
              [0,1,1,1,1,0,0,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,0,0,1,0,0],
              [0,1,1,1,1,0,0,0]],
        '6': [[0,0,1,1,1,0,0,0],
              [0,1,0,0,0,1,0,0],
              [0,1,0,0,0,0,0,0],
              [0,1,1,1,1,0,0,0],
              [0,1,0,0,0,1,0,0],
              [0,1,0,0,0,1,0,0],
              [0,1,0,0,0,1,0,0],
              [0,0,1,1,1,0,0,0]],
        '7': [[0,1,1,1,1,0,0,0],
              [0,0,0,0,0,1,0,0],
              [0,0,0,0,1,0,0,0],
              [0,0,0,1,0,0,0,0],
              [0,0,1,0,0,0,0,0],
              [0,0,1,0,0,0,0,0],
              [0,0,1,0,0,0,0,0],
              [0,0,1,0,0,0,0,0]],
        '9': [[0,0,1,1,1,1,0,0],
              [0,1,0,0,0,0,1,0],
              [0,1,0,0,0,0,1,0],
              [0,0,1,1,1,1,1,0],
              [0,0,0,0,0,0,1,0],
              [0,0,0,0,0,0,1,0],
              [0,0,0,0,0,1,0,0],
              [0,0,1,1,1,0,0,0]],
        '8': [[0,0,1,1,1,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,0,1,1,0,0,0],
              [0,0,0,1,1,0,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,1,1,1,0,0]],
        '0': [[0,0,0,1,1,0,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,1,1,0,0],
              [0,0,1,1,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,1,0,0,1,0,0],
              [0,0,0,1,1,0,0,0]]
    }
    
    b = Board()
    from datetime import datetime, timezone
    from zoneinfo import ZoneInfo
    now = datetime.now().astimezone(ZoneInfo("America/Los_Angeles"))
    timestr = now.strftime("%H%M")
    off = 0
    for digit in timestr:
        pattern = numbers[digit]
        for i in range(8):
            for j in range(8):
                b.board[i][j+off] = pattern[i][j]
        off += 8
    b.board[2][16] = 1
    b.board[3][16] = 1
    b.board[5][16] = 1
    b.board[6][16] = 1
    m.display(b.pack())
    sleep(10)

def snake():
    b = Board()
    # maybe this should go in board
    def unoccupied():
        (r,c) = random.choice(b.free())
        b.board[r][c] = 1
        return (r,c)

    snake = [unoccupied()]
    food = unoccupied()
    alive = True

    def calculate_direction():
        (sr, sc) = snake[0]
        (fr, fc) = food
        (dr, dc) = (0,0)

        if abs(fr - sr) > abs(fc - sc):
            dr = 1
            if (fr < sr):
                dr = -1
        else:
            dc = 1
            if (fc < sc):
                dc = -1
        return (dr, dc)
                
    
    
    def update_snake():
        nonlocal food
        nonlocal alive
        dr,dc = calculate_direction() # delta
        cr,cc = snake[0] #current
        r,c = (dr+cr, dc+cc)
        snake.insert(0, (r,c))
        if b.board[r][c]:
            if food == (r,c):
                food = unoccupied()
            else:
                alive = False
        else:
            b.board[r][c] = 1
            (er,ec) = snake.pop() # end
            b.board[er][ec] = 0
            
        

    while alive:
        m.display(b.pack())
        update_snake()
        sleep(.1)

    sleep(5)



        
        
    

    
m = LedMatrix()
m.bootstrap()
m.fill()
m.clear()
while True:
    snake()
    clock()
    conway()
    clock()
