import traceback
import datetime
from PyQt6 import QtWidgets
import PyQt6.QtCore as QtCore
from DevicePackages.defaultConfigForDevices import LOGGER, defaultConfig
from DevicePackages.GwinstekGPP4323Package.GwinstekGPP4323GUI import Ui_GwinstekGPP4323
from LowLevelWidgetsPackage.VisaControlPackage.visaControlModule import VisaControlClass
from DevicePackages.GwinstekGPP4323Package.GwinstekGPP4323ConnectionModule import ConnectionClass
from DevicePackages.DeviceStatusModule import DeviceStatusClass
from DevicePackages.GwinstekGPP4323Package.GwinstekGPP4323TestsModule import DeviceTestsClass
from LowLevelWidgetsPackage.DeviceStatusIndicatorPackage.DeviceStatusIndicatorModule import DeviceStatusIndicatorClass
from LowLevelWidgetsPackage.AdvancedProgrammablePSUPackage.AdvancedProgrammablePSUModule import AdvancedProgrammablePSUClass, _PSU_status
from typing import Dict

class GwinstekGPP4323Class(QtWidgets.QWidget):
    """Provides basic USER interface for Gwinstek GPP4323 Programmable Power Supply, test commands and connection to the device.
    Serves as wrapper for ConnectionClass and DeviceTestsClass with basic USER Interface."""
    def __init__(self,log:LOGGER = LOGGER("ALL"),\
        config_dictionary:dict = 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 = Ui_GwinstekGPP4323() #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 GwinstekGPP4323 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.tabe_device_status.layout().replaceWidget(self.ui.placeholder_for_device_status,self.DeviceStatusIndicatorWidget)
        self.ui.placeholder_for_device_status.setParent(None)

        #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")

        #TEST SETTINGS ---------------------------------------------------------
        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()

       
        
        #CONTROLL WIDGET SETTINGS --------------------------------------------------
        #CH1
        self.ControlPsuWidgetCH1 = AdvancedProgrammablePSUClass(log=self.log,channel="CH1")
        self.ControlPsuWidgetCH1.setName("ProgrammablePSUGwinstekCH1")
        self.ControlPsuWidgetCH1.signals.user_input_changed_signal.connect(self._PSUwidgetUserInputChanged)
        self.ui.CH1_layout.layout().replaceWidget(self.ui.pb_widget_placeholder_channel_1,self.ControlPsuWidgetCH1)
        self.ui.pb_widget_placeholder_channel_1.deleteLater()


        #CH2
        self.ControlPsuWidgetCH2 = AdvancedProgrammablePSUClass(log=self.log, channel="CH2")
        self.ControlPsuWidgetCH2.setName("ProgrammablePSUGwinstekCH2")
        self.ControlPsuWidgetCH2.signals.user_input_changed_signal.connect(self._PSUwidgetUserInputChanged)
        self.ui.CH2_layout.layout().replaceWidget(self.ui.pb_widget_placeholder_channel_2,self.ControlPsuWidgetCH2)
        self.ui.pb_widget_placeholder_channel_2.deleteLater()

        #CH3
        self.ControlPsuWidgetCH3 = AdvancedProgrammablePSUClass(log=self.log, channel="CH3")
        self.ControlPsuWidgetCH3.setName("ProgrammablePSUGwinstekCH3")
        self.ControlPsuWidgetCH3.signals.user_input_changed_signal.connect(self._PSUwidgetUserInputChanged)
        self.ui.CH3_layout.layout().replaceWidget(self.ui.pb_widget_placeholder_channel_3,self.ControlPsuWidgetCH3)
        self.ui.pb_widget_placeholder_channel_3.deleteLater()

        #CH4
        self.ControlPsuWidgetCH4 = AdvancedProgrammablePSUClass(log=self.log, channel="CH4")
        self.ControlPsuWidgetCH4.setName("ProgrammablePSUGwinstekCH4")
        self.ControlPsuWidgetCH4.signals.user_input_changed_signal.connect(self._PSUwidgetUserInputChanged)
        self.ui.CH4_layout.layout().replaceWidget(self.ui.pb_widget_placeholder_channel_4,self.ControlPsuWidgetCH4)
        self.ui.pb_widget_placeholder_channel_4.deleteLater()



        self.ControlPsuWidgetCH1._dummyAskForValues()
        self.ControlPsuWidgetCH2._dummyAskForValues()
        self.ControlPsuWidgetCH3._dummyAskForValues()
        self.ControlPsuWidgetCH4._dummyAskForValues()
        
        #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)

    def _CH3outputChanged(self):
        if self.sender().isChecked():
            self.DeviceTestsClass.doTest(["CH3","SET","OUTPUT","ON"])
        else:
            self.DeviceTestsClass.doTest(["CH3","SET","OUTPUT","OFF"])
    
    def _PSUwidgetUserInputChanged(self,psu_widget:_PSU_status,signal_name:str):
        self.log.log(f"{self.config_dictionary['DEVICENAME']}: Received signal {signal_name} ASKS FOR {psu_widget.requested_command}","SIGNAL")
        match psu_widget.requested_command:
            case "SET VOLTAGE":
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "VOLTAGE", f"{psu_widget.voltage:0.3f}"])
            case "SET CURRENT":
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "CURRENT", f"{psu_widget.current:0.3f}"])
            case "SET OUTPUT":
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "OUTPUT", f"{psu_widget.output}".replace("True","ON").replace("False","OFF")])

            case "SET OVP":
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "OVP_VALUE", f"{psu_widget.OVP_value}"])
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "OVP_STATE", f"{psu_widget.OVP_state}".replace("True","ON").replace("False","OFF")])
            case "SET OCP":
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "OCP_VALUE", f"{psu_widget.OCP_value}"])
                self.DeviceTestsClass.doTest([psu_widget.channel_name, "SET", "OCP_STATE", f"{psu_widget.OCP_state}".replace("True","ON").replace("False","OFF")])

            case "MEASURE READBACKS":
                status,psu_widget.voltage_readback = self.DeviceTestsClass.doTest([psu_widget.channel_name, "MEASURE", "VOLTAGE"])
                status,psu_widget.current_readback= self.DeviceTestsClass.doTest([psu_widget.channel_name, "MEASURE", "CURRENT"])
                tatus,psu_widget.output= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OUTPUT"])


            case "READ SETTINGS ONLY":
                status,psu_widget.current= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "CURRENT"])
                status,psu_widget.voltage= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "VOLTAGE"])
                status,psu_widget.output= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OUTPUT"])
                status,psu_widget.OVP_state = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OVP_STATUS"])
                status,psu_widget.OVP_value = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OVP_VALUE"])
                status,psu_widget.OCP_state = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OCP_STATUS"])
                status,psu_widget.OCP_value = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OCP_VALUE"])    

            case "READ ALL":
                status,psu_widget.voltage_readback = self.DeviceTestsClass.doTest([psu_widget.channel_name, "MEASURE", "VOLTAGE"])
                status,psu_widget.current_readback= self.DeviceTestsClass.doTest([psu_widget.channel_name, "MEASURE", "CURRENT"])
                status,psu_widget.current= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "CURRENT"])
                status,psu_widget.voltage= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "VOLTAGE"])
                status,psu_widget.output= self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OUTPUT"])
                status,psu_widget.OVP_state = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OVP_STATUS"])
                status,psu_widget.OVP_value = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OVP_VALUE"])
                status,psu_widget.OCP_state = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OCP_STATUS"])
                status,psu_widget.OCP_value = self.DeviceTestsClass.doTest([psu_widget.channel_name, "READ", "OCP_VALUE"])
        
        if psu_widget.channel_name == "CH1":
            self.ControlPsuWidgetCH1.signals.received_data_signal.emit(psu_widget,self.config_dictionary['DEVICENAME'])

        if psu_widget.channel_name == "CH2":
            self.ControlPsuWidgetCH2.signals.received_data_signal.emit(psu_widget,self.config_dictionary['DEVICENAME'])  

        if psu_widget.channel_name == "CH3":
            self.ControlPsuWidgetCH3.signals.received_data_signal.emit(psu_widget,self.config_dictionary['DEVICENAME']) 

        if psu_widget.channel_name == "CH4":
            self.ControlPsuWidgetCH4.signals.received_data_signal.emit(psu_widget,self.config_dictionary['DEVICENAME'])  



    @QtCore.pyqtSlot(str,object)
    def _errorSignalHandler(self,error_message,sender_object):
        self.log.log(f"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 _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()
