# Adapted from https://github.com/muccc/flipdots/blob/master/scripts/clock.py
# ...and then stole some more stuff :)
import display
from utime import sleep
import utime
import math
import leds
import buttons
import ujson
import bme680
import os
import power
import light_sensor

CONFIG_NAME = "catclock.json"

BATCONF = [
    True,
    [50, 50, 50],
    [255, 215, 0],
    [255, 0, 0],
    [0, 255, 255],
    [0, 255, 0],
    [120,100,0],
]

v_batt = 0
c_batt = 0
warning_col = [50,50,50]

def render_warning(disp, x=141,y=18):
    global warning_col
    c = warning_col
    disp.print('!',posx=x+5,posy=y+4,fg=c,font=0)
    disp.line(x+7,y,x+14,y+11, col = c)
    disp.line(x,y+11,x+7,y, col = c)
    disp.line(x,y+11,x+14,y+11, col = c)

def get_bat_state(): # 1: normal, 2: low, 3: very low, 4: charging, 5: full and plugged, 6: plugged, not full, not charging (error)
    global v_batt
    global c_batt
    global warning_col
    a,humid,c,d = bme680.get_data()
    if humid > 60:
        warning_col = [100,100,200]
    else:
        warning_col = [50,50,50]
    v_batt = power.read_battery_voltage()
    c_batt = power.read_battery_current()
    if power.read_chargein_voltage() > 4.5 :
        if power.read_chargein_current() > 0.07 :
            return 4
        else:
            if v_batt > 4:
                return 5
            else:
                warning_col = [0,255,255]
                return 6

    if v_batt > 3.8:
        return 1
    if v_batt > 3.6:
        return 2
    return 3

def get_bat_color(bat):
    return bat[get_bat_state()]


def render_battery(disp, bat):
    global vbatt
    c = get_bat_color(bat)
    disp.rect(140, 2, 155, 9, filled=True, col=c)
    disp.rect(155, 4, 157, 7, filled=True, col=c)
    disp.print(str(int(v_batt*100)),fg = [0,0,0], bg = c, font = 0, posx=140, posy=3)
    disp.line(140,10,155,10, col = [0,0,0])

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=40,
        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()
        self.theme = 0
        self.default_themes = [
            {
                "background": [0, 0, 0],
                "ears": [255,255,255],
                "m1": [255,255,255],
                "m5": [255,255,255],
                "hour_hand": [255,255,255],
                "minute_hand": [255,255,255],
                "second_hand": [255,255,255],

            },
        ]
        self.themes = self.default_themes

        # check for config file
        if CONFIG_NAME in os.listdir("."):
            self.readConfig()
        else:
            self.writeConfig()

        # load colors
        self.setTheme(self.theme)

    def readConfig(self):
        with open(CONFIG_NAME, "r") as f:
            try:
                c = ujson.loads(f.read())
                if (
                    "themes" in c
                    and len(c["themes"]) > 0
                    and isinstance(c["themes"], list)
                ):
                    self.themes = c["themes"]
                if "theme" and isinstance(c["theme"], int):
                    self.theme = c["theme"]
            except ValueError:
                print("parsing %s failed" % CONFIG_NAME)

    def writeConfig(self):
        with open(CONFIG_NAME, "w") as f:
            f.write(ujson.dumps({"theme": self.theme, "themes": self.themes}))

    def setTheme(self, theme):
        self.theme = theme % len(self.themes)
        self.background_col = (
            self.themes[self.theme]["background"]
            if "background" in self.themes[self.theme]
            else self.default_themes[0]["background"]
        )
        self.ears_col = (
            self.themes[self.theme]["ears"]
            if "ears" in self.themes[self.theme]
            else self.default_themes[0]["ears"]
        )
        self.m1_col = (
            self.themes[self.theme]["m1"]
            if "m1" in self.themes[self.theme]
            else self.default_themes[0]["m1"]
        )
        self.m5_col = (
            self.themes[self.theme]["m5"]
            if "m5" in self.themes[self.theme]
            else self.default_themes[0]["m5"]
        )
        self.hour_hand_col = (
            self.themes[self.theme]["hour_hand"]
            if "hour_hand" in self.themes[self.theme]
            else self.default_themes[0]["hour_hand"]
        )
        self.minute_hand_col = (
            self.themes[self.theme]["minute_hand"]
            if "minute_hand" in self.themes[self.theme]
            else self.default_themes[0]["minute_hand"]
        )
        self.second_hand_col = (
            self.themes[self.theme]["second_hand"]
            if "second_hand" in self.themes[self.theme]
            else self.default_themes[0]["second_hand"]
        )

    def updateBrightness(self, disp):
        brightness = light_sensor.read()
        brightness = (brightness-12)*10+5
        brightness = min(100,max(brightness,5))
        disp.backlight(brightness)
        
    def loop(self):
        try:
            with display.open() as disp:
                button_pressed = False
                flashlight_on = False
                display_on = True
                timeout = 10
                brightness_timer = 1
                battery_timer = 10
                blink_timer = 2
                nextBatteryBlink = 0
                battery_state = 1
                nextBrightnessUpdate = 0
                nextBatteryUpdate = 0
                turnoff_time = utime.time()+timeout
                while True:
                    if self.run_once:
                        break

                    # check for button presses
                    v = buttons.read(buttons.BOTTOM_LEFT | buttons.BOTTOM_RIGHT | buttons.TOP_RIGHT | buttons.TOP_LEFT)
                    if v == 0:
                        button_pressed = False

                    if v & buttons.TOP_LEFT != 0:
#                        self.updateBrightness(disp)
#                        leds.set_flashlight(False)
                        break
                    elif not button_pressed and v & buttons.BOTTOM_LEFT != 0:
                        button_pressed = True
                        if display_on:
                            self.setTheme(self.theme + 1)
                            self.writeConfig()
                    elif not button_pressed and v & buttons.BOTTOM_RIGHT != 0:
                        button_pressed = True
                        display_on = not display_on
                        if display_on:
                            nextBrightnessUpdate = 0
                            turnoff_time = utime.time()+timeout
                        else:
                            disp.backlight(0)
                    elif not button_pressed and v & buttons.TOP_RIGHT != 0:
                        button_pressed = True
                        flashlight_on = not flashlight_on
                        leds.set_flashlight(flashlight_on)
                    
                    utime.sleep_ms(50)
                    
                    if utime.time() > nextBatteryUpdate:
                        nextBatteryUpdate = utime.time()+battery_timer
                        battery_state = get_bat_state()

                    if utime.time() > turnoff_time and display_on:
                        display_on = False
                        disp.backlight(0)

                    if display_on:
                        leds.set(10,[0,0,0])
                        self.updateClock(disp)
                        if utime.time() > nextBrightnessUpdate:
                            self.updateBrightness(disp)
                            nextBrightnessUpdate = utime.time() + brightness_timer
                    else:
                        utime.sleep_ms(100)
                        if battery_state == 3:
                            if utime.time() > nextBatteryBlink:
                                nextBatteryBlink = utime.time()+blink_timer
                                leds.set(10,[100,0,0])
                            else:
                                leds.set(10,[0,0,0])
                        elif battery_state == 4:
                            leds.set(10,[0,0,80])
                        elif battery_state == 5:
                            leds.set(10,[0,80,0])
                        else:
                            leds.set(10,[0,0,0])

        except:
            raise

    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(self.background_col)
        localtime = utime.localtime()

        disp.line(40,0,43,20, col=self.ears_col)
        disp.line(120,0,117,20, col=self.ears_col)
        disp.line(40,0,60,3, col=self.ears_col)
        disp.line(120,0,100,3, col=self.ears_col)

        hour_coords = self.circlePoint(
            math.radians(
                (((localtime[3] % 12) / 12.0) 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*0.5),
                col=self.hour_hand_col,
            )
        if self.minute_hand:
            self.addLine(
                disp,
                self.center,
                minute_coords,
                int(self.radius*0.7),
                col=self.minute_hand_col,
            )
        if self.second_hand:
            self.addLine(
                disp,
                self.center,
                second_coords,
                self.radius - int(self.radius / 8.0),
                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)

        render_battery(disp, BATCONF)
        render_warning(disp)

        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])

bme680.init()
clock = Clock()
clock.loop()