from micropython import const

from kmk.consts import CIRCUITPYTHON, MICROPYTHON

PULL_UP = const(1)
PULL_DOWN = const(2)


try:
    import board
    import digitalio

    PLATFORM = CIRCUITPYTHON
    PIN_SOURCE = board
except ImportError:
    import machine

    PLATFORM = MICROPYTHON
    PIN_SOURCE = machine.Pin.board


def get_pin(pin):
    '''
    Cross-platform method to find a pin by string.

    The pin definitions are platform-dependent, but this provides
    a way to say "I'm using pin D20" without rolling a D20 and
    having to actually learn MicroPython/CircuitPython and the
    differences in how they handle pinouts.

    This also makes the keymap sanity checker actually work for
    CircuitPython boards, since it's not possible in CPY to
    define a module stub for `board` that uses Passthrough
    natively (which is how the MicroPython stub worked originally)
    '''
    return getattr(PIN_SOURCE, pin)


class AbstractedDigitalPin:
    def __init__(self, pin):
        self.raw_pin = pin

        if PLATFORM == CIRCUITPYTHON:
            self.pin = digitalio.DigitalInOut(pin)
        elif PLATFORM == MICROPYTHON:
            self.pin = machine.Pin(pin)
        else:
            self.pin = pin

        self.call_value = callable(self.pin.value)

    def __repr__(self):
        return 'AbstractedPin({})'.format(repr(self.raw_pin))

    def switch_to_input(self, pull=None):
        if PLATFORM == CIRCUITPYTHON:
            if pull == PULL_UP:
                return self.pin.switch_to_input(pull=digitalio.Pull.UP)
            elif pull == PULL_DOWN:
                return self.pin.switch_to_input(pull=digitalio.Pull.DOWN)

            return self.pin.switch_to_input(pull=pull)

        elif PLATFORM == MICROPYTHON:
            if pull == PULL_UP:
                return self.pin.init(machine.Pin.IN, machine.Pin.PULL_UP)
            elif pull == PULL_DOWN:
                return self.pin.init(machine.Pin.IN, machine.Pin.PULL_DOWN)

            raise ValueError('only PULL_UP and PULL_DOWN supported on MicroPython')

        raise NotImplementedError('switch_to_input not supported on platform')

    def switch_to_output(self):
        if PLATFORM == CIRCUITPYTHON:
            return self.pin.switch_to_output()
        elif PLATFORM == MICROPYTHON:
            return self.pin.init(machine.Pin.OUT)

        raise NotImplementedError('switch_to_output not supported on platform')

    def value(self, value=None):
        if value is None:
            if self.call_value:
                return self.pin.value()
            return self.pin.value

        if self.call_value:
            return self.pin.value(value)
        self.pin.value = value
        return value


class PinLookup:
    def __getattr__(self, attr):
        return AbstractedDigitalPin(get_pin(attr))


Pin = PinLookup()