# Copyright © 2026 Remco van 't Veer
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

import machine, time
import display, events, rotary_encoder, snooze, sound

DISPLAY_DIN_PIN = 26

SNOOZE_RX_PIN = 27
SNOOZE_TX_PIN = 28

ROT_CLK = 5 # s1
ROT_DT = 6 # s2
ROT_SW = 7

DFR_RX = 0
DFR_TX = 1
DFR_BUSY = 2

DCF77_PON = 4
DCF77_OUT = 3

AMP_DIN = 0
AMP_LRCLK = 2
AMP_BCLK = 1

def test_rot():
    clk = machine.Pin(ROT_CLK, machine.Pin.IN)
    dt = machine.Pin(ROT_DT, machine.Pin.IN)
    sw = machine.Pin(ROT_SW, machine.Pin.IN, machine.Pin.PULL_UP)

    current_count = count = 0
    current_value = [clk.value(), dt.value(), sw.value()]
    while True:
        old_clk, old_dt, old_sw = current_value
        new_value = [clk.value(), dt.value(), sw.value()]
        new_clk, new_dt, new_sw = new_value

        if new_value != current_value:
            if new_clk != old_clk and new_clk == 1:
                if new_dt == 0:
                    count = count + 1
                else:
                    count = count - 1

            h, m  = divmod(count, 60)
            display_time(h % 24, m)

            current_value = new_value

def test_clock():
    while True:
        display_time_now();
        display_toggle_dots();
        time.sleep(.5)
        display_toggle_dots();
        time.sleep(.5)

def make_event_handler(events, event_type):
    return lambda: events.push(event_type)

class Twofac:
    MODE_SLEEP = 0
    MODE_ALARM = 1
    MODE_SNOOZED = 2
    MODE_DOCKED = 3
    MODE_WAKE_HOURS = 4
    MODE_WAKE_MINUTES = 5
    MODE_SET_HOURS = 6
    MODE_SET_MINUTES = 7

    EVENT_ROT_SW_ON = 0
    EVENT_ROT_SW_OFF = 1
    EVENT_ROT_CW = 2
    EVENT_ROT_CCW = 3
    EVENT_SNOOZE = 4

    DEFAULT_COLOR = (4, 2, 0)
    WAKE_COLOR = (0, 0, 16)
    SET_COLOR = (0, 16, 0)

    WAKE_MINUTE_STEP = 1
    WAKE_UP_SOUNDS = ["rooster.wav", "alarm.wav", "alarm-clock.wav"]

    MAX_SNOOZE = 2
    SNOOZE_TIME = 1

    RESET_WAIT = 5

    debug = False
    mode = MODE_SET_HOURS

    wake_time_hours = 7
    wake_time_minutes = 0

    set_time_hours = 0
    set_time_minutes = 0

    def __init__(self):
        self.events = events.Events()
        self.display = display.Display(data_gpio = DISPLAY_DIN_PIN)
        self.display.color = self.DEFAULT_COLOR
        self.rotary_encoder = rotary_encoder.RotaryEncoder(sw_gpio = ROT_SW,
                                                           sw_on_handler = lambda: self.events.push(self.EVENT_ROT_SW_ON),
                                                           sw_off_handler = lambda: self.events.push(self.EVENT_ROT_SW_OFF),
                                                           clk_gpio = ROT_CLK,
                                                           dt_gpio = ROT_DT,
                                                           cw_handler = lambda: self.events.push(self.EVENT_ROT_CW),
                                                           ccw_handler = lambda: self.events.push(self.EVENT_ROT_CCW))
        self.snooze = snooze.Snooze(sm_id = 1,
                                    tx_gpio = SNOOZE_TX_PIN,
                                    rx_gpio = SNOOZE_RX_PIN,
                                    snooze_handler = lambda: self.events.push(self.EVENT_SNOOZE))

        self.sound = sound.Sound(sck_gpio = AMP_BCLK,
                                 ws_gpio = AMP_LRCLK,
                                 sd_gpio = AMP_DIN)


    def print(self, *args):
        self.debug and print(*args)

    debounce = {}

    def debounced(self, event, target, timeout = 100):
        if event != target:
            return False

        current_ticks = time.ticks_ms()
        previous_ticks = self.debounce.get(target, 0)
        if current_ticks - previous_ticks > timeout:
            self.debounce[target] = current_ticks
            self.rot_sw_time = None
            return True

    rot_sw_time = None

    def handler(self):
        event = self.events.pop()
        _, _, _, hours, minutes, secs, _, _ = time.gmtime()

# reset

        self.print("event", event)
        if event == self.EVENT_ROT_SW_ON:
            self.rot_sw_time = time.time()
        elif event == self.EVENT_ROT_SW_OFF:
            if self.rot_sw_time and (time.time() - self.rot_sw_time) > self.RESET_WAIT:
                self.print("reset")
                machine.reset()

# sleeping and alarm

        if self.mode == self.MODE_SLEEP:
            self.snooze.stop()
            self.display.display_current_time(toggle_dots = True)
            self.print(hours, minutes)

            if self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_DOCKED
                self.print("mode: docked")
            elif hours == self.wake_time_hours and minutes == self.wake_time_minutes:
                self.mode = self.MODE_ALARM
                self.print("mode: alarm")
                self.snooze_count = 0

        elif self.mode == self.MODE_ALARM:
            self.snooze.start()

            if self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_DOCKED
                self.display.color = self.DEFAULT_COLOR
                self.snooze.stop()
                self.sound.stop()
                self.print("mode: docked")
            elif self.debounced(event, self.EVENT_SNOOZE, timeout = 10000) and self.snooze_count < self.MAX_SNOOZE:
                self.display.color = self.DEFAULT_COLOR
                self.mode = self.MODE_SNOOZED
                self.snooze_count += 1
                self.snooze.stop()
                self.sound.stop()
                self.print("mode: snoozed", self.snooze_count)
            else:
                self.display.color = self.WAKE_COLOR
                self.display.display_current_time(toggle_dots = True)

            if not self.sound.is_playing():
                self.sound.play(self.WAKE_UP_SOUNDS[self.snooze_count % len(self.WAKE_UP_SOUNDS)])

        elif self.mode == self.MODE_SNOOZED:
            self.snooze.stop()

            if self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_DOCKED
                self.display.color = self.DEFAULT_COLOR
                self.print("mode: docked")
            else:
                self.display.display_current_time(toggle_dots = True, color_dots = self.WAKE_COLOR)

                c = hours * 60 + minutes
                t = (self.wake_time_hours * 60) + self.wake_time_minutes + (self.SNOOZE_TIME * self.snooze_count)

                if t < c:
                    self.mode = self.MODE_ALARM
                    self.print("mode: alarm")

        elif self.mode == self.MODE_DOCKED:
            self.snooze.stop()
            self.display.dots_on()
            self.display.display_current_time(toggle_dots = False)

            if self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_WAKE_HOURS
                self.print("mode: wake hours")
                self.display.set_hours(self.wake_time_hours, color = self.WAKE_COLOR)
                self.display.set_minutes(self.wake_time_minutes)
                self.display.commit()

# setting the wake time

        elif self.mode == self.MODE_WAKE_HOURS:
            if event == self.EVENT_ROT_CW:
                self.wake_time_hours = (self.wake_time_hours + 1) % 24
                self.display.set_hours(self.wake_time_hours, color = self.WAKE_COLOR)
                self.display.commit()
                self.print("wake hours", self.wake_time_hours)
            elif event == self.EVENT_ROT_CCW:
                self.wake_time_hours = (self.wake_time_hours - 1) % 24
                self.display.set_hours(self.wake_time_hours, color = self.WAKE_COLOR)
                self.display.commit()
                self.print("wake hours", self.wake_time_hours)
            elif self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_WAKE_MINUTES
                self.print("mode: wake minutes")
                self.display.set_hours(self.wake_time_hours)
                self.display.set_minutes(self.wake_time_minutes, color = self.WAKE_COLOR)
                self.display.commit()

        elif self.mode == self.MODE_WAKE_MINUTES:
            if event == self.EVENT_ROT_CW:
                self.wake_time_minutes = (self.wake_time_minutes + self.WAKE_MINUTE_STEP) % 60
                self.display.set_minutes(self.wake_time_minutes, color = self.WAKE_COLOR)
                self.display.commit()
                self.print("wake minutes", self.wake_time_minutes)
            elif event == self.EVENT_ROT_CCW:
                self.wake_time_minutes = (self.wake_time_minutes - self.WAKE_MINUTE_STEP) % 60
                self.display.set_minutes(self.wake_time_minutes, color = self.WAKE_COLOR)
                self.print("wake minutes", self.wake_time_minutes)
                self.display.commit()
            elif self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_SLEEP
                self.print("mode: sleep")
                self.display.set_minutes(self.wake_time_minutes)
                self.display.commit()

# setting the current time

        elif self.mode == self.MODE_SET_HOURS:
            if event == self.EVENT_ROT_CW:
                self.set_time_hours = (self.set_time_hours + 1) % 24
                self.display.set_hours(self.set_time_hours, color = self.SET_COLOR)
                self.display.commit()
                self.print("set hours", self.set_time_hours)
            elif event == self.EVENT_ROT_CCW:
                self.set_time_hours = (self.set_time_hours - 1) % 24
                self.display.set_hours(self.set_time_hours, color = self.SET_COLOR)
                self.display.commit()
                self.print("set hours", self.set_time_hours)
            elif self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.mode = self.MODE_SET_MINUTES
                self.print("mode: set minutes")
                self.display.set_hours(self.set_time_hours)
                self.display.set_minutes(self.set_time_minutes, color = self.SET_COLOR)
                self.display.commit()
            else:
                self.display.set_hours(self.set_time_hours, color = self.SET_COLOR)
                self.display.commit()

        elif self.mode == self.MODE_SET_MINUTES:
            if event == self.EVENT_ROT_CW:
                self.set_time_minutes = (self.set_time_minutes + 1) % 60
                self.display.set_minutes(self.set_time_minutes, color = self.SET_COLOR)
                self.display.commit()
                self.print("set minutes", self.set_time_minutes)
            elif event == self.EVENT_ROT_CCW:
                self.set_time_minutes = (self.set_time_minutes - 1) % 60
                self.display.set_minutes(self.set_time_minutes, color = self.SET_COLOR)
                self.print("set minutes", self.set_time_minutes)
                self.display.commit()
            elif self.debounced(event, self.EVENT_ROT_SW_OFF):
                self.display.color = self.DEFAULT_COLOR
                self.mode = self.MODE_DOCKED
                self.print("mode: docked")

                year, month, day, weekday, _, _, _, _ = machine.RTC().datetime()
                machine.RTC().datetime((year, month, day, weekday, self.set_time_hours, self.set_time_minutes, 0, 0))

    def loop(self):
        while True:
            self.handler()

if __name__ == "__main__":
    twofac = Twofac()
    twofac.debug = True
    twofac.snooze.debug = True
    twofac.loop()
