# Adapted from https://github.com/muccc/flipdots/blob/master/scripts/clock.py
#
# Game of life framerates
# 24/min 1x1 pixel, no draw loop 
# 20/min 1x1 pixel, drawpixel
# 20/min 1x1 pixel, 0x0 filled rectangle
# 153/min 3x3 pixel, 2x2 filled rectangle
# 213/min sweeping 2x3 cache
#
import display
from utime import sleep
import utime
import urandom
import math
import leds
import buttons

class Time:
    def __init__(self, start = 0):
        self.time = start
        self.wait_time = 0.95

    def tick(self):
        sleep(self.wait_time)
        self.time += 1

    @property
    def second(self):
        return self.time % 60

    @property
    def minute(self):
        return (self.time / 60) % 60

    @property
    def hour(self):
        return (self.time / 3600) % 24

class Clock:
    def __init__(self, sizex = 80, sizey = 80, radius = 38, offsetx = 30,
            hour_hand = True, minute_hand = True, second_hand = True, console_out = False,
            run_once = False, update_interval = 0):
        self.sizex = sizex
        self.sizey = sizey
        self.radius = radius
        self.center = (int(self.sizex/2), int(self.sizey/2))
        self.hour_hand = hour_hand
        self.minute_hand = minute_hand
        self.second_hand = second_hand
        self.console_out = console_out
        self.update_interval = update_interval if update_interval != 0 else (1 if self.second_hand else 30)
        self.run_once = run_once
        self.offsetx = offsetx
        self.time = Time()
        white = (255, 255, 255)
        self.center_col = white
        self.m1_col = white
        self.m5_col = white
        self.hour_hand_col = white
        self.minute_hand_col = white
        self.second_hand_col = white
        self.warp = 0
        self.frames = 0
        self.starttime=utime.time()
        self.golcol=(0,255,0)
        #print("Da fu-ed time is "+str(self.starttime))
        self.pixsize=3
        self.sx=int(160/self.pixsize)
        self.sy=int(80/self.pixsize)
        self.cellcount=0
        self.cellcounthistory=[]
        self.flags=0
        self.FLAG_HISTORY=2
        self.FLAG_NUMBERS=1
        self.f=[]
        self.nxt=[]
        for x in range(self.sx):
            self.f.append([])
            self.nxt.append([])
            for y in range(self.sy):
                self.f[x].append(urandom.randint(0,1))
                self.nxt[x].append(0)

    def showCellCountHistory(self,disp):
        if self.flags & self.FLAG_HISTORY == 0:
            return
        for x in range(len(self.cellcounthistory)):
            a=80-1
            b=80-1-int(self.cellcounthistory[x]/2)
            if b<0:
                b=0
            disp.line(x,a,x,b,size=1,col=(255,0,0))

    def show(self,disp):
        self.showCellCountHistory(disp)
        pixsize=self.pixsize
        for x in range(self.sx):
            for y in range(self.sy):
                if(self.f[x][y]):
#                    disp.pixel(x*pixsize,y*pixsize, col=self.golcol)
                    disp.rect(x*pixsize,y*pixsize,x*pixsize+pixsize-1,y*pixsize+pixsize-1, col=self.golcol)
        now=utime.time()
        #print("Da now is "+str(now)+" und da start is "+str(self.starttime))
        if now==self.starttime:
            now-=1
        #print("Di now is "+str(now)+" und da start is "+str(self.starttime))
        self.frames+=1
        framerate=(self.frames*60)/(now-self.starttime)
        status="{0:1.2f} {1}".format(framerate,self.cellcount)
        if self.flags & self.FLAG_NUMBERS != 0:
            disp.print(status, fg=[0,255,255])
        #print("FR "+str(framerate)+" = "+str(self.frames*60)+"/"+str(now-self.starttime))
                
    def neigh(self,x,y):
        return self.f[x-1][y-1]+self.f[x][y-1]+self.f[x+1][y-1]+self.f[x-1][y]+self.f[x+1][y]+self.f[x-1][y+1]+self.f[x][y+1]+self.f[x+1][y+1]

    def future(self,x,y):
        f = self.neigh(x,y)
        if f == 3 or (f==2 and self.f[x][y]==1):
            return 1
        return 0
    
    def iter(self):
        rx=urandom.randint(1,self.sx-2)
        ry=urandom.randint(1,self.sy-2)
        #print("Randpatch "+str(rx)+":"+str(ry))
        self.f[rx][ry]=1
        for x in range(1,self.sx-1):
            y=1
            s2=self.f[x-1][y-1]+self.f[x][y-1]+self.f[x+1][y-1]
            s3=self.f[x-1][y]+self.f[x][y]+self.f[x+1][y]
            for y in range(1,self.sy-1):
                s1=s2
                s2=s3
                s3=self.f[x-1][y+1]+self.f[x][y+1]+self.f[x+1][y+1]
                s=self.f[x][y]
                neigh=s1+s2+s3-s                     
                #print("Iter "+str(x)+","+str(y)+" has "+str(s1)+","+str(s2)+","+str(s3)+","+str(neigh))
                if neigh == 3 or (s==1 and neigh==2):
                    self.nxt[x][y]=1
                else:
                    self.nxt[x][y]=0
        cc=0
        for x in range(0,self.sx):
            for y in range(0,self.sy):
                self.f[x][y]=self.nxt[x][y]
                cc+=self.f[x][y]
        self.cellcount=cc
        self.cellcounthistory.append(cc)
        if(len(self.cellcounthistory)>160-1):
            self.cellcounthistory.pop(0)
        #print("Cells: "+str(cc)+" hist "+str(len(self.cellcounthistory)))
        # Reboot if cellcount is zero
        if cc==0:
            cx=int(self.sx/2)
            cy=int(self.sy/2)
            self.f[cx-2][cy]=1
            self.f[cx-1][cy]=1
            self.f[cx][cy-1]=1
            self.f[cx][cy+1]=1
            self.f[cx+1][cy]=1
            self.f[cx+2][cy]=1
            print("Restart game of life because all cells have vanished after frame "+str(self.frames))
        # Check button
        pressed=buttons.read(buttons.BOTTOM_LEFT)
        if pressed & buttons.BOTTOM_LEFT != 0:
            self.flags+=1
            print("Button BL pressed! Flags now "+str(self.flags))
            
    def loop(self):
        colored = False
        try:
            with display.open() as disp:
                while True:
                    self.updateClock(disp)
                    if self.run_once:
                        break
        except KeyboardInterrupt:
            for i in range(11):
                leds.set(i, (0, 0, 0))
            return

    def drawImage(self, image):
        with display.open() as d:
            d.clear()
            for x in range(len(image)):
                for y in range(len(image[x])):
                    d.pixel(x + self.offsetx, y, col = (255, 255, 255) if image[x][y] else (0, 0, 0))
            d.update()

    def updateClock(self, disp):
        disp.clear()
        localtime = utime.localtime()
        self.warp = self.warp + 1

        self.iter()
        self.show(disp)
        
        disp.pixel(self.center[0] + self.offsetx, self.center[1], col = self.center_col)
        hour_coords = self.circlePoint(math.radians((((localtime[3]%12)/12.) if localtime[3] else 0)*360 + 270 + (localtime[4]/2)))
        minute_coords = self.circlePoint(math.radians(localtime[4]*6+270))
        second_coords = self.circlePoint(math.radians(localtime[5]*6+270))

        for i in range(60):
            degree = i*6 + 90
            radian = - math.radians(degree)
            coords = self.circlePoint(radian)

            if not i % 5:
                self.addLine(disp, coords, self.center, 3, 1, col = self.m5_col)
            else:
                self.addLine(disp, coords, self.center, 1, col = self.m1_col)

        if self.hour_hand:
            self.addLine(disp, self.center, hour_coords, int(self.radius / 3), 1, col = self.hour_hand_col)
        if self.minute_hand:
            self.addLine(disp, self.center, minute_coords, int(self.radius / 2), col = self.minute_hand_col)
        if self.second_hand:
            self.addLine(disp, self.center, second_coords, self.radius - int(self.radius/8.), col = self.second_hand_col)

        if self.console_out:
            for y in range(self.radius*2):
                line = ""
                for x in range(self.radius*2):
                    line = line + ("." if image[(self.center[1]-self.radius)+y][(self.center[0]-self.radius)+x] else " ")
                print(line)

        disp.update()

    def circlePoint(self, t):
        return (int(round(self.radius*math.cos(t))) + self.center[0], int(round(self.radius*math.sin(t))) + self.center[1])

    def addLine(self, disp, source, aim, length, thickness = 1, col = (255, 255, 255)):
        vector = self.subVector(aim, source)
        vector = self.normVector(vector)
        destination = self.addVector(source, self.multiplyVector(vector, length))

        disp.line(round(source[0]) + self.offsetx, round(source[1]), round(destination[0]) + self.offsetx, round(destination[1]), col=col, size=thickness)

    def normVector(self, v):
        length = math.sqrt(sum([i**2 for i in v]))
        new_v = []
        for i in range(len(v)):
            new_v.append(v[i]/length)
        return tuple(new_v)

    def subVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
            res.append(v1[i]-v2[i])
        return tuple(res)

    def addVector(self, v1, v2):
        res = []
        for i in range(len(v1)):
            res.append(v1[i]+v2[i])
        return tuple(res)

    def multiplyVector(self, v, multiplier):
        return tuple([i*multiplier for i in v])

clock = Clock()
clock.loop()