import time
import Adafruit_BBIO.GPIO as GPIO
import Adafruit_BBIO.PWM as PWM

class Keypad():
    def __init__(self):
        # CONSTANTS 
        self.KEYPAD = [
            ["1","2","3"],
            ["4","5","6"],
            ["7","8","9"],
            ["*","0","#"]
        ]

        self.ROW         = ["P8_8", "P8_10", "P8_12", "P8_14"]
        self.COLUMN      = ["P8_16", "P8_17", "P8_18"]
        self.PASS = "1234"
     
    def getKey(self):
        # Set all columns as output low
        for i in range(len(self.COLUMN)):
            GPIO.setup(self.COLUMN[i], GPIO.OUT)
            GPIO.output(self.COLUMN[i], GPIO.LOW)
         
        # Set all rows as input
        for i in range(len(self.ROW)):
            GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP)
         
        # Scan rows for pushed key/button
        # A valid key press should set "rowVal"  between 0 and 3.
        rowVal = -1
        for i in range(len(self.ROW)):
            tmpRead = GPIO.input(self.ROW[i])
            if tmpRead == 0:
                rowVal = i
                 
        if rowVal <0 or rowVal >3:
            self.reset()
            return
         
        # Convert columns to input
        for j in range(len(self.COLUMN)):
            GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_UP)
         
        # Switch the i-th row found from scan to output
        GPIO.setup(self.ROW[rowVal], GPIO.OUT)
        GPIO.output(self.ROW[rowVal], GPIO.LOW)
 
        # Scan columns for still-pushed key/button
        # A valid key press should set "colVal"  between 0 and 2.
        colVal = -1
        for j in range(len(self.COLUMN)):
            tmpRead = GPIO.input(self.COLUMN[j])
            if tmpRead == 0:
                colVal=j
                 
        if colVal <0 or colVal >2:
            self.reset()
            return
 
        # Return the value of the key pressed
        self.reset()
        return self.KEYPAD[rowVal][colVal]
         
    def reset(self):
        # Reinitialize all rows and columns
        for i in range(len(self.ROW)):
            GPIO.setup(self.ROW[i], GPIO.IN, pull_up_down=GPIO.PUD_UP) 
        for j in range(len(self.COLUMN)):
            GPIO.setup(self.COLUMN[j], GPIO.IN, pull_up_down=GPIO.PUD_UP)
    
    def verify_pass(self, pass_entered):
        return pass_entered == self.PASS
        

class IR:
    _name = ""
    _pin = ""
    _mode = GPIO.IN
    _rot = None
    _laut = None
    _keypad = None
    def __init__(self, name, pin, rot, laut, keypad):
        self._name = name
        self._pin = pin
        self._rot = rot
        self._laut = laut
        self._keypad = keypad
        GPIO.setup(self._pin, self._mode)
        GPIO.add_event_detect(self._pin, GPIO.RISING, callback=self.cb_ir)
    
    def __del__(self):
        print("IR {} deleted.".format(self._name))
        GPIO.remove_event_detect(self._pin)

    def cb_ir(self, pin):
        print("cb_ir called. enter password to cancel alarm")
        pass_entered = ""
        digit = None
        for i in range(self._laut._duration * self._rot._blink_freq):
            self._laut.start()
            self._rot.start()
            time.sleep(1/self._rot._blink_freq)
            self._rot.stop()
            self._laut.stop()
            digit = self._keypad.getKey()
            if digit != None:
                pass_entered = pass_entered + digit
                print("{} digit(s) alreadt entered".format(len(pass_entered)))
                digit = None
                if len(pass_entered) == 4:
                    if self._keypad.verify_pass(pass_entered):
                        print("alarm cancelled")
                        self._rot.stop()
                        self._laut.stop()
                        return
                    else:
                        print("wrong password")
                        pass_entered = ""
                

class PIR:
    _name = ""
    _pin = ""
    _mode = GPIO.IN
    _rot = None
    _laut = None
    def __init__(self, name, pin, rot, laut):
        self._name = name
        self._pin = pin
        self._rot = rot
        self._laut = laut
        GPIO.setup(self._pin, self._mode)
        GPIO.add_event_detect(self._pin, GPIO.RISING, callback=self.cb_pir)
    
    def __del__(self):
        print("PIR {} deleted.".format(self._name))
        GPIO.remove_event_detect(self._pin)

    def cb_pir(self, pin):
        print("cb_pir called")
        for i in range(self._laut._duration * self._rot._blink_freq):
            self._laut.start()
            self._rot.start()
            time.sleep(1/self._rot._blink_freq)
            self._rot.stop()
            self._laut.stop()
        
class MAG:
    _name = ""
    _pin = ""
    _mode = GPIO.IN
    _window_closed = True
    _rot = None
    _laut = None
    def __init__(self, name, pin, rot, laut):
        self._name = name
        self._pin = pin
        self._rot = rot
        self._laut = laut
        GPIO.setup(self._pin, self._mode)
        GPIO.add_event_detect(self._pin, GPIO.BOTH, callback=self.cb_mag)
    
    def __del__(self):
        print("MAG {} deleted.".format(self._name))
        GPIO.remove_event_detect(self._pin)

    def cb_mag(self, pin):
        if self._window_closed:
            print("Fenster geoeffnet...")
            self._window_closed = False
            for i in range(self._laut._duration * self._rot._blink_freq):
                self._laut.start()
                self._rot.start()
                time.sleep(1/self._rot._blink_freq)
                self._rot.stop()
                self._laut.stop()
        else:
            print("Fenster zu...")
            self._window_closed = True

class ROT:
    _name = ""
    _pin = ""
    _mode = GPIO.OUT
    _blink_freq = 2
    def __init__(self, name, pin, blink_freq=2):
        self._name = name
        self._pin = pin
        self._blink_freq = blink_freq
        GPIO.setup(self._pin, self._mode)
    
    def __del__(self):
        print("ROT {} deleted.".format(self._name))

    def start(self):
        GPIO.output(self._pin, GPIO.HIGH)
    
    def stop(self):
        GPIO.output(self._pin, GPIO.LOW)

class LAUT:
    _name = ""
    _pin = ""
    _mode = GPIO.OUT
    _duration = 60 # in second
    _pwm_perc = 50 # in percentage
    _pwm_freq = 10000 # in hz
    def __init__(self, name, pin, duration=60, pwm_perc=50, pwm_freq=10000):
        self._name = name
        self._pin = pin
        self._duration = duration
        self._pwm_perc = pwm_perc
        self._pwn_freq = pwm_freq
        GPIO.setup(self._pin, self._mode)
    
    def __del__(self):
        print("PWM {} deleted.".format(self._name))

    def start(self):
        PWM.start(self._pin, self._pwm_perc, self._pwm_freq)
    
    def stop(self):
        PWM.stop(self._pin)

if __name__ == "__main__":
    try:
        print("Program start. Initializing with alarm config...")
        keypad = Keypad()
        rot = ROT("outPinROT", "P9_27")
        laut = LAUT("myPWMLAUT", "P8_13", duration=4)
        ir = IR("inPinIR", "P9_12", rot, laut, keypad)
        pir = PIR("inPinPIR", "P9_41", rot, laut)
        mag = MAG("inPinMAG", "P8_7", rot, laut)

        print("Initialization succeeded. Start in 2 seconds.")
        time.sleep(2)
        while True:
            # print("...")
            time.sleep(2)
    except Exception as e:
        print("Exception captured. Clean up and quit...")
        GPIO.cleanup()
        PWM.cleanup()
        print(e)
