# tm1637.py — minimal bit-banged driver for the TM1637 4-digit 7-segment display.
#
# The TM1637 uses a 2-wire protocol that *looks* like I2C but is not: there is
# no device address and the data line is read on a fixed clock edge. So we
# bit-bang it on two ordinary GPIO pins (CLK + DIO) with digitalio.
#
# Adapted for CircuitPython from the widely used MicroPython tm1637 driver
# (Mike Causer, MIT). Push-pull outputs are used and the ACK bit is clocked but
# ignored, which is reliable on the Grove TM1637 modules (they have pull-ups).

import time
from digitalio import DigitalInOut, Direction

# Command bytes
_CMD_DATA = const(0x40)   # data command: automatic address increment
_CMD_ADDR = const(0xC0)   # address command: 0xC0 | position (0-3)
_CMD_DISP = const(0x80)   # display control: 0x80 | on-bit | brightness
_DISP_ON = const(0x08)    # display-on bit (OR with brightness 0-7)

_COLON = const(0x80)      # high bit of digit 1 drives the center colon

# 7-segment patterns for 0-9 then A-F (gfedcba bit order)
_SEGMENTS = (
    0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07,
    0x7F, 0x6F, 0x77, 0x7C, 0x39, 0x5E, 0x79, 0x71,
)

# Short delay so we stay under the TM1637's ~250 kHz clock limit. The call
# overhead alone is usually enough, but the explicit sleep keeps it safe.
def _tick():
    time.sleep(5e-6)


class TM1637:
    def __init__(self, clk, dio, brightness=5):
        self.clk = DigitalInOut(clk)
        self.dio = DigitalInOut(dio)
        self.clk.direction = Direction.OUTPUT
        self.dio.direction = Direction.OUTPUT
        self.clk.value = True
        self.dio.value = True
        self.brightness = max(0, min(7, brightness))
        self.clear()

    # --- low-level protocol ---------------------------------------------
    def _start(self):
        self.dio.value = False
        _tick()
        self.clk.value = False
        _tick()

    def _stop(self):
        self.clk.value = False
        _tick()
        self.dio.value = False
        _tick()
        self.clk.value = True
        _tick()
        self.dio.value = True
        _tick()

    def _write_byte(self, b):
        for _ in range(8):
            self.clk.value = False
            _tick()
            self.dio.value = bool(b & 1)
            _tick()
            self.clk.value = True
            _tick()
            b >>= 1
        # 9th clock = ACK (we clock it but don't read it back)
        self.clk.value = False
        _tick()
        self.clk.value = True
        _tick()
        self.clk.value = False
        _tick()

    # --- public API ------------------------------------------------------
    def write(self, segments, pos=0):
        """Write raw segment bytes (a list of up to 4 ints) starting at pos."""
        self._start()
        self._write_byte(_CMD_DATA)
        self._stop()

        self._start()
        self._write_byte(_CMD_ADDR | (pos & 0x03))
        for seg in segments:
            self._write_byte(seg)
        self._stop()

        self._start()
        self._write_byte(_CMD_DISP | _DISP_ON | self.brightness)
        self._stop()

    def clear(self):
        self.write([0, 0, 0, 0])

    def show_time(self, total_seconds, colon=True):
        """Render seconds as MM:SS (zero-padded) across the four digits."""
        if total_seconds < 0:
            total_seconds = 0
        mm = total_seconds // 60
        ss = total_seconds % 60
        segs = [
            _SEGMENTS[(mm // 10) % 10],
            _SEGMENTS[mm % 10] | (_COLON if colon else 0),
            _SEGMENTS[(ss // 10) % 10],
            _SEGMENTS[ss % 10],
        ]
        self.write(segs)