import subprocess
from time import time, sleep, localtime
from wiringpi import wiringPiSetupGpio, pinMode, digitalRead, digitalWrite, GPIO

wiringPiSetupGpio()

_CMD_SET1 = 0x40  # 0x40 data set
_CMD_SET2 = 0xc0 # 0xC0 address command set
_CMD_SET3 = 0x80 # 0x80 data control command set

# 0-9, a-f, blank, dash
_SEGMENTS = (63,6,91,79,102,109,125,7,127,111,119,124,57,94,121,113,0,64,99)

class TM1637(object):

    """

    Library for the quad 7-segment LED display modules based on the TM1637
    LED driver.
    """

    def __init__(self, clk, dio, brightness=7):
        self.clk = clk
        self.dio = dio

        if not 0 <= brightness <= 7:
            raise ValueError("Brightness out of range")
        self._brightness = brightness

        pinMode(self.clk, GPIO.INPUT)
        pinMode(self.dio, GPIO.INPUT)
        digitalWrite(self.clk, 0)
        digitalWrite(self.dio, 0)

    def _start(self):
        pinMode(self.dio, GPIO.OUTPUT)
        sleep(0.00050)

    def _stop(self):
        pinMode(self.dio, GPIO.OUTPUT)
        sleep(0.00050)
        pinMode(self.clk, GPIO.INPUT)
        sleep(0.00050)
        pinMode(self.dio, GPIO.INPUT)
        sleep(0.00050)

    def _write_comm1(self):
        self._start()
        self._write_byte(_CMD_SET1)
        self._stop()

    def _write_comm3(self):
        self._start()
        self._write_byte(_CMD_SET3 + self._brightness + 7)
        self._stop()

    def _write_byte(self, b):
        # send each bit
        for i in range(8):
            pinMode(self.clk, GPIO.OUTPUT)
            sleep(0.00050)
            pinMode(self.dio, GPIO.INPUT if b & 1 else GPIO.OUTPUT)
            sleep(0.00050)
            pinMode(self.clk, GPIO.INPUT)
            sleep(0.00050)
            b >>= 1
        pinMode(self.clk, GPIO.OUTPUT)
        sleep(0.00050)
        pinMode(self.clk, GPIO.INPUT)
        sleep(0.00050)
        pinMode(self.clk, GPIO.OUTPUT)
        sleep(0.00050)

    def brightness(self, val=None):
        """Set the display brightness 0-7."""
        if val is None:
            return self._brightness
        if not 0 <= val <= 7:
            raise ValueError("Brightness out of range")
        self._brightness = val
        self._write_comm1()
        self._write_comm3()

    def write(self, segments, pos=0):
        """Display up to 4 segments moving right from a given position.
        The MSB in the 2nd segment controls the colon between the 2nd
        and 3rd segments."""
        if not 0 <= pos <= 3:
            raise ValueError("Position out of range")
        self._write_comm1()

        # write COMM2 + first digit address
        self._start()
        self._write_byte(_CMD_SET2 + pos)
        for seg in segments:
            self._write_byte(seg)
        self._stop()
        self._write_comm3()

    def encode_digit(self, digit):
        """Convert a character 0-9, a-f to a segment."""
        return _SEGMENTS[digit & 0x0f]

    def encode_string(self, string):
        """Convert an up to 4 character length string containing 0-9, a-f,
        space, dash to an array of segments, matching the length of the
        source string."""
        segments = bytearray(4)
        for i in range(0, min(4, len(string))):
            segments[i] = self.encode_char(string[i])
        return segments

    def encode_char(self, char):
        """Convert a character 0-9, a-f, space or dash to a segment."""
        o = ord(char)
        # space
        if o == 32:
            return _SEGMENTS[16]
        # asterisk (grade symbol)
        if o == 42:
            return _SEGMENTS[18]
        # dash
        if o == 45:
            return _SEGMENTS[17]
        # uppercase A-F
        if o >= 65 and o <= 70:
            return _SEGMENTS[o-55]
        # lowercase a-f
        if o >= 97 and o <= 102:
            return _SEGMENTS[o-87]
        # 0-9
        if o >= 48 and o <= 57:
            return _SEGMENTS[o-48]
        raise ValueError("Character out of range")

    def hex(self, val):
        """Display a hex value 0x0000 through 0xffff, right aligned."""
        string = '{:04x}'.format(val & 0xffff)
        self.write(self.encode_string(string))

    def number(self, num):
        """Display a numeric value -999 through 9999, right aligned."""
        # limit to range -999 to 9999
        num = max(-999, min(num, 9999))
        string = '{0: >4d}'.format(num)
        self.write(self.encode_string(string))

    def numbers(self, num1, num2, colon=True):
        """Display two numeric values -9 through 99, with leading zeros
        and separated by a colon."""
        num1 = max(-9, min(num1, 99))
        num2 = max(-9, min(num2, 99))
        segments = self.encode_string('{0:0>2d}{1:0>2d}'.format(num1, num2))
        # colon on
        if colon:
            segments[1] |= 0x80
        self.write(segments)

    def temperature(self, num):
        """Display two digits temperature"""
        num = max(-9, min(num, 99))
        segments = self.encode_string('{0}*C'.format(num))
        self.write(segments)
