import machine 
import micropython
import time

PRESS = const(0x01)
RELEASE = const(0x02)
LONGPRESS = const(0x04)
DOUBLEPRESS = const(0x08)
MULTIPRESS = const(0x10)
PRESSWAIT = const(0x20)

state_list = [PRESS, RELEASE, LONGPRESS, DOUBLEPRESS]
class BtnChild:
    def __init__(self, pin, dbtime=20):
        self.pin = machine.Pin(pin, mode=machine.Pin.IN, pull=machine.Pin.PULL_UP)
        self._event = 0
        self._eventLast = 0
        self._valueLast = 1
        self._pressTime = 0
        self._releaseTime = 0
        self._doubleTime = 220
        self._dbtime = dbtime
        self._holdTime = 1000.0
        self._cbState = PRESS | RELEASE
        self._eventTime = {PRESS:0, RELEASE:0, LONGPRESS:0, DOUBLEPRESS:0}
        self.cb = {PRESS: None, RELEASE:None, LONGPRESS:None, DOUBLEPRESS:None}

    def wasDoublePress(self, callback=None):
        self._cbState |= DOUBLEPRESS
        if callback:
            self.cb[DOUBLEPRESS] = callback
        elif self._event & DOUBLEPRESS:
            self._event -= DOUBLEPRESS
            return True
        else:
            return False

    def pressFor(self, holdTime=1.0, callback=None):
        self._holdTime = holdTime * 1000
        self._cbState |= LONGPRESS
        if callback:
            self.cb[LONGPRESS] = callback
        elif self._event & LONGPRESS:
            self._event -= LONGPRESS
            return True
        else:
            return False

    def wasReleased(self, callback=None):
        if callback:
            self.cb[RELEASE] = callback
        elif self._event & RELEASE:
            self._event -= RELEASE
            return True
        else:
            return False        

    def wasPressed(self, callback=None):
        if callback:
            self.cb[PRESS] = callback
        elif self._event & PRESS:
            self._event -= PRESS
            return True
        else:
            return False

    def isPressed(self):
        return not self.pin.value()

    def isReleased(self):
        return self.pin.value()        

    def restart(self):
        self._cbState = PRESS | RELEASE
        self.cb = {PRESS: None, RELEASE:None, LONGPRESS:None, DOUBLEPRESS:None}
        self._eventTime = {PRESS:0, RELEASE:0, LONGPRESS:0, DOUBLEPRESS:0}
        self._event = 0
        self._pressTime = 0
        self._releaseTime = 0
        self._valueLast = 1

    def deinit(self):
        pass
    
    def upDate(self):
        value = self.pin.value()
        state = self._eventLast ^ self._event
        if state:
            for i in state_list:
                if state & i:
                    if self._event & i:
                        self._eventTime[i] = 40
                    else:
                        self._eventTime[i] = 0
            self._eventLast = self._event

        for i in state_list:
            if self._eventTime[i] > 1:
                self._eventTime[i] -= 1
            elif self._eventTime[i] == 1:
                self._eventTime[i] = 0
                self._event &= ~i

        if value ^ self._valueLast:
            nowTime = time.ticks_ms()
            self._valueLast = value
            if not value:
                if nowTime - self._pressTime > self._dbtime:
                    if self._cbState & DOUBLEPRESS and nowTime - self._pressTime < self._doubleTime:
                        self._event |= DOUBLEPRESS
                    self._event |= PRESSWAIT
                self._pressTime = nowTime
            else:
                if nowTime - self._releaseTime> self._dbtime:
                    if self._cbState & LONGPRESS and nowTime - self._pressTime > self._holdTime:
                        self._event |= LONGPRESS
                    else:
                        self._event |= RELEASE
                self._releaseTime = nowTime    

class Btn:
    def __init__(self, multiTime=50):
        self.timer = machine.Timer(1)
        self.timer.init(period=10, mode=self.timer.PERIODIC, callback=self.timerCb)
        self.btn = []
        self.multiList = []
        self._multiTime = multiTime

    def attach(self, pin):
        self.btn.append(BtnChild(pin))
        return self.btn[-1]
    
    def detach(self, btnIn):
        if btnIn in self.btn:
            self.btn.remove(btnIn)

    def restart(self):
        self.multiList = []
        self.btn = self.btn[:3]
        for i in self.btn:
            i.restart() 
    
    def multiBtnCb(self, btn1, btn2, callback=None):
        self.multiList.append([btn1, btn2, callback])
        btn1._cbState |= MULTIPRESS
        btn2._cbState |= MULTIPRESS

    def timerCb(self, arg):
        nowTime = time.ticks_ms()
        for i in self.btn:
            i.upDate()

        for i in self.multiList:
            if i[0]._event & PRESSWAIT and i[1]._event & PRESSWAIT:
                if abs(i[0]._pressTime - i[1]._pressTime) < self._multiTime:
                    i[0]._event &= ~PRESSWAIT
                    i[1]._event &= ~PRESSWAIT
                    i[2]()
        
        for i in self.btn:
            if i._event & DOUBLEPRESS:
                if i.cb[DOUBLEPRESS]:
                    i.cb[DOUBLEPRESS]()
                    i._event &= ~PRESSWAIT
                    i._event &= ~DOUBLEPRESS                    
            
            if i._event & PRESSWAIT:
                if i._cbState & DOUBLEPRESS:
                    if nowTime - i._pressTime > i._doubleTime:
                        i._event &= ~PRESSWAIT
                        if i.cb[PRESS]:
                            i.cb[PRESS]()         
                        else:
                            i._event |= PRESS
                            i._eventTime[0] = 40

                elif i._cbState & MULTIPRESS:
                    if nowTime - i._pressTime > self._multiTime:
                        i._event &= ~PRESSWAIT
                        if i.cb[PRESS]:
                            i.cb[PRESS]()
                        else:
                            i._event |= PRESS

                else:
                    i._event &= ~PRESSWAIT
                    if i.cb[PRESS]:
                        i.cb[PRESS]()
                    else:
                        i._event |= PRESS

            if i._event & LONGPRESS:
                if i.cb[LONGPRESS]:
                    i._event &= ~LONGPRESS
                    i.cb[LONGPRESS]()

            if i._event & RELEASE:
                if i.cb[RELEASE]:
                    i._event &= ~RELEASE
                    i.cb[RELEASE]()

    def deinit(self):
        pass