# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test.ui'
#
# Created by: PyQt5 UI code generator 5.15.9
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


########## beim Static workflow funktionieren die Threads ab dem zweiten nicht mehr

#from PyQt5 import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget, QProgressBar
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QComboBox
from PyQt5.QtCore import QTimer, QThread, pyqtSignal, QObject
from PyQt5.QtGui import QColor

import serial
import binascii
import math
import mariadb
import time
import threading

from CameraStream import YOLOv8CameraStream  # Import the YOLOv8CameraStream class
import cv2


db_config = {
    'user': 'dbUser',
    'password': 'dbPassword',
    'host': '127.0.0.1', # 'host': 'localhost',
    'database': 'projectGeislinger',
    'port': 3306  # Standard port for MariaDB
}

# Establishing the connection
conn = mariadb.connect(**db_config)
# Create a cursor to execute queries
cursor = conn.cursor()

# # Konfiguration der seriellen Schnittstelle
# ser = serial.Serial('/dev/ttyUSB0', 9600)

# Configuration of the serial port
try:
    ser = serial.Serial('/dev/serial/by-id/usb-Silicon_Labs_CP2102_USB_to_UART_Bridge_Controller_0001-if00-port0', 9600)
    print("Serial port connected successfully.")
except serial.SerialException:
    ser = None
    print("Warning: Serial port not found. Continuing without serial connection. Only working for demo-purposes.")

waageEingeschwungen = False


def wahrscheinlichkeitsDichte(x,mue, var):
    # in der Funktion wird der Wahrscheinlichkeitsdichtenwert der Variable x für eine bestimmte Normalverteilung berechnet
    standardabweichung = var**0.5

    result = 1/(standardabweichung * (2*math.pi)**0.5 ) * math.exp(-0.5 * ((x-mue)/standardabweichung)**2)

    return result

def calcWahrscheinlichkeitFromDichte(x,mue, var):
    # in der Funktion wird die Wahrscheinlichkeit via der Wahrscheinlichkeitsdichte berechnet, indem das Verhältnis aus der dem Bauteil zugehörigen Wahrscheinlichkeitsdichte zu der maximalen Wahrscheinlichkeitsdichte berechnet wird
    p1 = wahrscheinlichkeitsDichte(x,mue, var)
    p_max = wahrscheinlichkeitsDichte(mue,mue, var)

    return p1/p_max



#class Worker(QThread):
class Worker(QObject):
    #progress = pyqtSignal(int)
    objectDetectionStartSignal = pyqtSignal(str)
    objectDetectionFinishedSignal = pyqtSignal(int, object)
    
    checkWaageStartSignal = pyqtSignal(int, int, str)
    checkWaageUpdateSignal = pyqtSignal(int, int)
    checkWaageFinishedSignal = pyqtSignal(int)

    waageStoppedSignal = pyqtSignal(int)
    stopLoopSignal = pyqtSignal(bool)   #das Signal wird verwendet um direkt den stopLoop-Wert zu ändern (also kein Funktionsaufruf)

    stopLoop = False
    btTypeIsSet = False
    correctBtNr = False


    def __init__(self):
        super(Worker, self).__init__()
        stopLoop = False
                

    def getDataOfArticleType(self, allArticles, articleType):
    # die Funktion geht die Liste mit allen Artikeln durch und gibt jenen Eintrag, welcher mit dem "articleType" übereinstimmt zurück
        for i in allArticles:
            if i[1] == articleType:
                return i

        return -1

    def waageNichtEingeschwungenOutput(self):
        print("Die Waage ist noch nicht eingeschwungen - Ergebnisse sind dadurch noch fehlerhaft.")

    def readWaage(self):
        # in folgender Funktion wird die Waage ausgelesen

        #print("connection is open: ", ser.is_open)              #Debuggingausgabe
        #print("port to which it is connected: ", ser.portstr)   #Debuggingausgabe


        if ser.is_open == False:
            ser.open()

        
        #an die Waage den Befehl senden, dass sie ausgelesen werden soll
        ser.write(b'getWeight\n')
        #ser.write(b'tare\n')
        serialString = ser.readline().decode('utf-8').rstrip()  #Auslesen des Serial-Strings/der Messung der Waage
        
        # wenn am Ende des Strings kg steht, dann ist die Waage eingeschwungen - das wird hiermit überprüft
        lenString = len(serialString)-1
        if serialString[lenString] == "g" and serialString[lenString-1] == "k":
            #print("ist eingeschwungen")        #Debuggingausgabe
            waageEingeschwungen = True
        else:
            print("die Waage ist noch nicht eingeschwungen")
            waageEingeschwungen = False

        #aus dem String werden alle Zeichen, welche nicht zur Darstellung der Zahl benötigt werden entfernt
        intString = ""
        for i in serialString:
            if i=="-" or i=="0" or i=="." or i=="1" or i=="2" or i=="3" or i=="4" or i=="5" or i=="6" or i=="7" or i=="8" or i=="9":
                intString = intString + i
        print("Wert, welcher von der Waage ausgelesen wurde: " + intString + "kg")
        ## Waage auslesen - ENDE

        ser.close()
        #print("connection is open: ", ser.is_open)      #Debuggingausgabe

        return waageEingeschwungen, intString

    def objectTypeDetectionThread(self, auftragsnummer):
        # in dieser Funktion wird der Typ des Bauteils automatisch erkannt

        #print("objectTypeDetectionThread - Running in thread:", threading.current_thread().name)   #Debuggausgabe

        # Parameterdefinition
        propDensVect = []

        self.btTypeIsSet = False

        while(self.btTypeIsSet == False and self.stopLoop == False):  

            # Auslesen der Waage
            waageEingeschwungen, intString = self.readWaage()
            if waageEingeschwungen == False:
                self.waageNichtEingeschwungenOutput()
            else:

                # Datenbankabfrage
                sql_query = "SELECT Auftraege.id, EinzelteilID, Auftragsnummer, Anzahl, Einzelteile.id, Bezeichnung, CAST(projectGeislinger.Einzelteile.GewichtMittelwert AS CHAR), CAST(projectGeislinger.Einzelteile.GewichtVarianz AS CHAR) FROM projectGeislinger.Auftraege, projectGeislinger.Einzelteile where projectGeislinger.Auftraege.EinzelteilID = projectGeislinger.Einzelteile.id AND projectGeislinger.Auftraege.Auftragsnummer = " + auftragsnummer
                cursor.execute(sql_query)
                auftragEinzelteilDaten = cursor.fetchall()

                '''
                # Display data #Debugausgabe
                print("Ausgabe der Auftragsdetails des obigen Auftrags, inklusive Einzelteildetails:")
                for row in auftragEinzelteilDaten:
                    print(row)
                '''

                # in der Folge werden alle Wahrscheinlichkeitsdichten der Auftragsbauteile berechnet und in dem Vektor gesammelt 
                for row in auftragEinzelteilDaten:
                    propDensVect.append([wahrscheinlichkeitsDichte(float(intString),float(row[6]), float(row[7])), row[1], row[5]])

                # Jenen Eintrag des propDensVect raussuchen, welcher die größte Wahrscheinlichkeitsdichte beinhaltet
                maxpropDens = 0
                einzelteilID = 0
                rowData = None
                for row in propDensVect:
                    if row[0] > maxpropDens:
                        maxpropDens = row[0]
                        einzelteilID = row[1]
                        rowData = row

                # überprüfen, ob das aufliegende Bauteil in der Auftragstabelle vorhanden ist
                if(einzelteilID == 0):
                    print("Es wurde kein Bauteil aus der Auftragsliste (Tabelle) erkannt.")
                else:
                    print("Bei dem Bauteil" , einzelteilID , "wurde die höchste Wahrscheinlichkeitsdichte berechnet.")

                    # Wahrscheinlichkeit berechnen, dass das angegebene Bauteil auch wirklich diesem entspricht
                    prop = 0
                    for row in auftragEinzelteilDaten:
                        if row[1] == einzelteilID:
                            prop = calcWahrscheinlichkeitFromDichte(float(intString),float(row[6]), float(row[7]))
                            break
                    print("Die Wahrscheinlichkeit, dass es das Bauteil ist, beträgt: ", prop)

                    # den Bool auf true setzen, damit die Schleife beendet wird - dieser wird auf True gesetzt, wenn ein Bauteiltyp erkannt wird
                    self.btTypeIsSet = True

        if(self.stopLoop == False):
            # ein Signal zurück an den MainThreat senden, mit den Infos/Ergebnissen, der Typenbestimmung
            self.objectDetectionFinishedSignal.emit(einzelteilID, rowData)
        else:
            self.waageStoppedSignal.emit(einzelteilID)

    def checkWaageThread(self, einzelteilID, teileZuViel, auftragsnummer):
        
        self.correctBtNr = False
        prevAnzahl = 0
        anzahl = 0
        einzelteilID = einzelteilID

        while (self.correctBtNr == False and self.stopLoop == False):

            #überprüfen, ob die Waage eingeschwungen ist (und damit korrekte Ergebnisse liefert)
            waageEingeschwungen, intString = self.readWaage()
            if waageEingeschwungen == False:
                self.waageNichtEingeschwungenOutput()
            else: 
            
                # Datenbankabfrage - holen der Auftrags- und Bauteilinfos
                sql_query = "SELECT Auftraege.id, EinzelteilID, Auftragsnummer, Anzahl, Einzelteile.id, Bezeichnung, CAST(projectGeislinger.Einzelteile.GewichtMittelwert AS CHAR), CAST(projectGeislinger.Einzelteile.GewichtVarianz AS CHAR) FROM projectGeislinger.Auftraege, projectGeislinger.Einzelteile where projectGeislinger.Auftraege.EinzelteilID = projectGeislinger.Einzelteile.id AND projectGeislinger.Auftraege.Auftragsnummer = " + auftragsnummer
                cursor.execute(sql_query)
                auftragDaten = cursor.fetchall()

                '''
                # Display data - zum Debuggen
                print("Ausgabe der Auftragsdetails:")
                print("id|EinzelteilID|Auftragsnummer|Anzahl")
                for row in auftragDaten:
                    print(row)
                '''
                            
                #auslesen, wie viele Bauteile des Types laut Auftrag vorhanden sein sollen
                anzBauteile_soll = 0
                idVorhanden = False
                
                for row in auftragDaten:
                    if row[1] == einzelteilID:
                        anzBauteile_soll = row[3]
                        idVorhanden = True
                
                if(idVorhanden == False):
                    print("Die gewählte Pos.Nr ist nicht in der Auftragsliste (Tabelle) vorhanden.")
                    return

                # Berechnen der Wahrscheinlichkeitsdichten - der Betrag aller Wahrscheinlichkeitsdichten werden im propDensVect gespeichert
                propDensVect = []
                articleData = self.getDataOfArticleType(auftragDaten, einzelteilID)
                for i in range(1,anzBauteile_soll+teileZuViel+1):
                    # Mathematische Sätze, auf welchen die Berechnung basiert:
                    # seien X1,..., Xn unabhängige Zufallsvariablen die N(mue_i, sigma_i^2) verteilt sind, dann ist X = X1+...+Xn - N(mue, sigma^2) verteilt mit mue=mue1+...+mue_n, sigma^2 = sigma_1^2+...+sigma_n^2
                    mueGes = float(articleData[6])*i    # Berechnen des äquivalenten Mittelwert
                    varGes = float(articleData[7])*i    # Berechnen der äquivalenten Varianz
                    propDensVect.append([wahrscheinlichkeitsDichte(float(intString),mueGes, varGes), i])

                # durch den propDensVect iterieren und jenen Eintrag mit der höchsten Wahrscheinlichkeitsdichte raussuchen
                maxpropDens = 0
                for row in propDensVect:
                    if row[0] > maxpropDens:
                        maxpropDens = row[0]
                        anzahl = row[1]

                # damit nur etwas gemacht wird (zb ein Updatesignal an den Hauptthread zurücksenden), wenn sich die berechnete Anzahl ändert
                if prevAnzahl != anzahl:
                    prevAnzahl = anzahl

                    # Die Wahrscheinlichkeit berechnen, dass das obige Ergebnis auch dem Bauteil entspricht 
                    if anzahl > 0:
                        for row in auftragDaten:
                            if row[1] == einzelteilID:
                                prop = calcWahrscheinlichkeitFromDichte(float(intString),float(row[6])*anzahl, float(row[7])*anzahl)
                                break
                        print("Die Wahrscheinlichkeit, dass es das Bauteil ist, beträgt: ", prop)

                    # wenn genug Bauteile vorhanden sind, dann soll die Schleife beendet werden
                    if (anzahl == anzBauteile_soll):
                        self.correctBtNr = True

                    # Konsolenausgabe
                    if maxpropDens > 0:
                        print("Bei der Anzahl" , anzahl , "des Bauteiltypes", einzelteilID, ",wurde die höchste Wahrscheinlichkeitsdichte berechnet.")
                        self.checkWaageUpdateSignal.emit(einzelteilID, anzahl)
                        
                    else:
                        print("Von der ausgewählten Bauteiltype liegt die berechnete Wahrscheinlichkeit bei 0, dass zwischen 0 und", anzBauteile_soll+teileZuViel, "Bauteilen auf der Waage liegen.")
                        print("Falls die korrekte Anzhl an Bauteilen auf der Waage liegt, könnte der Fehler in einer falschen Kalibrierung der Waage liegen (TARE).")

        if(self.stopLoop == False):
            self.checkWaageFinishedSignal.emit(einzelteilID)
        else:
            self.waageStoppedSignal.emit(einzelteilID) 




class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        self.auftragsnummer = ""
        self.teileZuViel = 0    # Anzahl an Bauteilen, welche zu viel drinnen sein können - der Parameter kann selbst gesetzt werden #####
        self.correctBtNr = False
        self.btTypeIsSet = False
        

        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1090, 675)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.btn1 = QtWidgets.QPushButton(self.centralwidget)
        self.btn1.setGeometry(QtCore.QRect(700, 45, 111, 25))
        self.btn1.setObjectName("btn1")
        self.AuftragsdetailsTable = QtWidgets.QTableWidget(self.centralwidget)
        self.AuftragsdetailsTable.setGeometry(QtCore.QRect(10, 300, 661, 192))
        self.AuftragsdetailsTable.setObjectName("AuftragsdetailsTable")
        self.AuftragsdetailsTable.setColumnCount(7)
        self.AuftragsdetailsTable.setRowCount(0)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(0, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(1, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(2, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(3, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(5, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(4, item)
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setHorizontalHeaderItem(6, item)

        self.label = QtWidgets.QLabel(self.centralwidget)
        self.label.setGeometry(QtCore.QRect(500, 20, 121, 17))
        self.label.setObjectName("label")
        self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
        self.textEdit.setGeometry(QtCore.QRect(450, 40, 221, 31))
        self.textEdit.setObjectName("textEdit")
        self.bauteilTypBtn = QtWidgets.QPushButton(self.centralwidget)
        self.bauteilTypBtn.setGeometry(QtCore.QRect(700, 150, 161, 25))
        self.bauteilTypBtn.setObjectName("bauteilTypBtn")
        self.checkWaageBtn = QtWidgets.QPushButton(self.centralwidget)
        self.checkWaageBtn.setGeometry(QtCore.QRect(700, 460, 161, 25))
        self.checkWaageBtn.setObjectName("checkWaageBtn")
        self.stopLoopBtn = QtWidgets.QPushButton(self.centralwidget)
        self.stopLoopBtn.setGeometry(QtCore.QRect(700, 500, 161, 25))
        self.stopLoopBtn.setObjectName("stopLoopBtn")
        self.waageTareBtn = QtWidgets.QPushButton(self.centralwidget)
        self.waageTareBtn.setGeometry(QtCore.QRect(700, 100, 161, 25))
        self.waageTareBtn.setObjectName("waageTareBtn")
        self.bauteiltypTextbox = QtWidgets.QTextEdit(self.centralwidget)
        self.bauteiltypTextbox.setGeometry(QtCore.QRect(700, 290, 221, 31))
        self.bauteiltypTextbox.setObjectName("bauteiltypTextbox")
        self.BezeichnungLabel = QtWidgets.QLabel(self.centralwidget)
        self.BezeichnungLabel.setGeometry(QtCore.QRect(700, 270, 181, 20))
        self.BezeichnungLabel.setObjectName("BezeichnungLabel")
        self.PosNrLabel = QtWidgets.QLabel(self.centralwidget)
        self.PosNrLabel.setGeometry(QtCore.QRect(700, 200, 67, 17))
        self.PosNrLabel.setObjectName("PosNrLabel")
        self.PosNrTxtFeld = QtWidgets.QTextEdit(self.centralwidget)
        self.PosNrTxtFeld.setGeometry(QtCore.QRect(700, 220, 191, 31))
        self.BezeichnungLabel.setObjectName("BezeichnungLabel")
        self.teileZuVielLabel = QtWidgets.QLabel(self.centralwidget)
        self.teileZuVielLabel.setGeometry(QtCore.QRect(700, 350, 350, 17))
        self.teileZuVielLabel.setObjectName("teileZuVielLabel")
        self.teileZuVielTxtFeld = QtWidgets.QTextEdit(self.centralwidget)
        self.teileZuVielTxtFeld.setGeometry(QtCore.QRect(700, 370, 191, 31))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 1090, 22))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.checkBox = QtWidgets.QCheckBox(self.centralwidget)
        self.checkBox.setGeometry(QtCore.QRect(700, 10, 151, 23))
        self.checkBox.setObjectName("checkBox")

        # button and checkbox for camera workflow + graphicsview widget
        self.startCamBtn = QtWidgets.QPushButton(self.centralwidget)
        self.startCamBtn.setGeometry(QtCore.QRect(900, 45, 161, 25))
        self.startCamBtn.setObjectName("startCamBtn")
        self.stopCamBtn = QtWidgets.QPushButton(self.centralwidget)
        self.stopCamBtn.setGeometry(QtCore.QRect(900, 45+25, 161, 25))
        self.stopCamBtn.setObjectName("stopCamBtn")
        self.camWorkFlowcheckBox = QtWidgets.QCheckBox(self.centralwidget)
        self.camWorkFlowcheckBox.setGeometry(QtCore.QRect(900, 10, 151, 23))
        self.camWorkFlowcheckBox.setObjectName("camWorkFlowcheckBox")
        self.graphicsView = QtWidgets.QGraphicsView(self.centralwidget)
        self.graphicsView.setGeometry(QtCore.QRect(1100, 20, int(640*1.2), int(480*1.2))) # position and size of camera frame
        self.graphicsView.setObjectName("graphicsView")

        #self.myTestLambda = lambda: self.worker.checkWaageStartSignal.emit(einzelteilID, self.teileZuViel, self.auftragsnummer)
        
        '''
        item = QtWidgets.QTableWidgetItem()
        font = QtGui.QFont()
        font.setPointSize(8)
        item.setFont(font)
        self.AuftragsdetailsTable.setVerticalHeaderItem(0,item)
        '''
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.btn1.setText(_translate("MainWindow", "load Auftrag"))
        self.btn1.clicked.connect(self.auftragsBtnClicked)
        item = self.AuftragsdetailsTable.horizontalHeaderItem(0)
        item.setText(_translate("MainWindow", "Pos.-Nr"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(1)
        item.setText(_translate("MainWindow", "Matnr mit hoechster Version"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(2)
        item.setText(_translate("MainWindow", "Bezeichnung + Werkstoff"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(3)
        item.setText(_translate("MainWindow", "Soll-Menge"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(4)
        item.setText(_translate("MainWindow", "Ist-Menge"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(5)
        item.setText(_translate("MainWindow", "Lgort"))
        item = self.AuftragsdetailsTable.horizontalHeaderItem(6)
        item.setText(_translate("MainWindow", "set Farbe"))

        self.label.setText(_translate("MainWindow", "Auftragsnummer:"))
        self.bauteilTypBtn.setText(_translate("MainWindow", "Bauteiltyp erkennen"))
        self.bauteilTypBtn.clicked.connect(self.objectTypeDetection)
        self.AuftragsdetailsTable.cellClicked.connect(self.onTableCellClick)
        #self.AuftragsdetailsTable.setEditTriggers(QtWidgets.QTableWidget.NoEditTriggers)
        #item = self.AuftragsdetailsTable.verticalHeaderItem(0)
        #item.setText(_translate("MainWindow", "test"))
        self.BezeichnungLabel.setText(_translate("MainWindow", "Bezeichnung"))
        self.PosNrLabel.setText(_translate("MainWindow", "Pos.-Nr:"))
        self.teileZuVielLabel.setText(_translate("MainWindow", "Zu prüfende Teileanzahl über Auftragsanzahl:"))
        self.teileZuVielTxtFeld.setText(str(self.teileZuViel))
        self.checkWaageBtn.setText(_translate("MainWindow", "check Waage"))
        self.checkWaageBtn.clicked.connect(self.checkWaage)
        self.stopLoopBtn.setText(_translate("MainWindow", "stop loop"))
        self.stopLoopBtn.clicked.connect(self.stopLoopClicked)
        self.waageTareBtn.setText(_translate("MainWindow", "Waage tarieren"))
        self.waageTareBtn.clicked.connect(self.onTareClick)
        self.checkBox.setText(_translate("MainWindow", "static workflow"))
        self.checkBox.clicked.connect(self.onCheckboxCheck)

        # new camera workflow
        self.startCamBtn.setText(_translate("MainWindow", "Start Camera"))
        # self.startCamBtn.clicked.connect(self.startCamBtnClicked)
        self.stopCamBtn.setText(_translate("MainWindow", "Stop Camera"))
        self.camWorkFlowcheckBox.setText(_translate("MainWindow", "Camera Workflow"))
        # self.camWorkFlowcheckBox.clicked.connect(self.onCheckboxCheck)

    def onTareClick(self):
        #Tarieren der Waage
        if ser.is_open == False:
            ser.open()
        ser.write(b'tare\n')
        ser.close()


    def onCheckboxCheck(self):
        if self.checkBox.isChecked() == True:
            print("static workflow activated")
        else:
            print("static workflow deactivated")
    
    def getRowNr(self, posNr):
        for i in range(0,self.AuftragsdetailsTable.rowCount()):
            if(self.AuftragsdetailsTable.item(i,0).text() == str(posNr)):
                return i
# brauche ich hier vermutlich nicht mehr - ist in den Worker Thread kopiert worden
    def waageNichtEingeschwungenOutput(self):
        print("Die Waage ist noch nicht eingeschwungen - Ergebnisse sind dadurch noch fehlerhaft.")

    def onTableCellClick(self):
        self.PosNrTxtFeld.setText(self.AuftragsdetailsTable.item(self.AuftragsdetailsTable.currentRow(),0).text())
        self.bauteiltypTextbox.setText(self.AuftragsdetailsTable.item(self.AuftragsdetailsTable.currentRow(),2).text())

    def setAuftragsnummer(self):
        self.auftragsnummer = self.textEdit.toPlainText()
        if(not self.auftragsnummer):
            self.auftragsnummer = ""
            self.textEdit.setText("")

    def checkAuftragsnummerEmpty(self):
        if self.auftragsnummer == "":
            print("Das Auftragsnummernfeld ist leer.")
            return True
        else:
            return False

    def checkPosNrEmpty(self):
        if self.PosNrTxtFeld.toPlainText() == "":
            return True
        else:
            return False
        

# eventuell benötigt man die Funktion hier nicht mehr, da sie zu den Threads kopiert wurde
    def getDataOfArticleType(self, allArticles, articleType):
    # die Funktion geht die Liste mit allen Artikeln durch und gibt jenen Eintrag, welcher mit dem "articleType" übereinstimmt zurück
        for i in allArticles:
            if i[1] == articleType:
                return i
        return -1
    
    def updateGUI(self):
        self.PosNrLabel.repaint()       #GUI aktualisieren
        QApplication.processEvents()    #GUI aktualisieren

    def setRowColor(self, rowID,r,g,b):
        self.AuftragsdetailsTable.item(rowID, 0).setBackground(QtGui.QColor(r,g,b)) 
        self.AuftragsdetailsTable.item(rowID, 1).setBackground(QtGui.QColor(r,g,b)) 
        self.AuftragsdetailsTable.item(rowID, 2).setBackground(QtGui.QColor(r,g,b)) 
        self.AuftragsdetailsTable.item(rowID, 3).setBackground(QtGui.QColor(r,g,b)) 
        self.AuftragsdetailsTable.item(rowID, 4).setBackground(QtGui.QColor(r,g,b))
        self.AuftragsdetailsTable.item(rowID, 5).setBackground(QtGui.QColor(r,g,b))  

#wird hier vermutlich nicht mehr benötigt - wurde in die Workerklasse kopiert
    def readWaage(self):
        # in folgender Funktion wird die Waage ausgelesen

        #print("connection is open: ", ser.is_open)              #Debuggingausgabe
        #print("port to which it is connected: ", ser.portstr)   #Debuggingausgabe
        self.checkPosNrEmpty()

        if ser.is_open == False:
            ser.open()

        
        #an die Waage den Befehl senden, dass sie ausgelesen werden soll
        ser.write(b'getWeight\n')
        #ser.write(b'tare\n')
        serialString = ser.readline().decode('utf-8').rstrip()  #Auslesen des Serial-Strings/der Messung der Waage
        
        # wenn am Ende des Strings kg steht, dann ist die Waage eingeschwungen - das wird hiermit überprüft
        lenString = len(serialString)-1
        if serialString[lenString] == "g" and serialString[lenString-1] == "k":
            #print("ist eingeschwungen")        #Debuggingausgabe
            waageEingeschwungen = True
        else:
            print("die Waage ist noch nicht eingeschwungen")
            waageEingeschwungen = False

        #aus dem String werden alle Zeichen, welche nicht zur Darstellung der Zahl benötigt werden entfernt
        intString = ""
        for i in serialString:
            if i=="-" or i=="0" or i=="." or i=="1" or i=="2" or i=="3" or i=="4" or i=="5" or i=="6" or i=="7" or i=="8" or i=="9":
                intString = intString + i
        print("Wert, welcher von der Waage ausgelesen wurde: " + intString + "kg")
        ## Waage auslesen - ENDE

        ser.close()
        #print("connection is open: ", ser.is_open)      #Debuggingausgabe

        return waageEingeschwungen, intString

    def auftragsBtnClicked(self):
        databaseQueryWorking = False   #wird für die Überprüfung, ob die Datenbankabfrage fehlerhaft ist, verwendet  
        auftragEinzelteilDaten = []

        self.setAuftragsnummer()
        if(not self.checkAuftragsnummerEmpty()):

            sql_query = "SELECT Auftraege.id, EinzelteilID, Auftragsnummer, Anzahl, Einzelteile.id, Bezeichnung, CAST(projectGeislinger.Einzelteile.GewichtMittelwert AS CHAR), CAST(projectGeislinger.Einzelteile.GewichtVarianz AS CHAR) FROM projectGeislinger.Auftraege, projectGeislinger.Einzelteile where projectGeislinger.Auftraege.EinzelteilID = projectGeislinger.Einzelteile.id AND projectGeislinger.Auftraege.Auftragsnummer = " + self.auftragsnummer
            try:
                cursor.execute(sql_query)
                # Fetch results
                auftragEinzelteilDaten = cursor.fetchall()
                databaseQueryWorking = True
            except:
                print("Fehler in der Datenbankabfrage.")



            if databaseQueryWorking==True and len(auftragEinzelteilDaten)>0:
                '''
                # Display data
                print("Ausgabe der Auftragsdetails des obigen Auftrags, inklusive Einzelteildetails:")
                for row in auftragEinzelteilDaten:
                    if str(row[2]) == self.auftragsnummer:
                        print("passt")
                    print(row)
                '''

                ### die Auftragsdaten in die Tabelle laden
                self.AuftragsdetailsTable.setRowCount(len(auftragEinzelteilDaten))
                self.item = QtWidgets.QTableWidgetItem()
                counter = 0
                for row in auftragEinzelteilDaten:
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 0, self.item)
                    self.item.setText(str(row[1]))
                    self.item.setFlags(self.item.flags() & ~QtCore.Qt.ItemIsEditable)
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 1, self.item)
                    self.item.setFlags(self.item.flags() & ~QtCore.Qt.ItemIsEditable)
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 2, self.item)
                    self.item.setText(str(row[5]))
                    self.item.setFlags(self.item.flags() & ~QtCore.Qt.ItemIsEditable)
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 3, self.item)
                    self.item.setText(str(row[3]))
                    self.item.setFlags(self.item.flags() & ~QtCore.Qt.ItemIsEditable)
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 4, self.item)
                    self.item.setText(str("0"))
                    self.item = QtWidgets.QTableWidgetItem()
                    self.AuftragsdetailsTable.setItem(counter, 5, self.item)
                    self.item.setFlags(self.item.flags() & ~QtCore.Qt.ItemIsEditable)

                    # Dropdown für Farben in die 6. Spalte einfügen
                    #self.item = QtWidgets.QTableWidgetItem()
                    #self.AuftragsdetailsTable.setItem(counter, 6, self.item)
                    combo = QComboBox()
                    combo.addItems(["Farbe", "Weiß", "Orange", "Grün"])
                    combo.currentIndexChanged.connect(lambda index, rowId =counter: self.change_row_color(rowId, index))
                    self.AuftragsdetailsTable.setCellWidget(counter, 6, combo)
                    counter = counter +1
            
            elif(databaseQueryWorking==True and len(auftragEinzelteilDaten)==0):
                print("Es wurde in der Datenbank kein Auftrag mit dieser Auftragsnummer gefunden.")

    def change_row_color(self, row, color_index):
        if(color_index == 1):
            self.setRowColor(row,255,255,255)
        if(color_index == 2):
            self.setRowColor(row,255,165,0)
        if(color_index == 3):
            self.setRowColor(row,0,255,0)
               



    def stopLoopClicked(self):
        # damit wird beim Klick auf den Stopbutton der stopLoop-boolWert in der Workerklasse auf true gesetzt -> der Stop des Threads wird initiiert
        if hasattr(self, 'objectDetectionWorker'):
            self.objectDetectionWorker.stopLoopSignal.emit(True)

        if hasattr(self, 'checkWaageWorker'):
            self.checkWaageWorker.stopLoopSignal.emit(True)

    def checkWaage(self):
        #print("Running in thread:", threading.current_thread().name) # Debuggingausgabe
        QApplication.processEvents()  

        # die Loopvariable des Workers auf False setzten, damit die Schleife durchgelaufen wird (diese wird zum Abbruch der Schleife benötigt -> siehe stopLoop)
        #self.checkWaageWorker.stopLoopSignal.emit(False)  

        # überprüfen, ob der Auftrag geladen wurde
        if(self.AuftragsdetailsTable.item(0,0) == None):
            print("Der Auftrag muss zuerst geladen werden.")
            return
        
        # überprüfen, ob ein Bauteiltyp gesetzt wurde
        if(self.checkPosNrEmpty()==True and self.checkBox.isChecked() == False):
            print("Das Pos.-Nr.-Feld ist leer.")
            return

        # den GUI Wert von Textfeld TeileZuViel in die Variable schreiben
        self.teileZuViel = int(float(self.teileZuVielTxtFeld.toPlainText()))
        self.teileZuVielTxtFeld.setText(str(self.teileZuViel))

        einzelteilID = -1   # Initialisieren der Variable mit einem Defaultwert, welcher nie eingenommen werden können sollte
        if (self.checkBox.isChecked() == False):
            #übernehmen der BauteilID aus dem Feld Pos.-Nr - wenn der Workflow dynamisch gewählt wurde
            einzelteilID = int(self.PosNrTxtFeld.toPlainText())
        else:
            # wenn der Workflow statisch gesetzt wurde, dann soll das erste Item aus der Auftragsliste geladen werden, von welchem noch nicht genug Bauteile auf der Waage liegen
            for i in range(0,self.AuftragsdetailsTable.rowCount()):
                if self.AuftragsdetailsTable.item(i,3).text() != self.AuftragsdetailsTable.item(i,4).text():
                    einzelteilID = int(self.AuftragsdetailsTable.item(i,0).text())
                    self.PosNrTxtFeld.setText(str(self.AuftragsdetailsTable.item(i,0).text()))
                    self.bauteiltypTextbox.setText(str(self.AuftragsdetailsTable.item(i,2).text()))
                    break

            if einzelteilID == -1:
                # den Thread beenden
                self.checkWaageThread.quit()
                self.checkWaageThread.wait()
                try:
                    self.checkWaageThread.started.disconnect()            # Trenne das Signal, damit es beim nächsten Start keine Konflikte gibt
                    print("Der Thread wurde beendet.")
                except:
                    print("Disconnecting the thread did not work.")
                return


        #zum setzen den Farbe der gesamten Reihe auf Orange
        self.setRowColor(self.getRowNr(einzelteilID),255,165,0) 

        # den Thread starten, welcher die Bauteilanzahl überprüft
        self.checkWaageWorker = Worker()
        self.checkWaageThread = QThread()
        
        self.checkWaageWorker.moveToThread(self.checkWaageThread)

        self.checkWaageWorker.checkWaageStartSignal.connect(self.checkWaageWorker.checkWaageThread)
        self.checkWaageWorker.checkWaageUpdateSignal.connect(self.checkWaageUpdate)
        self.checkWaageWorker.checkWaageFinishedSignal.connect(self.checkWaageFinished)
        self.checkWaageWorker.waageStoppedSignal.connect(self.threadStopped)
        self.checkWaageWorker.stopLoopSignal.connect(lambda status: setattr(self.checkWaageWorker, 'stopLoop', status)) #wird fürs aktive Stoppen des Threads benötigt
        
        self.checkWaageThread.started.connect(lambda: self.checkWaageWorker.checkWaageStartSignal.emit(einzelteilID, self.teileZuViel, self.auftragsnummer))
        self.checkWaageThread.start()

    def checkWaageUpdate(self, einzelteilID, anzahl):
        # die Funktion updated die (vom Thread) berechnete Bauteilanzahl in der GUI
        self.AuftragsdetailsTable.item(self.getRowNr(einzelteilID), 4).setText(str(anzahl))             # in die Tabelle die Anzahl an berechneten Bauteilen reinschreiben

    def checkWaageFinished(self, einzelteilID):     
        self.setRowColor(self.getRowNr(einzelteilID),0,255,0)   #zum setzen den Farbe der gesamten Reihe auf Grün 
          
        # den Thread beenden
        self.checkWaageThread.quit()
        self.checkWaageThread.wait()

        # auch wenn die Länge der Liste überschritten wurde
        # wenn der statische Workflow aktiviert ist, dann soll die checkwaage-funktion erneut aufgerufen werden 
        if(self.checkBox.isChecked() == True):
            self.checkWaage()

        # GUI updaten
        #self.updateGUI()

    def objectTypeDetection(self):
        # in dieser Funktion wird der Typ des Bauteils automatisch erkannt

        # wenn der statische Workflow ausgewählt wurde, dann soll die checkWaage Funktion aufgerufen werden, auch wenn die detectBauteiltyp-Funkion aufgerufen wurde
        if self.checkBox.isChecked() == True:
            self.checkWaage()
            return

        '''
        if(self.checkPosNrEmpty()==True):
            print("Das Pos.-Nr.-Feld ist leer.")
            return
        '''

        # die Loopvariable des Workers auf False setzten, damit die Schleife durchgelaufen wird (diese wird zum Abbruch der Schleife benötigt -> siehe stopLoop)
        #self.objectDetectionWorker.stopLoopSignal.emit(False)  

        # überprüfen, ob der Auftrag in die Tabelle geladen wurde
        if(self.AuftragsdetailsTable.item(0,0) == None):
            print("Der Auftrag muss zuerst geladen werden.")
            return

        # speichert den Wert, welcher im Textfeld steht in Variablen im Code
        self.setAuftragsnummer()

        # starten des Threats, welcher den Bauteiltyp zurück gibt
        self.objectDetectionWorker = Worker()
        self.objectDetectionThread = QThread()

        self.objectDetectionWorker.moveToThread(self.objectDetectionThread)

        self.objectDetectionWorker.objectDetectionStartSignal.connect(self.objectDetectionWorker.objectTypeDetectionThread)
        self.objectDetectionWorker.objectDetectionFinishedSignal.connect(self.objectTypeDetectionFinished)
        self.objectDetectionWorker.waageStoppedSignal.connect(self.threadStopped)
        self.objectDetectionWorker.stopLoopSignal.connect(lambda status: setattr(self.objectDetectionWorker, 'stopLoop', status)) #wird fürs aktive Stoppen des Threads benötigt
        
        self.objectDetectionThread.started.connect(lambda: self.objectDetectionWorker.objectDetectionStartSignal.emit(self.auftragsnummer))
        self.objectDetectionThread.start()

    def objectTypeDetectionFinished(self, einzelteilID, rowData):
        # wenn der Typ des Objektes erkannt wurde, dann soll die Funktion aufgerufen werden

        # Schreiben der Bauteiltype und Pos.-Nr in die jeweiligen Felder
        self.PosNrTxtFeld.setText(str(rowData[1]))
        self.bauteiltypTextbox.setText(rowData[2])

        # setzt den Boolean, um aus der Schleife raus zu gehen
        self.btTypeIsSet = True

        #zum setzen den Farbe der gesamten Reihe auf Orange
        self.setRowColor(self.getRowNr(einzelteilID),255,165,0)   

        # den Thread beenden
        self.objectDetectionThread.quit()
        self.objectDetectionThread.wait()

        '''
        try:
            self.objectDetectionThread.started.disconnect()            # Trenne das Signal, damit es beim nächsten Start keine Konflikte gibt
        except:
            print(f"Error while disconnecting: {e}")
            print("Disconnecting the thread did not work.")
        '''

        # wenn der Bauteiltyp erkannt wurde, dann soll die GUI aktualisiert werden und anschließend die CheckWaage-Funktion aufgerufen werden
        self.updateGUI()
        self.checkWaage()
                     
    def threadStopped(self, einzelteilID):
        # wenn kein Bauteil erkannt wurde, dann ist die EinzenteilID = 0
        if(einzelteilID > 0): 
            self.setRowColor(self.getRowNr(einzelteilID),255,255,255)   #zum setzen den Farbe der gesamten Reihe auf Weiß
          
        # den Thread beenden
        if hasattr(self, 'objectDetectionThread'):
            self.objectDetectionThread.quit()
            self.objectDetectionThread.wait()
            print("Der ObjektDetection-Thread wurde beendet.")

        if hasattr(self, 'checkWaageThread'):
            self.checkWaageThread.quit()
            self.checkWaageThread.wait()
            print("Der CheckWaage-Thread wurde beendet.")

# new class for Camera Object detection with YOLOv8
class CameraStreamApp(QtWidgets.QMainWindow):
    def __init__(self, ui):
        super().__init__()
        self.ui = ui
        self.yolo_stream = None  # Initialize YOLOv8CameraStream as None
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_frame)
        self.ui.startCamBtn.clicked.connect(self.start_camera) # start camera if button LoadAuftrag is clicked
        self.scene = QtWidgets.QGraphicsScene(self)
        self.ui.graphicsView.setScene(self.scene)

    def start_camera(self):
        # Start the YOLOv8 camera stream (only if not already started)
        if self.yolo_stream is None:
            self.yolo_stream = YOLOv8CameraStream(model_path="models/yolov8m_seg_e300.pt", logging_level="high")
            # self.yolo_stream.start()  # Start the YOLOv8 stream
            self.timer.start(30)  # Start the timer to update the frame every 30ms (about 33 FPS)

    def update_frame(self):
        # Update the frame from YOLOv8 stream
        if self.yolo_stream:
            ret, frame = self.yolo_stream.cap.read()  # Capture frame from YOLOv8 stream

            if ret:
                # new part including processing via yolo model
                processed_frame = self.yolo_stream.process_frame(frame)

                # Convert the frame from BGR (OpenCV format) to RGB
                # frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                frame = cv2.cvtColor(processed_frame, cv2.COLOR_BGR2RGB) # might change nomenclature later?

                # Convert the frame to QImage
                h, w, ch = frame.shape
                bytes_per_line = ch * w
                qt_image = QtGui.QImage(frame.data, w, h, bytes_per_line, QtGui.QImage.Format_RGB888)

                # Add the QImage to a QPixmap
                pixmap = QtGui.QPixmap.fromImage(qt_image)

                # Get the size of the graphicsView and scale the pixmap to fit
                view_size = self.ui.graphicsView.size()
                scaled_pixmap = pixmap.scaled(view_size, QtCore.Qt.KeepAspectRatio)

                # Update the scene with the scaled pixmap
                self.scene.clear()
                self.scene.addPixmap(scaled_pixmap)

    def closeEvent(self, event):
        # Release the camera when the application is closed
        if self.yolo_stream is not None:
            self.yolo_stream.cap.release()
        event.accept()



if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)

    # Initialize the CameraStreamApp with the UI
    camera_app = CameraStreamApp(ui)

    MainWindow.show()
    sys.exit(app.exec_())
    




