import time
import board
import math
import random
import busio
import usb_hid
from digitalio import DigitalInOut, Direction, Pull
import displayio
from vectorio import Rectangle, Circle, Polygon
from adafruit_display_text import label
from adafruit_bitmap_font import bitmap_font
from adafruit_hid.keyboard import Keyboard
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS
import gc9a01
from characters import characters, shortcuts, words, keycodes

kbd = Keyboard(usb_hid.devices)
layout = KeyboardLayoutUS(kbd)

font = bitmap_font.load_font("fonts/VictorMonoNerdFont-SemiBoldItalic-32.pcf")

displayio.release_displays()

mode_labels = [
    ["Letters", "Shortcuts", "Words", "Predictive"],
    ["Lettres", "Raccourcis", "Mots", "Prédictif"],
]
language_labels = ["English", "Français"]
backlight_labels = ["Backlight", "Rétroéclairage"]

tft_clk = board.GP14
tft_mosi = board.GP15
tft_rst = board.GP27
tft_dc = board.GP28
tft_cs = board.GP29

tft_bl = DigitalInOut(board.GP26)
tft_bl.direction = Direction.OUTPUT
tft_bl.value = True

key0 = DigitalInOut(board.GP5)
key0.pull = Pull.UP
key1 = DigitalInOut(board.GP6)
key1.pull = Pull.UP
key2 = DigitalInOut(board.GP4)
key2.pull = Pull.UP
key3 = DigitalInOut(board.GP3)
key3.pull = Pull.UP
key4 = DigitalInOut(board.GP1)
key4.pull = Pull.UP
key5 = DigitalInOut(board.GP2)
key5.pull = Pull.UP
key6 = DigitalInOut(board.GP0)
key6.pull = Pull.UP

spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)

display_bus = displayio.FourWire(spi, command=tft_dc, chip_select=tft_cs, reset=tft_rst)
display = gc9a01.GC9A01(display_bus, width=240, height=240)

main_display = displayio.Group()
display.root_group = main_display

label_position_radius = 90
circle_radius = 16


def create_settings_text(y):
    text = label.Label(
        font, text="", color=0xFFFFFF, anchor_point=(0.5, 0.5), anchored_position=(0, 0)
    )
    text_group = displayio.Group(scale=1)
    text_group.x = 120
    text_group.y = y
    text_group.append(text)
    main_display.append(text_group)
    return text


top_text = create_settings_text(50)
middle_text = create_settings_text(110)
bottom_text = create_settings_text(170)


def calculate_text_position(key_angle, label_position_radius=label_position_radius):
    x = int(120 + label_position_radius * math.sin(key_angle))
    y = int(120 - label_position_radius * math.cos(key_angle))
    return x, y


def create_text_area_angle(
    key_angle, text_colour=0xFFFFFF, label_position_radius=label_position_radius
):
    return create_text_area(
        *calculate_text_position(
            key_angle,
            text_colour=text_colour,
            label_position_radius=label_position_radius,
        )
    )


def create_text_area(x, y, text_colour=0xFFFFFF):
    key_display = displayio.Group(scale=1)
    key_display.x = x
    key_display.y = y
    key_text = label.Label(
        font,
        text=" ",
        color=text_colour,
        anchor_point=(0.5, 0.5),
        anchored_position=(0, 0),
    )
    key_display.append(key_text)
    main_display.append(key_display)
    return key_text


def create_circle(x, y, key_colour, circle_radius=circle_radius):
    key_palette = displayio.Palette(1)
    key_circle = Circle(pixel_shader=key_palette, radius=circle_radius, x=x, y=y)
    main_display.append(key_circle)
    return key_palette, key_circle


def create_key_display(
    key_angle,
    key_colour,
    text_colour=0xFFFFFF,
    circle_radius=circle_radius,
    label_position_radius=label_position_radius,
):
    x, y = calculate_text_position(
        key_angle, label_position_radius=label_position_radius
    )
    key_palette, key_circle = create_circle(
        x, y, key_colour, circle_radius=circle_radius
    )
    key_text = create_text_area(x, y, text_colour=text_colour)
    return key_text, key_palette, key_colour, key_circle


key0_text, key0_palette, key0_colour, key0_circle = create_key_display(
    -2.0 * math.pi / 3.0, (219, 97, 71)
)
key1_text, key1_palette, key1_colour, key1_circle = create_key_display(
    -math.pi / 3.0, (100, 150, 40)
)
key2_text, key2_palette, key2_colour, key2_circle = create_key_display(
    0.0, (40, 150, 100)
)
key3_text, key3_palette, key3_colour, key3_circle = create_key_display(
    math.pi / 3.0, (100, 40, 150)
)
key4_text, key4_palette, key4_colour, key4_circle = create_key_display(
    2.0 * math.pi / 3.0, (40, 40, 150)
)
selected_circle_radius = 32
selected_text, selected_palette, selected_colour, selected_circle = create_key_display(
    math.pi,
    (255, 255, 255),
    text_colour=0x000000,
    circle_radius=selected_circle_radius,
    label_position_radius=70,
)

key5_colour = (0, 100, 30)
key5_palette = displayio.Palette(1)
key5_circle = Circle(pixel_shader=key5_palette, radius=circle_radius, x=120 - 32, y=120)
main_display.append(key5_circle)

key6_colour = (15, 100, 200)
key6_palette = displayio.Palette(1)
key6_circle = Circle(pixel_shader=key6_palette, radius=circle_radius, x=120 + 32, y=120)
main_display.append(key6_circle)


def swap_to_settings_display():
    tft_bl.value = True
    key0_text.text = ""
    key1_text.text = ""
    key2_text.text = ""
    key3_text.text = ""
    key4_text.text = ""
    selected_text = ""
    key0_circle.radius = 0
    key1_circle.radius = 0
    key2_circle.radius = 0
    key3_circle.radius = 0
    key4_circle.radius = 0
    key5_circle.radius = 0
    key6_circle.radius = 0
    selected_circle.radius = 0


def settings_display(mode, language, backlight, location):
    top_text.text = mode_labels[language][mode]
    top_text.color = 0x1C4D2D if location == 0 else 0xFFFFFF
    middle_text.text = language_labels[language]
    middle_text.color = 0x1C4D2D if location == 1 else 0xFFFFFF
    bottom_text.text = backlight_labels[language]
    bottom_text.color = (
        (0x1C4D2D if backlight else 0xFF0000) if location == 2 else 0xFFFFFF
    )


def swap_away_from_settings_display():
    top_text.text = ""
    middle_text.text = ""
    bottom_text.text = ""


def swap_to_character_display():
    swap_away_from_settings_display()
    key0_circle.radius = circle_radius
    key1_circle.radius = circle_radius
    key2_circle.radius = circle_radius
    key3_circle.radius = circle_radius
    key4_circle.radius = circle_radius
    key5_circle.radius = circle_radius
    key6_circle.radius = circle_radius
    selected_circle.radius = selected_circle_radius


def edit_character(pin, character, key_text, key_palette, colour):
    if not pin:
        key_palette[0] = (0, 0, 0)
        if character is None:
            key_text.text = ""
        else:
            key_text.text = character
    else:
        key_text.text = ""
        key_palette[0] = colour


def character_display(pin0, pin1, pin2, pin3, pin4, selection):
    edit_character(
        pin0,
        characters[1][pin1][pin2][pin3][pin4][selection],
        key0_text,
        key0_palette,
        key0_colour,
    )
    edit_character(
        pin1,
        characters[pin0][1][pin2][pin3][pin4][selection],
        key1_text,
        key1_palette,
        key1_colour,
    )
    edit_character(
        pin2,
        characters[pin0][pin1][1][pin3][pin4][selection],
        key2_text,
        key2_palette,
        key2_colour,
    )
    edit_character(
        pin3,
        characters[pin0][pin1][pin2][1][pin4][selection],
        key3_text,
        key3_palette,
        key3_colour,
    )
    edit_character(
        pin4,
        characters[pin0][pin1][pin2][pin3][1][selection],
        key4_text,
        key4_palette,
        key4_colour,
    )
    if any([pin0, pin1, pin2, pin3, pin4]):
        character = characters[pin0][pin1][pin2][pin3][pin4][selection]
        if character is None:
            selected_palette[0] = (0, 0, 0)
            selected_text.text = ""
        else:
            selected_text.text = character
            selected_palette[0] = selected_colour
    else:
        selected_text.text = ""
        selected_palette[0] = (0, 0, 0)


def mode_display(selection):
    if selection is 0:
        key5_palette[0] = (0, 0, 0)
        key6_palette[0] = (0, 0, 0)
    elif selection is 1:
        key5_palette[0] = key5_colour
        key6_palette[0] = (0, 0, 0)
    elif selection is 2:
        key5_palette[0] = (0, 0, 0)
        key6_palette[0] = key6_colour
    elif selection is 3:
        key5_palette[0] = key5_colour
        key6_palette[0] = key6_colour


def pressed(key):
    return not key.value


def poll():
    return [
        pressed(key0),
        pressed(key1),
        pressed(key2),
        pressed(key3),
        pressed(key4),
    ], [pressed(key5), pressed(key6)]


def toggle(_p, p, t):
    updates = update(_p, p)
    toggles = [not _t if u and __p else _t for _t, u, __p in zip(t, updates, _p)]
    return any(updates), toggles


def update(_p, p):
    return [___p != __p for ___p, __p in zip(_p, p)]


def get_time(p, t):
    return [_t + 1 if _p else 0 for _p, _t in zip(p, t)]


def send_keycode(v):
    if v is not None:
        kbd.send(*keycodes[v])


def send_character(p, s):
    send_keycode(characters[p[0]][p[1]][p[2]][p[3]][p[4]][s])


def send_shortcut(p, s):
    send_keycode(shortcuts[p[0]][p[1]][p[2]][p[3]][p[4]][s])


def send_word(p, s, l):
    w = words[l][p[0]][p[1]][p[2]][p[3]][p[4]][s]
    if w is not None:
        layout.write(w)
        layout.write(" ")


def release_countdown(p, t, start=3):
    return [max(_t - 1, 0) if not _p else start for _p, _t in zip(p, t)]


def main(rate=0.05, start=3):
    p = [False] * 5
    t = [0] * 5
    m = [False] * 2
    tm = [0] * 2
    toggles = [False] * 2
    selection = 0
    settings = False
    mode = 0
    language = 0
    backlight = True
    while True:
        _p, _m = poll()
        t = release_countdown(_p, t, start=start)
        tm = get_time(_m, tm)

        if all([_tm > 60 for _tm in tm]):
            if not settings:
                settings = True
                toggles = [False] * 2
                swap_to_settings_display()
                location = 0
            tm = [0] * 2
        elif settings:
            u = update(_m, m)
            if u[0] and _m[0]:
                location += 1
                if location > 2:
                    location = 0
                    settings = not settings
            if u[1] and _m[1]:
                if location == 0:
                    mode += 1
                    if mode > 3:
                        mode = 0
                elif location == 1:
                    language += 1
                    if language > 1:
                        language = 0
                elif location == 2:
                    backlight = not backlight
            if settings:
                if any(u):
                    settings_display(mode, language, backlight, location)
            else:
                tft_bl.value = backlight
                swap_to_character_display()

        elif not settings:
            u = any(update(_p, p))
            um, toggles = toggle(_m, m, toggles)
            if um:
                selection = toggles[0] + 2 * toggles[1]
                if backlight:
                    mode_display(selection)

            if (u or um) and backlight and mode == 0:
                character_display(*_p, selection)

            if u and not any(_p):
                pressed = [_t > 0 for _t in t]
                if mode == 0:
                    send_character(pressed, selection)
                elif mode == 1:
                    send_shortcut(pressed, selection)
                elif mode == 2:
                    send_word(pressed, selection, language)

        p = _p
        m = _m
        time.sleep(rate)


main()