"""
This project is based on collins watch++ which was inspired by yrlfs digiclk and base functionality (clock) is taken from there.
https://badge.team/projects/watch
https://github.com/Ferdi265/card10-digiclk

Thanks to lortas for the battery rendering code
"""

import buttons
import display
import os
import utime
import light_sensor
import power

import color
import ujson
import vibra
import struct

VIBRATETIME     = 150
STOPWATCHCOLOR  = (color.CHAOSBLUE,  color.CHAOSBLUE_DARK)
TIMERCOLOR      = (color.CAMPGREEN,  color.CAMPGREEN_DARK)
ALARMCOLOR      = (color.COMMYELLOW, color.COMMYELLOW_DARK)

def ceil_div(a, b):
    return (a + (b - 1)) // b

def tip_height(w):
    return ceil_div(w, 2) - 1

def draw_tip(d, x, y, w, c, invert=False, swapAxes=False):
    h = tip_height(w)
    for dy in range(h):
        for dx in range(dy + 1, w - 1 - dy):
            px = x + dx
            py = y + dy if not invert else y + h - 1 - dy
            if swapAxes:
                px, py = py, px
            d.pixel(px, py, col=c)

def draw_seg(d, x, y, w, h, c, swapAxes=False):
    tip_h = tip_height(w)
    body_h = h - 2 * tip_h

    draw_tip(d, x, y, w, c, invert=True, swapAxes=swapAxes)

    px1, px2 = x, x + (w - 1)
    py1, py2 = y + tip_h, y + tip_h + (body_h - 1)
    if swapAxes:
        px1, px2, py1, py2 = py1, py2, px1, px2
    d.rect(px1, py1, px2, py2, col=c)

    draw_tip(d, x, y + tip_h + body_h, w, c, invert=False, swapAxes=swapAxes)


def draw_Vseg(d, x, y, w, l, c):
    draw_seg(d, x, y, w, l, c)


def draw_Hseg(d, x, y, w, l, c):
    draw_seg(d, y, x, w, l, c, swapAxes=True)


def draw_grid_seg(d, x, y, w, l, c, swapAxes=False):
    sw = w - 2
    tip_h = tip_height(sw)

    x = x * w
    y = y * w
    l = (l - 1) * w
    draw_seg(d, x + 1, y + tip_h + 3, sw, l - 3, c, swapAxes=swapAxes)

def draw_grid_Vseg(d, x, y, w, l, c):
    draw_grid_seg(d, x, y, w, l, c)

def draw_grid_Hseg(d, x, y, w, l, c):
    draw_grid_seg(d, y, x, w, l, c, swapAxes=True)

def draw_grid(d, x1, y1, x2, y2, w, c):
    for x in range(x1 * w, x2 * w):
        for y in range(y1 * w, y2 * w):
            if x % w == 0 or x % w == w - 1 or y % w == 0 or y % w == w - 1:
                d.pixel(x, y, col=c)

def draw_grid_7seg(d, x, y, w, segs, c):
    if segs[0]:
        draw_grid_Hseg(d, x, y, w, 4, c)
    if segs[1]:
        draw_grid_Vseg(d, x + 3, y, w, 4, c)
    if segs[2]:
        draw_grid_Vseg(d, x + 3, y + 3, w, 4, c)
    if segs[3]:
        draw_grid_Hseg(d, x, y + 6, w, 4, c)
    if segs[4]:
        draw_grid_Vseg(d, x, y + 3, w, 4, c)
    if segs[5]:
        draw_grid_Vseg(d, x, y, w, 4, c)
    if segs[6]:
        draw_grid_Hseg(d, x, y + 3, w, 4, c)

DIGITS = [
    (True, True, True, True, True, True, False),
    (False, True, True, False, False, False, False),
    (True, True, False, True, True, False, True),
    (True, True, True, True, False, False, True),
    (False, True, True, False, False, True, True),
    (True, False, True, True, False, True, True),
    (True, False, True, True, True, True, True),
    (True, True, True, False, False, False, False),
    (True, True, True, True, True, True, True),
    (True, True, True, True, False, True, True)
]

BATTERY_COLOR_GOOD = [0, 128, 0]
BATTERY_COLOR_OK = [128, 100, 0]
BATTERY_COLOR_BAD = [128, 0, 0]

def get_bat_color(v):
    if v > 3.8:
        return BATTERY_COLOR_GOOD
    if v > 3.6:
        return BATTERY_COLOR_OK
    return BATTERY_COLOR_BAD

def render_battery(display, pos_x=142, pos_y=72):
    v = os.read_battery()
    c = get_bat_color(v)

    if not c:
        return
    display.rect(pos_x, pos_y, pos_x + 15, pos_y + 7, filled=True, col=c)
    display.rect(pos_x + 15, pos_y + 2, pos_x + 17, pos_y + 5, filled=True, col=c)
    if v < 4.0:
        display.rect(pos_x + 11, pos_y + 1, pos_x + 14, pos_y + 6, filled=True, col=[0, 0, 0])
    if v < 3.8:
        display.rect(pos_x + 6, pos_y + 1, pos_x + 11, pos_y + 6, filled=True, col=[0, 0, 0])
    if v < 3.6:
        display.rect(pos_x + 1, pos_y + 1, pos_x + 6, pos_y + 6, filled=True, col=[0, 0, 0])
    render_charging(display, pos_x + 6, pos_y)

def render_charging(display, pos_x, pos_y):
    v_in = power.read_chargein_voltage()
    if v_in > 4.0:
        c = [0, 0, 0]
        c_shade = [120, 120, 120]
        display.pixel(pos_x + 1, pos_y, col=c)
        display.pixel(pos_x + 1, pos_y, col=c)
        display.pixel(pos_x + 2, pos_y, col=c_shade)
        display.pixel(pos_x + 1, pos_y + 1, col=c)
        display.pixel(pos_x, pos_y + 1, col=c_shade)
        display.pixel(pos_x + 1, pos_y + 2, col=c)
        display.pixel(pos_x, pos_y + 2, col=c)
        display.pixel(pos_x, pos_y + 3, col=c)
        display.pixel(pos_x + 1, pos_y + 3, col=c)
        display.pixel(pos_x + 2, pos_y + 3, col=c)
        display.pixel(pos_x + 3, pos_y + 3, col=c_shade)
        display.pixel(pos_x + 2, pos_y + 4, col=c)
        display.pixel(pos_x + 3, pos_y + 4, col=c)
        display.pixel(pos_x + 4, pos_y + 4, col=c)
        display.pixel(pos_x + 1, pos_y + 4, col=c_shade)
        display.pixel(pos_x + 3, pos_y + 5, col=c)
        display.pixel(pos_x + 4, pos_y + 5, col=c)
        display.pixel(pos_x + 3, pos_y + 6, col=c)
        display.pixel(pos_x + 4, pos_y + 6, col=c_shade)
        display.pixel(pos_x + 3, pos_y + 7, col=c)
        display.pixel(pos_x + 2, pos_y + 7, col=c_shade)

def render_num(d, num, x, Color):
    draw_grid_7seg(d, x, 0, 7, DIGITS[num // 10], Color)
    draw_grid_7seg(d, x + 5, 0, 7, DIGITS[num % 10], Color)

def render_colon(d, Color):
    draw_grid_Vseg(d, 11, 2, 7, 2, Color)
    draw_grid_Vseg(d, 11, 4, 7, 2, Color)

def render_bar(d, num, Color):
    d.rect(0, 72, num, 80, col=Color)


def ctrl_backlight(d):
    light = light_sensor.get_reading()
    display_brightness = int(light // 4) if light >= 4 else 1
#    display_brightness = int(light-12) if light >= 14 else 1
    display_brightness = 100 if light > 300 else display_brightness
    d.backlight(display_brightness)

def save_to_flash():
    if 'variables.json' in os.listdir('/apps/timer'):
        f = open('/apps/timer/variables.json', 'r')
        try:
            variables = ujson.loads(f.read())
        except:
            variables = ujson.loads('{"mode": 0,"runstamp": 0,"pausestamp": 0,"starttime": 0,"pausetime": 0}')
        f.close()
        if (
            variables["mode"] != mode or 
            variables["runstamp"] != runstamp or 
            variables["pausestamp"] != pausestamp or
            variables["starttime"] != starttime or
            variables["pausetime"] != pausetime or
            not ("mode" and "runstamp" and "pausestamp" and "starttime" and "pausetime" in variables)
        ):
            variables["mode"] = mode
            variables["runstamp"] = runstamp
            variables["pausestamp"] = pausestamp
            variables["starttime"] = starttime
            variables["pausetime"] = pausetime
            f = open('/apps/timer/variables.json', 'w')
            f.write(ujson.dumps(variables))
            f.close()

TIMER_START = 0
TIMER_RUN = 1
TIMER_PAUSE = 2
TIMER_ALARM = 3
STOPWATCH_RUN = 4
STOPWATCH_PAUSE = 5
RESET = 6

pressed_prev = 0
button_startstopp_time = 0
button_min_time = 0
button_sec_time = 0
flashlight = 0
vibrated = False

mode = TIMER_START
runstamp = 0
pausestamp = 0
starttime = 0
pausetime = 0

if 'variables.json' in os.listdir('/apps/timer'):
    f = open('/apps/timer/variables.json', 'r')
    try:
        variables = ujson.loads(f.read())
        if "mode" and "runstamp" and "pausestamp" and "starttime" and "pausetime" in variables:
            mode = variables["mode"]
            runstamp = variables["runstamp"]
            pausestamp = variables["pausestamp"]
            starttime = variables["starttime"]
            pausetime = variables["pausetime"]
    except:
        pass
    f.close()


light_sensor.start()
with display.open() as d:
    while True:
        t = utime.time_ms()
        t_year, t_month, t_mday, t_hour, t_min, t_sec, t_wday, t_yday = utime.localtime()

                
        if mode == STOPWATCH_RUN:
            showtimems = t - runstamp
        elif mode == TIMER_RUN:
            showtimems = runstamp - t +1000
            if showtimems < 1000:
                mode = TIMER_ALARM
        else:
            showtimems = pausestamp
        if mode == TIMER_ALARM:
            Background = ALARMCOLOR[0]
            Color      = ALARMCOLOR[1]
            showtimems = t - runstamp
            if showtimems > 6000000:
                showtimems = 0
                pausestamp = 0
                starttime = 0
                mode = TIMER_START
                save_to_flash()
        elif (mode == TIMER_START and pausestamp > 0) or mode == TIMER_RUN or mode == TIMER_PAUSE:
            Background = TIMERCOLOR[0]
            Color      = TIMERCOLOR[1]
        else:
            Background = STOPWATCHCOLOR[0]
            Color      = STOPWATCHCOLOR[1]

        d.clear(Background)

#        d.clear()
#        Color = color.from_hsv(((hour*60+min)*360/1439+240), 1, 1)
        showtimes = showtimems/1000
        
        if mode != TIMER_ALARM or ((showtimems%1000)<500):
            if (mode != STOPWATCH_RUN and mode != TIMER_RUN) or ((showtimems%500)<250):
                render_colon(d,Color)

            text = ("0"+str(t_hour))[-2:]+":"+("0"+str(t_min))[-2:] #Time

            if mode == STOPWATCH_RUN or mode == STOPWATCH_PAUSE or mode == RESET or (mode == TIMER_START and pausestamp == 0):
                sec = abs(int(showtimes%60))
                min = abs(int((showtimes/60)%60))

                hour = abs(int((showtimes/3600)%24))
                day  = abs(int(showtimes/86400))
                text += " " + str(day)+"d" + ("0"+str(hour))[-2:]+"h" #Extended Time
            else:
                sec = abs(int(showtimes%60))
                min = abs(int((showtimes/60)%100))

                if starttime < 60000: 
                    text += " "
                text += ("0"+str(int(starttime/60)))[-2:] + "m" + ("0"+str(int(starttime%60)))[-2:] #Added Time
            d.print(text, fg=Color, bg=None, posx=5, posy=52)

            render_num(d, min, 1,Color)
            render_num(d, sec, 13,Color)

            if (mode == TIMER_RUN or mode == TIMER_PAUSE) and starttime > 0 and showtimes-1 > 0:
                render_bar(d, int(140*((showtimes-0.999)/(starttime))), Color)
            if mode == TIMER_START and pausestamp > 0:
                render_bar(d, 140, Color)
            
        render_battery(d)
        d.update()
        #ctrl_backlight(d)

        if mode == TIMER_ALARM and ((showtimems%1000)<500):
            if not vibrated:
                vibra.vibrate(VIBRATETIME)
                vibrated = True
        else:             
            vibrated = False

        pressed = buttons.read(buttons.BOTTOM_LEFT | buttons.TOP_RIGHT | buttons.BOTTOM_RIGHT)

        if pressed & buttons.TOP_RIGHT and not pressed_prev & buttons.TOP_RIGHT:
            button_startstopp_time = t
        elif not pressed and pressed_prev & buttons.TOP_RIGHT:
            if button_startstopp_time+250 < t:
                os.exec("/main.py")
            else:
                if mode == STOPWATCH_RUN:
                    mode = STOPWATCH_PAUSE
                    pausestamp = t - runstamp
                elif mode == STOPWATCH_PAUSE:
                    mode = STOPWATCH_RUN
                    runstamp = t - pausestamp

                elif mode == TIMER_START:
                    starttime = int(pausestamp/1000)
                    if starttime == 0:
                        mode = STOPWATCH_RUN
                        runstamp = t
                    else:
                        mode = TIMER_RUN
                        runstamp = t + pausestamp
                elif mode == TIMER_RUN:
                    mode = TIMER_PAUSE
                    pausestamp = runstamp - t + 1000
                elif mode == TIMER_PAUSE:
                    mode = TIMER_RUN
                    runstamp = pausestamp + t -1000
                elif mode == TIMER_ALARM:
                    mode = TIMER_START
                    if starttime < 6000:
                        pausestamp = starttime*1000
                    else:
                        pausestamp = 5999000
                save_to_flash()
        if pressed & buttons.BOTTOM_LEFT and pressed & buttons.BOTTOM_RIGHT:
            mode = RESET
            pausestamp = 0
            starttime = 0
        elif mode == RESET and pressed == 0:
            mode = TIMER_START
            save_to_flash()

        if (pressed & buttons.BOTTOM_LEFT or pressed & buttons.BOTTOM_RIGHT):
            if mode == TIMER_PAUSE or mode == STOPWATCH_PAUSE:
                pausestamp = int(pausestamp/1000)*1000+999

        if mode == TIMER_START or mode == TIMER_PAUSE or mode == STOPWATCH_PAUSE or mode == TIMER_ALARM:
            if pressed & buttons.BOTTOM_LEFT and not pressed_prev & buttons.BOTTOM_LEFT:
                button_min_time = t
                if mode == TIMER_ALARM:
                    pausestamp = 0
                    starttime = 0
                    mode = TIMER_START
                    save_to_flash()                    
                else:
                    pausestamp = int(pausestamp + 60000)
                    starttime += 60
                    if pausestamp >= 6000000:
                        pausestamp -= 6000000
                        starttime -= 6000
            elif pressed & buttons.BOTTOM_LEFT and button_min_time+250 < t:
                button_min_time += 100
                pausestamp = int(pausestamp + 60000)
                starttime += 60
                if pausestamp >= 6000000:
                    pausestamp -= 6000000
                    starttime -= 6000

            if pressed & buttons.BOTTOM_RIGHT and not pressed_prev & buttons.BOTTOM_RIGHT:
                button_sec_time = t
                if mode == TIMER_ALARM:
                    pausestamp = 0
                    starttime = 0
                    mode = TIMER_START
                    save_to_flash()                    
                else:
                    pausestamp = (int(pausestamp/1000+1)*1000)
                    starttime += 1
                    if (int(pausestamp/1000)%60) == 0:
                        pausestamp -= 60000
                        starttime -= 60
            elif pressed & buttons.BOTTOM_RIGHT and button_sec_time+250 < t:
                button_sec_time += 100
                pausestamp = (int(pausestamp/1000+1)*1000)
                starttime += 1
                if (int(pausestamp/1000)%60) == 0:
                    pausestamp -= 60000
                    starttime -= 60

        pressed_prev = pressed
