# by @monkeydom (@monkeydom@mastodon.technology)
# font by Daniel Linnsen ( https://managore.itch.io/m3x6 )


import display
import os
import utime
import leds
import buttons
import bme680
import light_sensor


WIDTH = 160
HEIGHT = 80

DEBUG = False


class SmallType:

    def __init__(self, display, position_x = 0, position_y = 0, color = (255,255,255)):
        self.display = display
        self.position_x = position_x
        self.position_y = position_y
        self.scale = 1
        self.color = color
        self.toLeft = False

    def char_spec(self, character):
        return {
            ""
            " ":(4,(-1,-1)),
            ".":(4,(1,5)),
            "=":(4,(1,2),(0,2),(2,2),(1,4),(0,4),(2,4)),
            "<":(4,(2,1),(1,2),(0,3),(1,4),(2,5)),
            ">":(4,(0,1),(1,2),(2,3),(1,4),(0,5)),
            ":":(4,(1,2),(1,5)),
            ";":(4,(1,2),(1,5),(1,6)),
            ",":(4,(1,5),(1,6)),
            "!":(4,(1,0),(1,1),(1,2),(1,3),(1,5)),
            '"':(4,(0,0),(0,1),(2,0),(2,1)),
            "'":(4,(1,0),(1,1)),
            "|":(4,(1,0),(1,1),(1,2),(1,3),(1,4),(1,5)),
            "/":(4,(2,0),(2,1),(1,2),(1,3),(0,4),(0,5)),
            "\\":(4,(0,0),(0,1),(1,2),(1,3),(2,4),(2,5)),
            "%":(4,(0,1),(2,2),(1,3),(0,4),(2,5)),
            "*":(4,(0,1),(2,1),(1,2),(0,3),(2,3)),
            "+":(4,(1,2),(0,3),(1,3),(2,3),(1,4)),
            "^":(4,(1,0),(0,1),(2,1)),
            "-":(4,(0,3),(1,3),(2,3)),
            "_":(4,(0,5),(1,5),(2,5)),
            "@":(6,(1,0),(2,0),(3,0),(0,1),(3,1),(4,1),(0,2),(2,2),(4,2),(0,3),(2,3),(3,3),(4,3),(0,4),(1,5),(2,5),(3,5),(4,5)),
            "$":(4,(1,0),(1,1),(2,1),(0,2),(0,3),(1,3),(2,3),(2,4),(0,5),(1,5),(1,6)),
            "{":(4,(2,0),(1,1),(1,2),(0,3),(1,4),(1,5),(2,6)),
            "}":(4,(0,0),(1,1),(1,2),(2,3),(1,4),(1,5),(0,6)),
            "(":(4,(1,0),(0,1),(0,2),(0,3),(0,4),(1,5)),
            ")":(4,(1,0),(2,1),(2,2),(2,3),(2,4),(1,5)),
            "[":(4,(0,0),(1,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,5)),
            "]":(4,(2,0),(1,0),(2,1),(2,2),(2,3),(2,4),(2,5),(1,5)),
            '#':(6,(1,0),(3,0),(0,1),(1,1),(2,1),(3,1),(4,1),(1,2),(3,2),(1,3),(3,3),(0,4),(1,4),(2,4),(3,4),(4,4),(1,5),(3,5)),
            '0':(4,(1,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(0,5)),
            '1':(4,(1,0),(0,1),(1,1),(1,2),(1,3),(1,4),(0,5),(1,5),(2,5)),
            '2':(4,(0,0),(1,0),(2,1),(2,2),(1,3),(0,4),(0,5),(1,5),(2,5)),
            '3':(4,(0,0),(1,0),(2,1),(0,2),(1,2),(2,3),(2,4),(0,5),(1,5)),
            '4':(4,(0,0),(2,0),(0,1),(2,1),(1,2),(2,2),(2,3),(2,4),(2,5)),
            '5':(4,(0,0),(1,0),(2,0),(0,1),(0,2),(1,2),(2,3),(2,4),(0,5),(1,5)),
            '6':(4,(1,0),(2,0),(0,1),(0,2),(1,2),(0,3),(2,3),(0,4),(2,4),(1,5),(2,5)),
            '7':(4,(0,0),(1,0),(2,0),(2,1),(1,2),(1,3),(1,4),(1,5)),
            '8':(4,(1,0),(2,0),(0,1),(2,1),(1,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5)),
            '9':(4,(0,0),(1,0),(0,1),(2,1),(0,2),(2,2),(1,3),(2,3),(2,4),(0,5),(1,5)),
            'A':(4,(1,0),(2,0),(0,1),(2,1),(0,2),(1,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'B':(4,(0,0),(1,0),(0,1),(2,1),(0,2),(1,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'C':(4,(1,0),(2,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,5),(2,5)),
            'D':(4,(0,0),(1,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(2,5)),
            'E':(4,(0,0),(1,0),(2,0),(0,1),(0,2),(1,2),(0,3),(0,4),(0,5),(1,5),(2,5)),
            'F':(4,(1,0),(2,0),(0,1),(0,2),(1,2),(0,3),(0,4),(0,5)),
            'G':(4,(1,0),(2,0),(0,1),(0,2),(0,3),(2,4),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'H':(4,(0,0),(2,0),(0,1),(2,1),(0,2),(1,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'I':(4,(0,0),(1,0),(2,0),(1,1),(1,2),(1,3),(1,4),(0,5),(1,5),(2,5)),
            'J':(4,(0,0),(1,0),(2,0),(2,1),(2,2),(2,3),(2,4),(0,5),(1,5)),
            'K':(4,(0,0),(2,0),(0,1),(2,1),(0,2),(1,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'L':(4,(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(1,5),(2,5)),
            'M':(6,(0,0),(1,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(4,5),(4,4),(4,3),(4,2),(4,1),(3,0)),
            'N':(4,(0,0),(1,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'O':(4,(1,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(0,5)),
            'P':(4,(0,0),(1,0),(2,0),(0,1),(2,1),(0,2),(1,2),(0,3),(0,4),(0,5)),
            'Q':(4,(1,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(2,5),(2,6)),
            'R':(4,(0,0),(1,0),(2,0),(0,1),(2,1),(0,2),(1,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'S':(4,(1,0),(2,0),(0,1),(0,2),(1,2),(2,2),(2,3),(2,4),(0,5),(1,5)),
            'T':(4,(0,0),(1,0),(2,0),(1,1),(1,2),(1,3),(1,4),(1,5)),
            'U':(4,(0,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'V':(4,(0,0),(2,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5)),
            'W':(6,(0,0),(4,0),(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(3,5),(4,1),(4,2),(4,3),(4,4)),
            'X':(4,(0,0),(2,0),(0,1),(2,1),(1,2),(1,3),(0,4),(2,4),(0,5),(2,5)),
            'Y':(4,(0,0),(2,0),(0,1),(2,1),(0,2),(2,2),(1,3),(1,4),(1,5)),
            'Z':(4,(0,0),(1,0),(2,0),(2,1),(1,2),(1,3),(0,4),(0,5),(1,5),(2,5)),

            'a':(4,(0,1),(1,1),(2,2),(1,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'b':(4,(0,0),(0,1),(1,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'c':(4,(2,1),(1,1),(0,2),(0,3),(0,4),(0,5),(1,5),(2,5)),
            'd':(4,(2,0),(2,1),(1,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'e':(4,(1,1),(2,1),(0,2),(2,2),(0,3),(1,3),(2,3),(0,4),(1,5),(2,5)),
            'f':(4,(2,0),(1,1),(1,2),(0,3),(1,3),(2,3),(1,4),(1,5)),
            'g':(4,(1,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(1,4),(2,4),(2,5),(0,6),(1,6)),
            'h':(4,(0,0),(0,1),(1,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'i':(4,(1,0),(1,2),(1,3),(1,4),(1,5)),
            'j':(4,(1,0),(1,2),(1,3),(1,4),(1,5),(0,6)),
            'k':(4,(0,0),(0,1),(2,1),(0,2),(1,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'l':(4,(1,0),(1,1),(1,2),(1,3),(1,4),(2,5)),
            'n':(4,(0,1),(1,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5)),
            'm':(6,(0,1),(1,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(2,5),(3,1),(4,2),(4,3),(4,4),(4,5)),
            'o':(4,(1,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5)),
            'p':(4,(0,1),(1,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(0,6),(0,7)),
            'q':(4,(1,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5),(2,6),(2,7)),
            'r':(4,(1,1),(2,1),(0,2),(0,3),(0,4),(0,5)),
            's':(4,(1,1),(2,1),(0,2),(0,3),(1,3),(2,3),(2,4),(0,5),(1,5)),
            't':(4,(1,0),(1,1),(1,2),(0,2),(1,3),(2,2),(1,4),(1,5)),
            'u':(4,(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(0,5),(1,5),(2,5)),
            'v':(4,(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5)),
            'w':(6,(0,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(3,5),(4,1),(4,2),(4,3),(4,4)),
            'x':(4,(0,1),(2,1),(0,2),(2,2),(1,3),(0,4),(2,4),(0,5),(2,5)),
            'y':(4,(0,1),(2,1),(0,2),(2,2),(0,3),(2,3),(0,4),(2,4),(1,5),(2,5),(2,6),(0,7),(1,7)),
            'z':(4,(0,1),(1,1),(2,1),(2,2),(1,3),(0,4),(0,5),(1,5),(2,5)),

            '°':(4,(1,0),(0,1),(2,1),(1,3)),

        }.get(character, (4,(0,0),(1,0),(2,1),(2,2),(1,3),(1,5)) ) 
    
        

    def print_char(self, character):
        disp = self.display
        color = self.color
        (x_advance, *pixels) = self.char_spec(character)
        
        scale = self.scale
        x_advance *= scale

        if self.toLeft:
            self.position_x -= x_advance

        x = self.position_x
        y = self.position_y

        if scale != 1:
            gap = 1 if scale > 2 else 0
            plot = lambda dx,dy: disp.rect(x+dx*scale,y+dy*scale,x+(dx+1)*scale-gap,y+(dy+1)*scale-gap,col=color)
        else:
            plot = lambda dx,dy: disp.pixel(x+dx,y+dy,col=color)
                    
        for (dx, dy) in pixels:
            if dx != -1:
                plot(dx, dy)
            
        if not self.toLeft:
            self.position_x += x_advance
                            

    def advance_if_needed(self):
        if self.position_x > WIDTH - 3:
            self.position_x = 0
            self.position_y += 9

    def width(self, string):
        result = 0
        for character in string:
            (x_advance, *pixels) = self.char_spec(character)
            result += x_advance
        return result * self.scale

    def print_string(self, string):
        start_x = self.position_x
        
        def advance():
            self.position_x = start_x
            self.position_y += 9 * self.scale
        
        for character in string:
            if character == "\n":
                advance()
            else:
                advancement = self.char_spec(character)[0] 
                if (self.toLeft and (self.position_x - advancement) < 0) or (not(self.toLeft) and (self.position_x + advancement) > WIDTH): 
                    advance() 
                self.print_char(character)


class DigiClock:

    def __init__(self, display):
        # sensors
        bme680.init()
        light_sensor.start()
        
        self.display = display
        self.small_type = SmallType(display)
        
        self.brightness = 70
        self.mode = 5
        
        self.button_bottom_right = False
        self.button_bottom_left = False
        self.button_top_right = False

        self.color_background = (0,0,0)
        self.color_foreground = (255,255,255)
        self.color_secondary = (128,128,128)
        self.color_adjust = (205,10,10)
        self.color_sensor = (128,255,128)


    def draw_sensordata(self):
        disp = self.display 
        st = self.small_type
        
        temperature, humidity, pressure, resistance = bme680.get_data()
        sensor_string = "{0:2.1f}C {1:1.0f}%rh {2:1.1f}hPA {3:.0f}l {4:1.2f}V {5:3.0f}b".format(temperature, humidity, pressure, resistance, os.read_battery(), light_sensor.get_reading())
        
        
        disp.rect(0,55,WIDTH,55+7,col=self.color_background)

        st.color = self.color_sensor
        st.toLeft = False
        st.scale = 1
        st.position_x = 0
        st.position_y = 55
        st.print_string(sensor_string)    

    def draw_statics(self):
        disp = self.display 
        st = self.small_type

        disp.clear()

        if self.mode==6:
            st.color = self.color_foreground
            nick = load_nick().get("nick","")

            st.scale = 2
            st.position_x = int((WIDTH - st.width(nick))/ 2)
            st.position_y = 55
            st.print_string(nick)
        elif self.mode==5:
            pass
        else:
            text = "adjust"
            st.color = self.color_adjust
            st.scale = 2
            st.position_x = int((WIDTH - st.width(text))/ 2)
            st.position_y = 55
            st.print_string(text)

        st.color = self.color_secondary

        # button labels
        st.scale = 1
        st.position_x = 0
        st.position_y = HEIGHT - 8
        st.print_string("MODE")

        st.scale = 2
        st.toLeft = True
        st.position_y = HEIGHT - 8
        st.position_x = WIDTH-1
        st.print_string("-")
        
        st.position_y = HEIGHT-20
        st.position_x = WIDTH-1
        st.print_string("+")

        
    def draw(self):
        disp = self.display 
        st = self.small_type

        disp.rect(0,0,WIDTH,50,col=self.color_background)

        (year, month, mday, hour, min, sec, wday, yday) = utime.localtime(utime.time())

        if self.mode<5:
            digit_width = 20
            (x,y) = (WIDTH-digit_width,5)
            x -= digit_width * self.mode + int(self.mode/2) * digit_width + 3
            
            disp.rect(x,y,x+digit_width-1,y+8*5,col=self.color_adjust)  
        elif self.mode==5:
           self.draw_sensordata()
        else:
            rotate = utime.time() % 3
            for i in range(3):
                leds.set_rocket(i, int(31 * self.brightness / 100) if (rotate == i) else 0)
          
        time_string = "%02d:%02d:%02d" % (hour,min,sec) 
        
        
        
        st.color = self.color_foreground
        st.scale = 5
        st.toLeft = False
        st.position_x = 0
        st.position_y = 10
        st.print_string(time_string)
        

    def loop(self):
        try:
            disp = self.display 
            st = self.small_type
            self.draw_statics()
            
            while True:
                start_time = utime.time_ms()
                self.draw()
                disp.update()                
                
                now = utime.time_ms()                
                draw_time = now - start_time;

                if DEBUG:
                    st.position_x = 0
                    st.position_y = 0
                    st.scale = 1
                    st.toLeft = False
                    st.print_string(str(draw_time))
                    disp.update()
                
                now = utime.time_ms()
                self.handle_buttons(1000 - (now % 1000) + now)
                
#                if (now - start_time < 900):
#                    utime.sleep_ms(1000 - (utime.time_ms() % 1000))
        
        except KeyboardInterrupt:
            for i in range(11):
                leds.set(i, (0,0,0))
            return
            
    def handle_buttons(self,until_time):
        
        needs_redraw = False
        
        while True:
            v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT)
            if v & buttons.TOP_RIGHT:
                if not self.button_top_right:
                    needs_redraw = self.handle_top_right_down()
                self.button_top_right = True
            else:
                self.button_top_right = False

            if v & buttons.BOTTOM_RIGHT:
                if not self.button_bottom_right:
                    needs_redraw = self.handle_bottom_right_down()
                self.button_bottom_right = True
            else:
                self.button_bottom_right = False

            if v & buttons.BOTTOM_LEFT:
                if not self.button_bottom_left:
                    needs_redraw = self.handle_bottom_left_down()
                self.button_bottom_left = True
            else:
                self.button_bottom_left = False
        
            if utime.time_ms() > until_time or needs_redraw: return
            
            utime.sleep_ms(10)

    def adjust_brightness(self, change):
        def clamp(value): 
            return min(100,max(0, value))
        self.brightness = clamp(self.brightness + change)
        self.display.backlight(self.brightness)

    def handle_top_right_down(self):
        return self.handle_adjust(1)

    def handle_bottom_right_down(self):
        return self.handle_adjust(-1)

    def handle_bottom_left_down(self):
        def clamp(value):
            if value > 6:
                value = 0
            return value
    
        self.mode = clamp(self.mode+1) 
        self.draw_statics()       
        return True

    def handle_adjust(self,change):
        if self.mode >= 5:
            self.adjust_brightness(10 * change)
            return False
        else:
            adjusted_change = {0: 1, 1: 10, 2: 60, 3: 60*10, 4: 60*60}.get(self.mode, 60) * change
            utime.set_time(utime.time() + adjusted_change)
            return True

def load_nick():
    filename = "nickname.txt"
    if filename in os.listdir("/"):
        with open("/" + filename, "r") as f:
            nick = f.read().strip()
            return {"nick": nick}
    
    return {}
            

def main():
    with display.open() as disp:
        if DEBUG:
            disp.clear((255,0,0)).update()
            disp.backlight(100)
        
        dc = DigiClock(disp)
        dc.loop()

if DEBUG:
    def r():
        os.reset()
    
if __name__ == "__main__":
    main()