"""Part 9: the reader decides alone. PIN 1234 and one known badge. Needs mfrc522.py and ssd1306.py."""

from machine import Pin, PWM, I2C
import time
from mfrc522 import MFRC522
from ssd1306 import SSD1306_I2C

KEYS = [
    ["1", "2", "3", "A"],
    ["4", "5", "6", "B"],
    ["7", "8", "9", "C"],
    ["*", "0", "#", "D"],
]
PIN_CODE = "1234"
KNOWN_BADGE = [0x6B, 0xAD, 0x30, 0x8E]
BADGE_POLL_MS = 100
BADGE_GONE_AFTER_MISSES = 5

rows = [Pin(n, Pin.OUT, value=1) for n in (9, 8, 7, 6)]
columns = [Pin(n, Pin.IN, Pin.PULL_UP) for n in (13, 12, 11, 10)]
red = Pin(14, Pin.OUT)
green = Pin(15, Pin.OUT)
buzzer = PWM(Pin(0))
buzzer.freq(4000)
reader = MFRC522(spi_id=0, sck=18, mosi=19, miso=16, cs=17, rst=20)
screen = SSD1306_I2C(128, 64, I2C(1, sda=Pin(26), scl=Pin(27)))
screen.write_cmd(0xA0)
screen.write_cmd(0xC0)


def pressed_key():
    found = None
    for row, labels in zip(rows, KEYS):
        row.value(0)
        for column, label in zip(columns, labels):
            if column.value() == 0:
                found = label
        row.value(1)
    return found


def read_badge():
    reader.init()
    status, _ = reader.request(reader.REQIDL)
    if status == reader.OK:
        status, uid = reader.SelectTagSN()
    return uid if status == reader.OK else None


def beep(milliseconds):
    buzzer.duty_u16(32768)
    time.sleep_ms(milliseconds)
    buzzer.duty_u16(0)


def beeps(count, milliseconds):
    for _ in range(count):
        beep(milliseconds)
        time.sleep_ms(milliseconds)


def display(*lines):
    screen.fill(0)
    for number, line in enumerate(lines):
        screen.text(line, 0, number * 16)
    screen.show()


def show_idle():
    display("Enter PIN", "or show badge")


def grant_access():
    display("Access granted")
    green.value(1)
    beep(600)
    time.sleep_ms(900)
    green.value(0)


def deny_access():
    display("Access denied")
    red.value(1)
    beeps(3, 120)
    time.sleep_ms(800)
    red.value(0)


def decide(allowed):
    if allowed:
        grant_access()
    else:
        deny_access()
    show_idle()


typed = ""
previous_key = None
previous_badge = None
misses = 0
last_poll = time.ticks_ms()
show_idle()

while True:
    key = pressed_key()
    if key is not None and key != previous_key:
        beep(40)
        if key == "#":
            decide(typed == PIN_CODE)
            typed = ""
        elif key == "*":
            typed = ""
            show_idle()
        else:
            typed += key
            display("PIN:", "*" * len(typed))
    previous_key = key

    if time.ticks_diff(time.ticks_ms(), last_poll) >= BADGE_POLL_MS:
        last_poll = time.ticks_ms()
        uid = read_badge()
        if uid is None:
            misses += 1
            if previous_badge is not None and misses > BADGE_GONE_AFTER_MISSES:
                print("Badge removed")
                previous_badge = None
        else:
            misses = 0
            if uid != previous_badge:
                previous_badge = uid
                print("Badge:", reader.tohexstring(uid))
                decide(uid == KNOWN_BADGE)
                typed = ""

    time.sleep_ms(20)
