#ErgoClock - Nick Stevens May 2026 (v1.1)
#Clock program with multi-colour, 4 styles + alarm functions.
#Utlises PYGame Library for on-screen display

#setup libraries
import pygame
from time import strftime
import math

# define function to get right of string
def right(s, amount):
    return s[-amount:]

# define number postfix
def ordinal(n):
    return str(n) + ("th" if 4 <= n % 100 <= 20 else {1:"st",2:"nd",3:"rd"}.get(n%10, "th"))

# define the clock words
min_word1 = ["",
             "around ",
             "about ",
             "nearly ",
             "coming up for ",
             "",
             "just after ",
             "roughly ",
             "just about ",
             "almost "]

min_word2 = ["",
             "five ",
             "ten ",
             "quarter ",
             "twenty ",
             "twenty five ",
             "half ",
             "twenty five ",
             "twenty ",
             "quarter ",
             "ten ",
             "five ",
             ""]

min_word3 = ["",
             "past ",
             "to "]

hour_word1 = ["twelve",
              "one",
              "two",
              "three",
              "four",
              "five",
              "six",
              "seven",
              "eight",
              "nine",
              "ten",
              "eleven",
              "twelve",
              "midnight",
              "midday"]

hour_word2 = ["",
              " o'clock"]

#colours
clock_colour = ["white",
                "red",
                "chocolate",
                "yellow",
                "green",
                "blue",
                "indigo",
                "violet"]

but_col = ["yellow",
           "yellow",
           "yellow",
           "yellow",
           "yellow",
           "yellow"]

def ergoclock(hour, minute):
    # get the time right now in various forms
    current_hour12 = hour
    if hour > 12:
        current_hour12 = hour - 12
    current_hour24 = hour
    current_min = minute
    current_min2 = int(right(str(minute), 1))

    # work out the hour word
    if current_min > 32:
        current_hour12 += 1
    if current_hour12 > 12:
        current_hour12 = 1

    current_hour_word1 = hour_word1[current_hour12]

    # do we say oclock?
    if current_min == 0 and current_hour12 != 12:
        current_hour_word2 = hour_word2[1]
    else:
        current_hour_word2 = hour_word2[0]

    # work out midday or midnight words
    if current_hour24 == 0 and current_min == 0:
        current_hour_word1 = hour_word1[13]
        current_hour_word2 = "" #dont say oclock
    if current_hour24 == 12 and current_min == 0:
        current_hour_word1 = hour_word1[14]
        current_hour_word2 = "" #dont say oclock

    # first minute word - use second digit of min to pick word
    current_min_word1 = min_word1[current_min2]

    # second minute word - formula reduces the min down to pick the right word
    current_min_word2 = min_word2[((current_min - 3) // 5) + 1]

    # third minute word, work out the past or to words based on the min value
    current_min_word3 =  min_word3[0]
    if current_min > 2:
        current_min_word3 = min_word3[1]
    if current_min > 32:
        current_min_word3 = min_word3[2]
    if current_min > 57:
        current_min_word3 = min_word3[0]

    #make up the sentence
    #current_time_words = current_min_word1 + current_min_word2 + current_min_word3 + current_hour_word1 + current_hour_word2
    #return(current_time_words)

    return(current_min_word1, current_min_word2, current_min_word3, current_hour_word1, current_hour_word2)

def date():
    date_string = strftime('%A, ') + ordinal(int(strftime('%d'))) + strftime(' %B, %Y')
    return(date_string)

def oval(button_text, col, x, y):
    pygame.draw.circle(screen, col, (x + 40, y + 40), 40)
    pygame.draw.circle(screen, col, (x + 120, y + 40), 40)
    pygame.draw.rect(screen, col, (x + 40, y, 80, 80))
    text = button_font.render(button_text, True, ("black"))
    text_rect = text.get_rect(center = (x + 80, y + 40))
    screen.blit(text, text_rect)

def show_buttons1():
    oval("Alarm", but_col[0], 10, 500)
    oval("Clock", but_col[1], 180, 500)
    oval("Colour", but_col[2], 350, 500)
    oval("Date", but_col[3], 520, 500)

def show_buttons2():
    oval("Set", but_col[0], 10, 500)
    oval("Hour", but_col[4], 690, 500)
    oval("Minute", but_col[5], 860, 500)

def analogue_clock(hour, minute, second):
    #draw clock face
    ampm_text = ""
    screen.blit(clock_face, (285, 60))

    #AM/PM Text
    if hour > 12:
        ampm_text = "PM"
    else:
        ampm_text = "AM"
    text = date_font.render(ampm_text, True, ("Blue"))
    text_rect = text.get_rect(center = (510, 200))
    screen.blit(text, text_rect)

    #convert from 24H
    hour = hour % 12

    center = (510, 285)
    hour_hand_len = 120
    min_hand_len = 180
    sec_hand_len = 180

    #hour hand
    hour_angle = 90 - (hour + (minute / 60)) * 30
    hand_x = center[0] + math.cos(math.radians(hour_angle)) * hour_hand_len
    hand_y = center[1] + math.sin(-math.radians(hour_angle)) * hour_hand_len
    pygame.draw.line(screen, clock_colour[clock_colour_index], center, (hand_x, hand_y), 14)
    pygame.draw.circle(screen, clock_colour[clock_colour_index], (hand_x, hand_y), 6)

    #minute hand
    min_angle = 90 - minute * 6
    hand_x = center[0] + math.cos(math.radians(min_angle)) * min_hand_len
    hand_y = center[1] + math.sin(-math.radians(min_angle)) * min_hand_len
    pygame.draw.line(screen, clock_colour[clock_colour_index], center, (hand_x, hand_y), 14)
    pygame.draw.circle(screen, clock_colour[clock_colour_index], (hand_x, hand_y), 6)

    #second hand
    if second > -1:
        sec_angle = 90 - second * 6
        hand_x = center[0] + math.cos(math.radians(sec_angle)) * sec_hand_len
        hand_y = center[1] + math.sin(-math.radians(sec_angle)) * sec_hand_len
        pygame.draw.line(screen, clock_colour[clock_colour_index], center, (hand_x, hand_y), 4)
        pygame.draw.circle(screen, clock_colour[clock_colour_index], (hand_x, hand_y), 2)

    #draw centre
    pygame.draw.circle(screen, clock_colour[clock_colour_index], center, 16)

# pygame setup
pygame.init()
screen = pygame.display.set_mode((1024, 600), pygame.FULLSCREEN)
clock = pygame.time.Clock()
pygame.mouse.set_visible(False)

#setup fonts
use_font="freesans"
clock_font = pygame.font.SysFont(use_font, 64)
clock_font2 = pygame.font.SysFont(use_font, 72)
date_font = pygame.font.SysFont(use_font, 28)
button_font = pygame.font.SysFont(use_font, 28)
digital_font = pygame.font.SysFont(use_font, 320)
hour_font = pygame.font.SysFont(use_font,172)

#initial variables
file_path = "/home/pi/ergoclock/"
running = True
show_buttons = False
buttons_time = 0
clock_colour_index = 0
clock_type = 0
alarm_on = False
alarm_set = False
alarm_silenced = False
clock_face = pygame.image.load(file_path + "clockface.png").convert()
alarm_sound = pygame.mixer.Sound(file_path + "up-beep.wav")
touch_sound = pygame.mixer.Sound(file_path + "tone-beep.wav")
alarm_hour = 7
alarm_min = 0
show_date = True
snooze_time = 9000 # ~5 mins
alarm_snooze = snooze_time
alarm_silenced = False
alarm_active = False

button_rect1 = pygame.Rect((10, 500), (160, 80))
button_rect2 = pygame.Rect((180, 500), (160, 80))
button_rect3 = pygame.Rect((350, 500), (160, 80))
button_rect4 = pygame.Rect((520, 500), (160, 80))
button_rect5 = pygame.Rect((690, 500), (160, 80))
button_rect6 = pygame.Rect((860, 500), (160, 80))

while running:

    # fill the screen
    screen.fill("black")

    # poll for events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

        if event.type == pygame.MOUSEBUTTONDOWN:
            touch_x, touch_y = pygame.mouse.get_pos()
            touch_sound.play()

            #snooze alarm if active
            if alarm_active:
                #snooze and reset timer
                alarm_silenced = True
                alarm_snooze = snooze_time

            #Alarm button
            if button_rect1.collidepoint(touch_x, touch_y) and show_buttons:
                but_col[0] = "red"
                if alarm_set:
                    #turn on alarm
                    alarm_on = True
                    alarm_set = False
                    alarm_silenced = False
                    alarm_snooze = snooze_time
                else:
                    if alarm_on:
                        alarm_on = False
                        alarm_active = False
                    else:
                        alarm_set = True

            #Clock button
            if button_rect2.collidepoint(touch_x, touch_y) and show_buttons:
                but_col[1] = "red"
                clock_type += 1
                if clock_type > 3:
                    clock_type = 0

            #Colour button
            if button_rect3.collidepoint(touch_x, touch_y) and show_buttons:
                but_col[2] = "red"
                clock_colour_index += 1
                if clock_colour_index > 7:
                    clock_colour_index = 0

            #date Button
            if button_rect4.collidepoint(touch_x, touch_y) and show_buttons:
                but_col[3] = "red"
                if show_date:
                    show_date = False
                else:
                    show_date = True

            #Hour Button (for alarm set)
            if button_rect5.collidepoint(touch_x, touch_y) and show_buttons and alarm_set:
                but_col[4] = "red"
                alarm_hour += 1
                if alarm_hour > 23:
                    alarm_hour = 0

            #Minute button (for alarm set)
            if button_rect6.collidepoint(touch_x, touch_y) and show_buttons and alarm_set:
                but_col[5] = "red"
                alarm_min += 1
                if alarm_min > 59:
                    alarm_min = 0

            show_buttons = True
            buttons_time = 0

        #ESC for immediate exit
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    #show word clock 0
    if clock_type == 0:
        if alarm_set:
            hour = alarm_hour
            minute = alarm_min
        else:
            hour = int(strftime('%H'))
            minute = int(strftime('%M'))

        current_min_word1, current_min_word2, current_min_word3, current_hour_word1, current_hour_word2 = ergoclock(hour, minute)
        time_string = current_min_word1 + current_min_word2 + current_min_word3 + current_hour_word1 + current_hour_word2

        #if alarm_set:
        #    time_string = ergoclock(hour,minute)

        text = clock_font.render(time_string, True, clock_colour[clock_colour_index])
        text_rect = text.get_rect(center=(512, 300))
        screen.blit(text, text_rect)

    #show word clock 1
    if clock_type == 1:
        if alarm_set:
            hour = alarm_hour
            minute = alarm_min
        else:
            hour = int(strftime('%H'))
            minute = int(strftime('%M'))

        current_min_word1, current_min_word2, current_min_word3, current_hour_word1, current_hour_word2 = ergoclock(hour, minute)
        time_string = current_min_word1 + current_min_word2 + current_min_word3


        #if alarm_set:
        #    time_string = ergoclock(hour,minute)

        text = clock_font2.render(time_string, True, clock_colour[clock_colour_index])
        text_rect = text.get_rect(center=(512, 170))
        screen.blit(text, text_rect)

        time_string = current_hour_word1 + current_hour_word2
        #test words
        #time_string = "eleven o'clock"

        text = hour_font.render(time_string, True, clock_colour[clock_colour_index])
        text_rect = text.get_rect(center=(512, 350))
        screen.blit(text, text_rect)

    #show digital clock
    if clock_type == 2:
        #seconds tick
        if int(strftime("%S")) % 2 == 0:
            time_string = strftime("%H:%M")
        else:
            time_string = strftime("%H %M")
        if alarm_set:
            time_string = str(alarm_hour).zfill(2) + ":" + str(alarm_min).zfill(2)
        text = digital_font.render(time_string, True, clock_colour[clock_colour_index])
        text_rect = text.get_rect(center = (512, 300))
        screen.blit(text, text_rect)

    #analogue clock
    if clock_type == 3:
        #analogue clock
        hour = int(strftime("%H"))
        minute = int(strftime("%M"))
        second = int(strftime("%S"))

        if alarm_set:
            hour = alarm_hour
            minute = alarm_min
            second = -1

        #draw clock face
        analogue_clock(hour, minute, second)

    #show date
    if show_date:
        date_string = date()
        text = date_font.render(date_string, True, ("Blue"))
        text_rect = text.get_rect(topright = (1024, 0))
        screen.blit(text, text_rect)

    #show alarm indicator
    if alarm_on:
        alarm_string = "Alarm on"
    else:
        alarm_string = ""
    if alarm_set:
        #flash alarm set
        if int(strftime("%S")) % 2 == 0:
            alarm_string = "Alarm set"
        else:
            alarm_string = ""
    if alarm_active:
        #flash alarm on
        if int(strftime("%S")) % 2 == 0:
            alarm_string = "Alarm"
        else:
            alarm_string = ""
    text = date_font.render(alarm_string, True, ("Blue"))
    text_rect = text.get_rect(topleft = (0, 0))
    screen.blit(text, text_rect)

    #show the main controls
    if show_buttons == True:
        show_buttons1()
        if alarm_set:
            show_buttons2()
        buttons_time += 1

        #button timeout
        if buttons_time > 20:
            but_col = ["yellow", "yellow", "yellow", "yellow", "yellow", "yellow"]

        #hide the buttons
        if buttons_time > 120:
            show_buttons = False
            buttons_time = 0
            but_col = ["yellow", "yellow", "yellow", "yellow", "yellow", "yellow"]

    #check for alarm
    if alarm_on and int(strftime("%H")) == alarm_hour and int(strftime("%M")) == alarm_min:
        #activate alarm
        alarm_active = True

    if alarm_active and alarm_silenced == False:
        #play alarm sound every other second
        if int(strftime("%S")) % 2 == 0:
            alarm_sound.play()

    if alarm_active and alarm_silenced:
        #snooze timer
        alarm_snooze -= 1
        if alarm_snooze < 1:
            alarm_silenced = False

    # flip the display to updae the screen
    pygame.display.flip()

    clock.tick(60) # limit to 60 FPS

pygame.quit()