#!/opt/kugelstossmeeting-ticketing/venv/bin/python3
'''
TODOS
 - UI aufhübschen
 - Suchfunktion einbauen
 - usbscanner.py journal log automatisch öffnen per Button (soll separate Shell öffnen)
'''

import sqlite3
from sqlite3 import Error
from contextlib import closing
import os
import time
from datetime import datetime
import pytz
import tkinter as tk
#from tkinter import *
from tkinter import ttk, messagebox
import tkinter.font as font


BASE_DIR = os.path.dirname(os.path.abspath(__file__))
db_file = os.path.join(BASE_DIR, "kugelstossmeeting-prod.db")
if os.path.isfile(db_file) is False:
    error = "Error: Datenbankdatei '{}' konnte nicht gefunden werden!".format(db_file)
    print(error)
    messagebox.showerror("Error", error)
    exit(1)
if os.path.getsize(db_file) == 0:
    error = "Error: Datenbankdatei '{}' ist 0 Byte groß!".format(db_file)
    print(error)
    messagebox.showerror("Error", error)
    exit(1)
    
title = "Kugelstoßmeeting Rochlitz"

timeoffset = 20 #seconds
autoscroll = True
lastSelectedTicket = None #das letzte manuell ausgewählte. Müssen wir merken, sonst wird es alle 2 Sekunden deselektiert

window = tk.Tk()
#window.geometry('800x600+0+0')
window.attributes('-zoomed', True)
window.title(title)
window.iconphoto(False, tk.PhotoImage(file=os.path.join(BASE_DIR, "icon.png")))

currentScan = None

def treeview_sort_column(tv, col, reverse):
    l = [(tv.set(k, col), k) for k in tv.get_children('')]
    l.sort(key=lambda t: int(t[0]), reverse=reverse)
    for index, (val, k) in enumerate(l):
        tv.move(k, '', index)
    tv.heading(col, command=lambda: treeview_sort_column(tv, col, not reverse))


def insertManualSelectionAsScan(mb):
    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:
            ts = time.time_ns()
            res = cur.execute(sql,(int(ts),mb,0,"Manuelle Auswahl",None,0))
            print(res)
            conn.commit()


def getLastScan():
    #sql = "SELECT * FROM scans WHERE skipped = 0 AND validated = 0 ORDER BY unixtimestamp_created DESC LIMIT 1;"
    sql = "SELECT * FROM scans WHERE (julianday('now') - julianday(CAST(unixtimestamp_created AS float) / 1e9,'unixepoch'))*24*60*60 < ? ORDER BY unixtimestamp_created DESC LIMIT 1;"
    #sql = "SELECT * FROM scans ORDER BY unixtimestamp_created DESC LIMIT 1;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql, (timeoffset, ))
            #res = cur.execute(sql)
            rows = res.fetchall()
    if len(rows) > 0:
        return rows[-1]
    else:
        return []

def getValidatedCount():
    sql = "SELECT COUNT(*) FROM scans WHERE validated = 1 and skipped = 0;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql)
            rows = res.fetchall()
    validated=rows[-1][0]
    sql = "SELECT COUNT(*) FROM tickets;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql)
            rows = res.fetchall()
    total=rows[-1][0]
    return "{}/{} eingecheckt".format(validated, total)
    
#Anzahl der Barcodes, die mehr als 1x eingescannt wurden (nur für Statistik)
def getScannedDuplicates():
    sql = 'SELECT COUNT(*) FROM (SELECT COUNT(*) as "ct",* FROM scans GROUP BY barcode) WHERE ct > 1;'
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql)
            rows = res.fetchall()
    total=rows[-1][0]
    return "{} Barcodes mehr als 1x gescannt".format(total)

#Prüfen, ob Barcode in der Ticketliste existiert oder nicht
def exists(barcode):
    sql = "SELECT * FROM tickets WHERE BarcodeContent = ? LIMIT 1;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql, (barcode, ))
            rows = res.fetchall()
        return True if len(rows) > 0 else False

def isAlreadyCheckedIn(barcode):
    sql = "SELECT * FROM scans WHERE validated = 1 AND barcode = ? ORDER BY unixtimestamp_checked LIMIT 1;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql, (barcode, ))
            rows = res.fetchall()
        return True if len(rows) > 0 else False
    
def getScans(tv):
    #sql = "SELECT * FROM scans WHERE skipped = 0 AND validated = 0;" # Scans, die nicht übersprungen und noch nicht validiert wurden
    sql = "SELECT * FROM scans;" # alle Scans
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql)
            rows = res.fetchall()
    for i in tv.get_children():
        tv.delete(i)
    k=1
    for i in rows:
        unixtimestamp_created_c = datetime.fromtimestamp(int(str(i[0])[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S')
        try:
            unixtimestamp_checked_c = datetime.fromtimestamp(int(str(i[4])[0:10]), pytz.timezone("Europe/Berlin")).strftime('%d.%m.%Y %H:%M:%S')
        except:
            unixtimestamp_checked_c = ""
        barcode = i[1]
        validated = "Ja" if i[2] == 1 else "Nein"
        hint = "" if i[3] == None else i[3]
        #unixtimestamp_skipped = i[4]
        skipped = "Ja" if i[5] == 1 else "Nein"
        unixtimestamp_created = i[0]
        if k % 2 == 0:
            evenodd = "evenrow"
        else:
            evenodd = "oddrow"
        tv.insert('', 1, text=k, values=(unixtimestamp_created_c, barcode, validated, hint, unixtimestamp_checked_c, skipped, unixtimestamp_created), tags=(evenodd, ))
        k+=1
    treeview_sort_column(tv, 6, False) #wir sortieren nach unixtimestamp_created, weil unixtimestamp_created_c einen Fehler bringt
    global autoscroll
    if autoscroll is True:
        if len(tv.get_children()) > 0:
            lastItem = tv.get_children()[-1]
            tv.focus(lastItem)
            tv.selection_set(lastItem)
            tv.yview_moveto(1)

#Tickets
def getTickets(tv):
    global lastSelectedTicket
    lastSelectedTicket = tv.index(tv.focus())
    
    #sql = "SELECT * FROM tickets;"
    sql = "SELECT DISTINCT tickets.*, CASE WHEN RES.validated = 1 THEN 'Ja' ELSE 'Nein' END FROM tickets FULL JOIN (SELECT * FROM scans WHERE validated = 1 and skipped = 0) AS RES ON RES.barcode = tickets.BarcodeContent;"
    with closing(sqlite3.connect(db_file)) as conn:
        with closing(conn.cursor()) as cur:
            res = cur.execute(sql)
            rows = res.fetchall()
    for i in tv.get_children():
        tv.delete(i)
    k=1
    for i in rows:
        if k % 2 == 0:
            evenodd = "evenrow"
        else:
            evenodd = "oddrow"
        tv.insert('', 1, text=k, values=(i), tags=(evenodd, ))
        k+=1
    treeview_sort_column(tv, 1, False)
    if len(tvScans.get_children()) > 0:
        global autoscroll
        if autoscroll is True:      
                Barcode = tvScans.item(tvScans.focus())['values'][1] #get row von Scan, wo Barcode dem Barcode in Ticket entspr
                col=0 #Barcode-Spalte
                l = [(tvTickets.set(k, col), k) for k in tvTickets.get_children('')]
                for index, (val, k) in enumerate(l):
                    if val == Barcode:
                        tv.focus(tv.get_children()[index])
                        tv.selection_set(tv.get_children()[index])
                        tv.yview_moveto(0)
                        tv.yview_scroll(index, "units")
        else:
            tv.focus(tv.get_children()[lastSelectedTicket])
            tv.selection_set(tv.get_children()[lastSelectedTicket])

def validate():
    if len(currentScan) > 0:
        sql = "UPDATE scans set unixtimestamp_checked = ?, hint = ?, validated = 1 WHERE unixtimestamp_created = ? AND barcode = ?  AND validated = 0 AND skipped = 0;"
        with closing(sqlite3.connect(db_file)) as conn:
            with closing(conn.cursor()) as cur:
                ts = time.time_ns()
                res = cur.execute(sql,(int(ts), hintTextInput.get(1.0, "end-1c"), currentScan[0],currentScan[1]))
                print(res)
                conn.commit()
        hintTextInput.delete(1.0,tk.END) #reset hintTextInput

def skip():
    if len(currentScan) > 0:
        sql = "UPDATE scans set unixtimestamp_checked = ?, hint = ?, skipped = 1 WHERE unixtimestamp_created = ? AND barcode = ? AND validated = 0;"
        with closing(sqlite3.connect(db_file)) as conn:
            with closing(conn.cursor()) as cur:
                ts = time.time_ns()
                res = cur.execute(sql,(int(ts), hintTextInput.get(1.0, "end-1c"), currentScan[0],currentScan[1]))
                print(res)
                conn.commit()
        hintTextInput.delete(1.0,tk.END) #reset hintTextInput

toggleScrollbarButton_t1 = "Umschalten zu: Ticket manuell auswählen"
toggleScrollbarButton_t2 = "Umschalten zu: Ticket automatisch per Scan auswählen (Standard)"

def manual():
    if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t2:
        global lastSelectedTicket
        mBarcode = None
        if len(tvScans.get_children()) > 0:
            item = tvTickets.item(tvTickets.focus())
            row = 0
            
            for (val, k) in enumerate(item.values()):
                row += 1
                if row == 3:
                    mBarcode = k[0]
            print(mBarcode)
            insertManualSelectionAsScan(mBarcode) #insert a new "lastScan"

def ScrollToggle():
    global autoscroll
    if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t1:
        toggleScrollbarButton.config(text=toggleScrollbarButton_t2)
        autoscroll = False
        tvTickets['selectmode'] = "browse"
    else:
        toggleScrollbarButton.config(text=toggleScrollbarButton_t1)
        autoscroll = True
        tvTickets['selectmode'] = "none"

toggleScrollbarButton = tk.Button(text=toggleScrollbarButton_t1, width=70, relief="raised", bg='pink', command=ScrollToggle)
toggleScrollbarButton.pack(padx=10, pady=5)

f1=tk.Frame(window)
f1.pack(expand=1)

tk.Label(f1, text="Die Anzeige des aktuellen Barcodes setzt sich automatisch nach {} Sekunden zurück".format(timeoffset), padx=10, pady=5).pack()

manualButton = tk.Button(f1, text="Manuelle Auswahl", command=manual)
manualButton.pack(side=tk.LEFT)
manualButton['font']=font.Font(size=35)

barcodeLabel = tk.Label(f1, text="<>", bg='#000000', fg='#00ff00', padx=10, pady=5)
barcodeLabel.config(font=(None, 35))
barcodeLabel.pack(side=tk.LEFT)

checkinButton = tk.Button(f1, text="Einchecken/entwerten", bg='red', fg='white', command=validate)
checkinButton.pack(side=tk.RIGHT)
checkinButton['font']=font.Font(size=35)
skipButton = tk.Button(f1, text="Überspringen", bg='red', fg='white', command=skip)
skipButton.pack(side=tk.RIGHT)
skipButton['font']=font.Font(size=35)

commentLabel = tk.Label(window, text="Kommentar", padx=10, pady=5)
commentLabel.config(font=(None, 15))
commentLabel.pack()

hintTextInput = tk.Text(window, height = 3, width = 100)
hintTextInput.pack()
totalValidatedLabel = tk.Label(window, text="/",  fg='#008000', padx=10, pady=5, width=100)
totalValidatedLabel.config(font=(None, 35))
totalValidatedLabel.pack()

tabControl = ttk.Notebook(window)
tabScans = ttk.Frame(tabControl)
tabTickets = ttk.Frame(tabControl)

tabControl.add(tabTickets, text='Alle Tickets')
tabControl.add(tabScans, text='Alle Scans')
tabControl.pack(fill='both')

tk.Label(tabScans, text="Scans").pack()
totalDuplicateCodeScansLabel = tk.Label(tabScans, text="/", padx=10, pady=5)
totalDuplicateCodeScansLabel.pack()
tk.Label(tabTickets, text="Tickets").pack()

def updateBarcodeLabel(label, scan):
    if len(scan) == 0:
        labeltext="Bitte scannen!"
        label.config(fg='#0000ff')
    else:
        barcode = scan[1]
        existing = exists(barcode)
        alreadyCheckedIn = isAlreadyCheckedIn(barcode)
        if existing is True:
            if alreadyCheckedIn is False:
                if toggleScrollbarButton.config('text')[-1] == toggleScrollbarButton_t2:
                    ScrollToggle() #toggle zurück zu Standardmodus, wenn ein normaler Scan gekommen ist
                labeltext="{0} ready!".format(barcode)
                label.config(fg='#00ff00')
                skip = scan[4]
                if skip is not None:
                    labeltext="{0} übersprungen!".format(barcode)
                    label.config(fg='#ffffff')
            else:
                labeltext="{0} entwertet!".format(barcode)
                label.config(fg='#ff0000')
        else:
                labeltext="{0} nicht gefunden!".format(barcode)
                label.config(fg='#7f00ff')
    label.config(text=labeltext)

tvScans = ttk.Treeview(tabScans, height=50, selectmode="browse")
tvScans["columns"] = ("c1", "c2", "c3", "c4", "c5", "c6", "c7") #eine Spalte mehr (unixtimestamp_created, welche aber ausgeblendet ist und nur zum Sortieren genutzt wird)
tvScans["displaycolumns"]=("c1", "c2", "c3", "c6", "c4", "c5")
tvScans.column("c1", width=100)
tvScans.column("c2", anchor='center', width=100)
tvScans.column("c3", anchor='center', width=100)
tvScans.column("c4", anchor='center', width=100)
tvScans.column("c5", anchor='center', width=100)
tvScans.column("c6", anchor='center', width=100)
tvScans.heading("#0", text="Nr.")
tvScans.heading("c1", text="TS Gescannt", anchor='w')
tvScans.heading("c2", text="Barcode")
tvScans.heading("c3", text="Entwertet")
tvScans.heading("c4", text="Anmerkung/Hinweise")
tvScans.heading("c5", text="TS Bearbeitet")
tvScans.heading("c6", text="Übersprungen")
tvScans.tag_configure('oddrow', background='white', font=("monospace", 10))
tvScans.tag_configure('evenrow', background='lightblue', font=("monospace", 10))
tvScansBar = ttk.Scrollbar(tabScans, orient="vertical", command=tvScans.yview)
tvScansBar.pack(side='right', fill='x')
tvScans.configure(yscrollcommand = tvScansBar.set)
tvScans.pack(fill='both')

tvTickets = ttk.Treeview(tabTickets, height=50, selectmode="none")
tvTickets["columns"] = ("c1", "c2", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11")
tvTickets["displaycolumns"]=("c1", "c3", "c4", "c5", "c6", "c7", "c8", "c9", "c10", "c11")
tvTickets.column("c1", width=100)
tvTickets.column("c2", anchor='center', width=100)
tvTickets.column("c3", anchor='center', width=100)
tvTickets.column("c4", anchor='center', width=100)
tvTickets.column("c5", anchor='center', width=100)
tvTickets.column("c6", anchor='center', width=100)
tvTickets.column("c7", anchor='center', width=100)
tvTickets.column("c8", anchor='center', width=100)
tvTickets.column("c9", anchor='center', width=100)
tvTickets.column("c10", anchor='center', width=100)
tvTickets.column("c11", anchor='center', width=100)
tvTickets.heading("#0", text="Nr.")
tvTickets.heading("c1", text="Barcode", anchor='w')
tvTickets.heading("c2", text="Nr")
tvTickets.heading("c3", text="VIP")
tvTickets.heading("c4", text="Medium")
tvTickets.heading("c5", text="Aushändigung")
tvTickets.heading("c6", text="Bestellnummer/Verwendungszweck")
tvTickets.heading("c7", text="Bezahlt am")
tvTickets.heading("c8", text="Hinweise")
tvTickets.heading("c9", text="Besteller")
tvTickets.heading("c10", text="Typ")
tvTickets.heading("c11", text="Entwertet")
tvTickets.tag_configure('oddrow', background='white', font=("monospace", 10))
tvTickets.tag_configure('evenrow', background='lightblue', font=("monospace", 10))
#tvTicketsBar = ttk.Scrollbar(tvTickets, orient="vertical", command=tvTickets.yview)
#tvTicketsBar.pack(side='right', fill='x')
#tvTickets.configure(yscrollcommand = tvTicketsBar.set)
tvTickets.pack(fill='both')

def update():
    getScans(tvScans)
    getTickets(tvTickets)
    global currentScan
    currentScan=getLastScan() #bekomme den letzten, unbearbeiteten Scan
    updateBarcodeLabel(barcodeLabel, currentScan)
    totalValidatedLabel.config(text=getValidatedCount())
    totalDuplicateCodeScansLabel.config(text=getScannedDuplicates())
    window.after(2000, update)

update() # run first time
window.mainloop()
