import os
import time
import traceback
import datetime
from PyQt6 import QtWidgets
import PyQt6.QtCore as QtCore
import DevicePackages.defaultConfigForDevices as defaultConfigForDevices
from DevicePackages.ArrigoPackage.ArrigoGUI import Ui_ARRIGOwidget as ARRIGOwidgetGUI
from LowLevelWidgetsPackage.Relay8Package.Relay8Module import Relay8Class
from LowLevelWidgetsPackage.Relay16Package.Relay16Module import Relay16Class
from LowLevelWidgetsPackage.Relay32Package.Relay32Module import Relay32Class
from LowLevelWidgetsPackage.VisaControlPackage.visaControlModule import VisaControlClass
from DevicePackages.ArrigoPackage.ArrigoConnectionModule import ConnectionClass
from DevicePackages.DeviceStatusModule import DeviceStatusClass
from DevicePackages.ArrigoPackage.ArrigoTestsModule import DeviceTestsClass
from LowLevelWidgetsPackage.DeviceStatusIndicatorPackage.DeviceStatusIndicatorModule import DeviceStatusIndicatorClass

class ArrigoClass(QtWidgets.QWidget):
    def __init__(self,log:defaultConfigForDevices.LOGGER = defaultConfigForDevices.LOGGER("INFO"),\
        config_dictionary:dict = defaultConfigForDevices.defaultConfig,\
        *args, **kwargs):#args = multiple input variables, **kvargs multiple keyword variables (dictionaries, touples etc.)
    ################ INIT Function ##################
        super().__init__(*args, **kwargs)#super inherits all methods and properties from its parent  (QWIDGET classes)
        #Load UI forms creaated in QTdDesigner
        self.ui = ARRIGOwidgetGUI() #UI form generated by UI designe
        self.ui.setupUi(self) #setup UI form
        self.log = log #log class
        self.config_dictionary = config_dictionary #config dictionary
        self.log.log(f"{self.config_dictionary['DEVICENAME']} :: STARTING Arrigo Class","UNNECESSARY")
        self.ui.la_device_name.setText(f"{self.config_dictionary['DEVICENAME']} CONTROL PANNEL")
        self.ConnectionClass = ConnectionClass(log=self.log,config_dictionary=self.config_dictionary)
        self.DeviceStatusClass = DeviceStatusClass(log=self.log)
        
        #DEVICE STATUS CLASS SETTINGS --------------------------------------------------
        self.DeviceStatusIndicatorWidget = DeviceStatusIndicatorClass(log=self.log,
                device_config=self.config_dictionary,
                device_status_class= self.DeviceStatusClass)
        self.ui.tab_device_status.layout().replaceWidget(self.ui.pb_widget_placeholder_device_status,self.DeviceStatusIndicatorWidget)
        self.ui.pb_widget_placeholder_device_status.deleteLater()
        self.ui.tab_device_status.layout().update()

        #change name after device status indicator widget is created
        self.DeviceStatusClass.device_name = self.config_dictionary["DEVICENAME"]

        #SIGNAL HANDLERS --------------------------------------------------
        self.ConnectionClass.signals.error_signal.connect(self._errorSignalHandler)

        if self.config_dictionary["CONNECTATSTARTUP"]:
            self.DeviceStatusClass.last_startUp_time = datetime.datetime.now()
            try:
                self._connect(ignore_wrong_setup=True)
            except Exception:
                self.log.log(f"{self.config_dictionary['DEVICENAME']} {traceback.format_exc()}","ERROR")
                self.DeviceStatusClass.last_fault_description = "Connection failed"   
                self.DeviceStatusClass.connection_status = self.DeviceStatusClass.STATE.ERROR
                self.DeviceStatusClass.test_status = self.DeviceStatusClass.STATE.ERROR
                self.DeviceStatusClass.last_fault_time = datetime.datetime.now()

            self.log.log("\n" + self.DeviceStatusClass.printStatus(),"INFO")
        self.DeviceTestsClass = DeviceTestsClass(config_dictionary=self.config_dictionary,log=self.log,ConnectionClass=self.ConnectionClass)
    
        #VISA WIDGET SETTINGS --------------------------------------------------
        if self.config_dictionary["COMMUNICATION"] == "VISA":
            self.VisaWidget = VisaControlClass(log=self.log, ConnectionClass=self.ConnectionClass,\
                DeviceStatusClass= self.DeviceStatusClass)

            self.ui.tab_visa.layout().replaceWidget(self.ui.pb_widget_placeholder_visa,self.VisaWidget)
            self.ui.pb_widget_placeholder_visa.deleteLater()
            self.ui.tab_visa.layout().update()

        
        #RELAY WIDGET SETTINGS --------------------------------------------------
        self._widget_placeholder_EXT_1 = self.ui.pb_widget_placeholder_EXT_1 #Points to the widget which will be replace by selected relay widget
        self._widget_placeholder_EXT_2 = self.ui.pb_widget_placeholder_EXT_2 #Points to the widget which will be replace by selected relay widget
        self._widget_placeholder_EXT_3 = self.ui.pb_widget_placeholder_EXT_3 #Points to the widget which will be replace by selected relay widget

        self.ui.cb_EXT1.currentTextChanged.connect(self._addRelayWidget) #relay card size selection
        self.ui.cb_EXT2.currentTextChanged.connect(self._addRelayWidget) #relay card size selection
        self.ui.cb_EXT3.currentTextChanged.connect(self._addRelayWidget) #relay card size selection

        #to ensure correct scaling of tabs
        self.ui.tabWidget.minimumSizeHint = self.tabMinimumSizeHint
        self.ui.tabWidget.sizeHint = self.tabSizeHint
        self.ui.tabWidget.currentChanged.connect(self.ui.tabWidget.updateGeometry)

    @QtCore.pyqtSlot(str,object)
    def _errorSignalHandler(self,error_message,sender_object):
        self.log.log(f"{self.config_dictionary['DEVICENAME']}: ERROR FROM{sender_object.__class__.__name__} : {error_message}","ERROR")
        self.DeviceStatusClass.last_fault_description = error_message
        self.DeviceStatusClass._last_fault_time = datetime.datetime.now()
    
    @QtCore.pyqtSlot(list,str)
    def _relayWidgetSignal(self,signal_list:list,name:str):
        self.log.log(f"{self.config_dictionary['DEVICENAME']} Relay Widget Signal","INFO")
        self.log.log(f"{self.config_dictionary['DEVICENAME']} Name: " + str(name),"INFO")
        self.log.log(f"{self.config_dictionary['DEVICENAME']} Signal List: " + str(signal_list),"INFO")

        command = f"SETEXTPORT;{name[-1]};"
        for vaules in signal_list:
            command += str(vaules).replace("True","1").replace("False","0") + ";"
        command = command[:-1]#remove last ";"

        self.log.log(f"{self.config_dictionary['DEVICENAME']} -Writing: {command}","INFO")

        status,response = self.ConnectionClass.queryMessage(command)
        self.log.log(f"{self.config_dictionary['DEVICENAME']} -Reading: {response}","INFO")

    def _addRelayWidget(self):
        """Adds RELAY widgets to the layout"""
        if self.sender().currentText() == "RELAY 8":
            widget_selection = Relay8Class(log=self.log) #serves as reference to last selected widget at EXT PORT
        elif self.sender().currentText() == "RELAY 16":
            widget_selection = Relay16Class(log=self.log) #serves as reference to last selected widget at EXT PORT
        elif self.sender().currentText() == "RELAY 32":
            widget_selection = Relay32Class(log=self.log) #serves as reference to last selected widget at EXT PORT

        widget_selection.signals.checkbox_updated.connect(self._relayWidgetSignal) #signal to send signals from relay widgets
        #Update ext ports accordingly to selected relay widget size
        if self.sender() == self.ui.cb_EXT1:
            widget_selection.setName("EXT_1") #set name of widget. In future this will be used to identify the widget when it sends a signal
            self.ui.EXT_1_LAYOUT.layout().replaceWidget(self._widget_placeholder_EXT_1,widget_selection) #replace widget reference with new widget
            self._widget_placeholder_EXT_1.deleteLater()
            self.ui.EXT_1_LAYOUT.layout().update()
            self._widget_placeholder_EXT_1 = widget_selection #save reference to placeholder widget

        if self.sender() == self.ui.cb_EXT2:
            widget_selection.setName("EXT_2")#set name of widget. In future this will be used to identify the widget when it sends a signal
            self.ui.EXT_2_LAYOUT.layout().replaceWidget(self._widget_placeholder_EXT_2,widget_selection) 
            self._widget_placeholder_EXT_2.deleteLater()
            self.ui.EXT_2_LAYOUT.layout().update()
            self._widget_placeholder_EXT_2 = widget_selection
            

        if self.sender() == self.ui.cb_EXT3:
            widget_selection.setName("EXT_3")#set name of widget. In future this will be used to identify the widget when it sends a signal
            self.ui.EXT_3_LAYOUT.layout().replaceWidget(self._widget_placeholder_EXT_3,widget_selection)
            self._widget_placeholder_EXT_3.deleteLater()
            self.ui.EXT_3_LAYOUT.layout().update()
            self._widget_placeholder_EXT_3 = widget_selection

    def _connect(self, ignore_wrong_setup:bool = False) -> bool:
        """ little bit customized connect function from connection class"""
        error_description = ""
        error_flag = False
        status,response = self.ConnectionClass.connectionSetup()
        if status == False: #setup connection parameters
            error_description = "Connection setup failed"
            error_flag = True

        #someteimes visa cannot setup connecion succesfully, but still can be connected.
        #ignore_wrong_setup is used to ignore this error and try to connect to device anyway
        if error_flag == True and ignore_wrong_setup == False:
            self.DeviceStatusClass.last_fault_description = error_description   
            self.DeviceStatusClass.connection_status = self.DeviceStatusClass.STATE.ERROR
            self.DeviceStatusClass.test_status = self.DeviceStatusClass.STATE.ERROR
            self.DeviceStatusClass.last_fault_time = datetime.datetime.now()
            return False

        status,response = self.ConnectionClass.connect()
        if status == False: #connection was succesful
            error_description = "Connection failed"
            error_flag = True

        else: #someteimes visa cannot setup connecion succesfully, but still can be connected
            error_flag = False

        if self.ConnectionClass.blockingSelfCheck() == False:
            error_description = "Self check failed"
            error_flag = True

        if error_flag == False:
            self.DeviceStatusClass.connection_status = self.DeviceStatusClass.STATE.CONNECTED
            self.DeviceStatusClass.test_status = self.DeviceStatusClass.STATE.READY
            self.DeviceStatusClass.last_self_check_time = datetime.datetime.now()
            return True

        else:
            self.DeviceStatusClass.last_fault_description = error_description   
            self.DeviceStatusClass.connection_status = self.DeviceStatusClass.STATE.ERROR
            self.DeviceStatusClass.test_status = self.DeviceStatusClass.STATE.ERROR
            self.DeviceStatusClass.last_fault_time = datetime.datetime.now()
            return False


    def close(self) -> bool:
        self.ConnectionClass.disconnect()
        self.DeviceStatusClass.connection_status = self.DeviceStatusClass.STATE.DISCONNECTED
        self.DeviceStatusClass.test_status = self.DeviceStatusClass.STATE.UNKNOWN
        return super().close()

    #to ensure correct scaling of tabs
    def tabMinimumSizeHint(self):
        return self.ui.tabWidget.sizeHint()
    #to ensure correct scaling of tabs
    def tabSizeHint(self):
        current = self.ui.tabWidget.currentWidget()
        if not current:
            return super().sizeHint()
        return current.sizeHint()



if __name__ == "__main__":
    if os.path.exists("ArrigoGUI.py"):
        os.remove("ArrigoGUI.py")
    bashCommand = "pyuic6 -o ArrigoGUI.py Arrigo.ui"
    os.system(bashCommand)
    app = QtWidgets.QApplication([])
    window = ArrigoClass(QtWidgets.QWidget)
    window.show()
    app.exec()