'''
Hilfe: 
- Scanner wird nicht mehr erkannt? Abziehen vom USB-Slot für 1-2 Minuten hilft + Stoppen dieses Programms für die gleiche Zeit
- Scanner-Wert wird nicht übertragen? -> Knopf ca. 1 Sekunde gedrückt halten. Nicht bloß antippen!
'''
import usb.core
import usb.util
import time
from datetime import datetime
import pytz
from systemd import journal
import sqlite3
from sqlite3 import Error
from contextlib import closing
import os.path

idVendor=0x0581
idProduct=0x0115
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_file = os.path.join(BASE_DIR, "kugelstossmeeting-prod.db")

def printwrite(str): #write to log and to stdout
    journal.write(str)
    print(str)
    
def hid2ascii(lst):
    #array('B', [0, 0, 0, 0, 0, 0, 0, 0]) # nothing, ignore
    conv_table = {
        0:['', ''],
        4:['a', 'A'],
        5:['b', 'B'],
        6:['c', 'C'],
        7:['d', 'D'],
        8:['e', 'E'],
        9:['f', 'F'],
        10:['g', 'G'],
        11:['h', 'H'],
        12:['i', 'I'],
        13:['j', 'J'],
        14:['k', 'K'],
        15:['l', 'L'],
        16:['m', 'M'],
        17:['n', 'N'],
        18:['o', 'O'],
        19:['p', 'P'],
        20:['q', 'Q'],
        21:['r', 'R'],
        22:['s', 'S'],
        23:['t', 'T'],
        24:['u', 'U'],
        25:['v', 'V'],
        26:['w', 'W'],
        27:['x', 'X'],
        28:['z', 'Z'],
        29:['y', 'Y'],
        30:['1', '!'],
        31:['2', '@'],
        32:['3', '#'],
        33:['4', '$'],
        34:['5', '%'],
        35:['6', '^'],
        36:['7' ,'&'],
        37:['8', '*'],
        38:['9', '('],
        39:['0', ')'],
        40:['\n', '\n'],
        41:['\x1b', '\x1b'],
        42:['\b', '\b'],
        43:['\t', '\t'],
        44:[' ', ' '],
        45:['_', '_'],
        46:['=', '+'],
        47:['[', '{'],
        48:[']', '}'],
        49:['\\', '|'],
        50:['#', '~'],
        51:[';', ':'],
        52:["'", '"'],
        53:['`', '~'],
        54:[',', '<'],
        55:['.', '>'],
        56:['/', '?'],
        100:['\\', '|'],
        103:['=', '='],
        }

    line = ''
    char = ''
    shift = 0
    
    #print(lst)
    
    for byte in lst: #raw byte array
        if byte == 2:
            shift = 1
        if byte != 0:
            if byte not in conv_table and byte != 2:
                printwrite("Warning: byte {0} not in conversion table".format(byte))
                char = ''
                line += char
            elif byte != 40 and byte != 2: #skip newline character
                char = conv_table[byte][shift]
                line += char
                shift = 0 #reset shift
    return line

sleepTime = 5 #secs
def mainloop():
    while True:
        dev = usb.core.find(idVendor=idVendor, idProduct=idProduct)
        time.sleep(sleepTime) #in jedem Fall etwas warten
        if dev is None:
            printwrite("No USB Scanner. Restarting mainloop {0}".format(sleepTime))
            mainloop()
        else:
            try:
                detached = dev.is_kernel_driver_active(0)
                if detached is False:
                    printwrite("USB Scanner found! Device is already detached")
                if detached is True:
                    dev.detach_kernel_driver(0)
                    #journal.write("Detached USB device from kernel driver")
                    printwrite("USB Scanner found! Detached from kernel driver")
                    
                cfg = dev.get_active_configuration()
                intf = cfg[(0,0)]
                ep = usb.util.find_descriptor(intf, custom_match = lambda e: \
                    usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN)
                assert ep is not None, "Endpoint for USB device not found. Something is wrong."
                while cfg is not None:
                    print("... in the mainloop")
                    # Wait up to 1 seconds for data
                    data = ep.read(1000)
                    line = hid2ascii(data)
                    if len(line) == 5: #wir akzepieren nur Codes, die 5-stellig sind (so, wie sie auf dem Ticket sind)
                        #printwrite(line)
                        sql = "INSERT INTO scans(unixtimestamp_created,barcode,validated,hint,unixtimestamp_checked,skipped) VALUES(?,?,?,?,?,?)"
                        with closing(sqlite3.connect(db_file)) as conn:
                            with closing(conn.cursor()) as cur:
                                #standardmäßig validated=0 und leerer Hinweis. Das wird vom GUI befüllt!
                                ts = time.time_ns()
                                res = cur.execute(sql,(int(ts),line,0,None,None,0))
                                printwrite("{0}: Inserting {1} into database".format(
                                    datetime.fromtimestamp(int(str(ts)[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S'), 
                                    line))
                                conn.commit()
                    time.sleep(0.1)
            except KeyboardInterrupt:
                printwrite("Stopping program")
                break
            except usb.core.USBError as e:
                printwrite("Device disappeared. Restarting mainloop | Error: {}".format(e))
                #Error: [Errno None] Configuration not set
                #Error: [Errno 5] Input/Output Error
                #Error: [Errno 16] Resource busy
                #Error: [Errno 13] Access denied (insufficient permissions)
                #Error: [Errno 19] No such device (it may have been disconnected)
                #Error: [Errno 110] Operation timed out
                time.sleep(sleepTime)
                #continue
                mainloop()
mainloop()
