"""Step 5: the screen shows the door's state, a countdown and why it opened.

Needs ssd1306.py.
"""

from machine import Pin, I2C
import time
from ssd1306 import SSD1306_I2C

OPEN_MS = 5000
SCREEN_REFRESH_MS = 200

red = Pin(14, Pin.OUT, value=0)
green = Pin(15, Pin.OUT, value=0)
strike = Pin(25, Pin.OUT, value=0)
wired_input = Pin(21, Pin.IN, Pin.PULL_DOWN)
screen = SSD1306_I2C(128, 64, I2C(1, sda=Pin(26), scl=Pin(27)))
screen.write_cmd(0xA0)
screen.write_cmd(0xC0)


class Door:
    """Closed, or open until a given time, with the reason it opened."""

    def __init__(self):
        self.open_until = time.ticks_ms()
        self.reason = ""

    def open_for(self, milliseconds, reason):
        self.open_until = time.ticks_add(time.ticks_ms(), milliseconds)
        self.reason = reason

    def remaining_ms(self):
        return max(time.ticks_diff(self.open_until, time.ticks_ms()), 0)

    def is_open(self):
        return self.remaining_ms() > 0

    def state(self):
        if self.is_open():
            return "OPEN {} s".format((self.remaining_ms() + 999) // 1000)
        return "CLOSED"


def draw(door):
    screen.fill(0)
    screen.text("DOOR", 0, 0)
    screen.text(door.state(), 0, 16)
    screen.text(door.reason[:16], 0, 32)
    screen.show()


door = Door()
was_wired = False
last_screen = ""
last_draw = time.ticks_ms()

while True:
    wired = wired_input.value() == 1
    if wired and not was_wired:
        door.open_for(OPEN_MS, "wired")
    was_wired = wired

    strike.value(door.is_open())
    red.value(not door.is_open())
    green.value(door.is_open())

    shown = door.state() + door.reason
    if shown != last_screen and time.ticks_diff(time.ticks_ms(), last_draw) >= SCREEN_REFRESH_MS:
        draw(door)
        last_screen = shown
        last_draw = time.ticks_ms()

    time.sleep_ms(20)
