#!/usr/bin/env python3
# This alternative Reader.py script was meant to cover not only USB readers but more.
# It can be used to replace Reader.py if you have readers such as
# MFRC522, RDM6300 or PN532.
# Please use the github issue threads to share bugs and improvements
# or create pull requests.

import os.path
import sys
import serial
import string
import RPi.GPIO as GPIO
import logging

from evdev import InputDevice, categorize, ecodes, list_devices
# Workaround: when using RC522 reader with pirc522 pkg the py532lib pkg may not be installed and vice-versa
try:
    import pirc522
    from py532lib.i2c import *
    from py532lib.mifare import *
except ImportError:
    pass

logger = logging.getLogger(__name__)


def get_devices():
    devices = [InputDevice(fn) for fn in list_devices()]
    devices.append(NonUsbDevice('MFRC522'))
    devices.append(NonUsbDevice('RDM6300'))
    devices.append(NonUsbDevice('PN532'))
    return devices


class NonUsbDevice(object):
    name = None

    def __init__(self, name):
        self.name = name


class UsbReader(object):
    def __init__(self, device):
        self.keys = "X^1234567890XXXXqwertzuiopXXXXasdfghjklXXXXXyxcvbnmXXXXXXXXXXXXXXXXXXXXXXX"
        self.dev = device

    def readCard(self):
        from select import select
        stri = ''
        key = ''
        while key != 'KEY_ENTER':
            select([self.dev], [], [])
            for event in self.dev.read():
                if event.type == 1 and event.value == 1:
                    stri += self.keys[event.code]
                    key = ecodes.KEY[event.code]
        return stri[:-1]


class Mfrc522Reader(object):
    def __init__(self):
        self.device = pirc522.RFID()

    def readCard(self):
        # Scan for cards
        self.device.wait_for_tag()
        (error, tag_type) = self.device.request()

        if not error:
            logger.info("Card detected.")
            # Perform anti-collision detection to find card uid
            (error, uid) = self.device.anticoll()
            if not error:
                card_id = ''.join((str(x) for x in uid))
                logger.info(card_id)
                return card_id
        logger.debug("No Device ID found.")
        return None

    @staticmethod
    def cleanup():
        GPIO.cleanup()


class Rdm6300Reader():
    def __init__(self):
        device = '/dev/ttyS0'
        baudrate = 9600
        ser_timeout = 0.1
        self.last_card_id = ''
        try:
            self.rfid_serial = serial.Serial(device, baudrate, timeout=ser_timeout)
        except serial.SerialException as e:
            logger.error(e)
            exit(1)

    def readCard(self):
        byte_card_id = b''

        try:
            while True:
                try:
                    read_byte = self.rfid_serial.read()

                    if read_byte == b'\x02':    # start byte
                        while read_byte != b'\x03':     # end bye
                            read_byte = self.rfid_serial.read()
                            byte_card_id += read_byte

                        card_id = byte_card_id.decode('utf-8')
                        byte_card_id = ''
                        card_id = ''.join(x for x in card_id if x in string.printable)

                        # Only return UUIDs with correct length
                        if len(card_id) == 12:
                            self.last_card_id = card_id
                            self.rfid_serial.reset_input_buffer()
                            return self.last_card_id

                        else:   # wrong UUID length or already send that UUID last time
                            self.rfid_serial.reset_input_buffer()
                            return None

                except ValueError as ve:
                    logger.error(ve)

        except serial.SerialException as se:
            logger.error(se)

    def cleanup(self):
        self.rfid_serial.close()


class Pn532Reader:
    def __init__(self):
        pn532 = Pn532_i2c()
        self.device = Mifare()
        self.device.SAMconfigure()
        self.device.set_max_retries(MIFARE_WAIT_FOR_ENTRY)

    def readCard(self):
        return str(+int('0x' + self.device.scan_field().hex(), 0))

    def cleanup(self):
        # Not sure if something needs to be done here.
        logger.debug("PN532Reader clean up.")


class Reader(object):
    def __init__(self):
        path = os.path.dirname(os.path.realpath(__file__))
        if not os.path.isfile(path + '/deviceName.txt'):
            sys.exit('Please run RegisterDevice.py first')
        else:
            with open(path + '/deviceName.txt', 'r') as f:
                device_name = f.read()

            if device_name == 'MFRC522':
                self.reader = Mfrc522Reader()
            elif device_name == 'RDM6300':
                self.reader = Rdm6300Reader()
            elif device_name == 'PN532':
                self.reader = Pn532Reader()
            else:
                try:
                    device = [device for device in get_devices() if device.name == device_name][0]
                    self.reader = UsbReader(device)
                except IndexError:
                    sys.exit('Could not find the device %s.\n Make sure it is connected' % device_name)