# pushbutton.py

# Copyright (c) 2018-2020 Peter Hinch
# Released under the MIT License (MIT) - see LICENSE file

import uasyncio as asyncio
import utime as time
from . import launch
from primitives.delay_ms import Delay_ms


# An alternative Pushbutton solution with lower RAM use is available here
# https://github.com/kevinkk525/pysmartnode/blob/dev/pysmartnode/utils/abutton.py
class Pushbutton:
    debounce_ms = 50
    long_press_ms = 1000
    double_click_ms = 400
    def __init__(self, pin, suppress=False, sense=None):
        self.pin = pin # Initialise for input
        self._supp = suppress
        self._dblpend = False  # Doubleclick waiting for 2nd click
        self._dblran = False  # Doubleclick executed user function
        self._tf = False
        self._ff = False
        self._df = False
        self._lf = False
        self._ld = False  # Delay_ms instance for long press
        self._dd = False  # Ditto for doubleclick
        self.sense = pin.value() if sense is None else sense  # Convert from electrical to logical value
        self.state = self.rawstate()  # Initial state
        asyncio.create_task(self.buttoncheck())  # Thread runs forever

    def press_func(self, func, args=()):
        self._tf = func
        self._ta = args

    def release_func(self, func, args=()):
        self._ff = func
        self._fa = args

    def double_func(self, func, args=()):
        self._df = func
        self._da = args

    def long_func(self, func, args=()):
        self._lf = func
        self._la = args

    # Current non-debounced logical button state: True == pressed
    def rawstate(self):
        return bool(self.pin.value() ^ self.sense)

    # Current debounced state of button (True == pressed)
    def __call__(self):
        return self.state

    def _ddto(self):  # Doubleclick timeout: no doubleclick occurred
        self._dblpend = False
        if self._supp and not self.state:
            if not self._ld or (self._ld and not self._ld()):
                launch(self._ff, self._fa)

    async def buttoncheck(self):
        if self._lf:  # Instantiate timers if funcs exist
            self._ld = Delay_ms(self._lf, self._la)
        if self._df:
            self._dd = Delay_ms(self._ddto)
        while True:
            state = self.rawstate()
            # State has changed: act on it now.
            if state != self.state:
                self.state = state
                if state:  # Button pressed: launch pressed func
                    if self._tf:
                        launch(self._tf, self._ta)
                    if self._lf:  # There's a long func: start long press delay
                        self._ld.trigger(Pushbutton.long_press_ms)
                    if self._df:
                        if self._dd():  # Second click: timer running
                            self._dd.stop()
                            self._dblpend = False
                            self._dblran = True  # Prevent suppressed launch on release
                            launch(self._df, self._da)
                        else:
                            # First click: start doubleclick timer
                            self._dd.trigger(Pushbutton.double_click_ms)
                            self._dblpend = True  # Prevent suppressed launch on release
                else:  # Button release. Is there a release func?
                    if self._ff:
                        if self._supp:
                            d = self._ld 
                            # If long delay exists, is running and doubleclick status is OK
                            if not self._dblpend and not self._dblran:
                                if (d and d()) or not d:
                                    launch(self._ff, self._fa)
                        else:
                            launch(self._ff, self._fa)
                    if self._ld:
                        self._ld.stop()  # Avoid interpreting a second click as a long push
                    self._dblran = False
            # Ignore state changes until switch has settled
            await asyncio.sleep_ms(Pushbutton.debounce_ms)
