from DevicePackages.defaultConfigForDevices import LOGGER, defaultConfig
from DevicePackages.GwinstekGPP4323Package.GwinstekGPP4323ConnectionModule import ConnectionClass
import traceback
import time
from typing import Tuple
from numpy import arange


def exceptionCheck(func):
    """Decorator to catch exceptions and log them to the log file"""
    def wrapper(*args, **kwargs):
        try:
            status,result = func(*args, **kwargs)
            return status,result
        except Exception as e:
            #args[0] is self from DeviceTestsClass
            args[0].log.log(f"{args[0].config_dictionary['DEVICENAME']} - {traceback.format_exc()}","ERROR",oneline=False)
            return False, f"{e}"
    return wrapper


def checkIfBussy(func):
    """Decorator to check if the device is busy with another test,
    this check is propably not needed in test class, because is also checked in ConnectionClass at PHY level,
    but is here for future use"""
    def wrapper(*args, **kwargs):
        #args[0] is self from Connection Class
        if args[0].busy_flag == False:
            args[0].busy_flag = True
            status,result = func(*args, **kwargs)
            args[0].busy_flag = False
            return status,result
        else:
            args[0].log.log(f"{args[0].config_dictionary['DEVICENAME']} - Hardware is doing another Test, try again later","ERROR",oneline=False)
            return False,"BUSSY"
    return wrapper

class DeviceTestsClass():
    """ This class serves as higher layer for communication with the device. This function implements the test commands and tries to make
    commands unified for all devices.

    For its internal use it uses the ConnectionClass to communicate with the device. Meanwhile ConnectionClass provides from point of programmer view the lowest
    level (PHY) for communication with device. The DeviceTestClass is the application layer for the programmer.

    Lets demonstrate the need of this class on folowing example:
    To set voltage on channel 1 to 1.2V at GWINSTEK GPP4223PSU the ConnectionClass would send the following command:
    "VSET1:1.2"

    The same settings for different PSU may look like this:
    "CH1:VOLT 1.2V"

    Solution is to use this class to unify the commands and make them look like this:
    "SET VOLTAGE CH1 1.2"
    
    Also this class provides more complex functions composed with more than one command and etc...
    Complete overview of Avaiable commands can be found in avaiableCommands.xlsx and avaiableCommands.pdf in documentation folder.
    """
    ################ INIT Function ##################
    def __init__(self,config_dictionary:dict = defaultConfig,\
        log:LOGGER = LOGGER("ALL"),\
        ConnectionClass :ConnectionClass = ConnectionClass,\
        *args, **kwargs):#args = multiple input variables, **kvargs multiple keyword variables (dictionaries, touples etc.)

        self.log = log
        self.ConnectionClass = ConnectionClass
        self.config_dictionary = config_dictionary
        self.busy_flag = False #to prevent multiple tests at the same time from another thread

    @checkIfBussy
    @exceptionCheck
    def doTest(self, test_command:list) -> Tuple[bool,str]:
        """This function parses the command and calls the appropriate function to do the test.
        
        INPUTS: test_command:list or str -> EX: ["SET","VOLTAGE","CH1","1.2"] or "SET VOLTAGE CH1 1.2"]
        
        OUTPUTS: status:bool, result:str
        In case of status = True, result is the result of the test
        In case of status = False, result is the error message
        In case of status = True and result = "VIRTUAL", the test was done only in VIRTUAL mode
        """
        if self.config_dictionary["COMMUNICATION"] == "VIRTUAL":
            self.log.log(f"{self.config_dictionary['DEVICENAME']} - Doing virtual Test","WARNING")
            return True,"VIRTUAL"

        if type(test_command) != list: #in case of simple string
            test_command = test_command.split(" ")

        if test_command[0].upper() == "QUERYCOMMAND": #redirect RAW query message to ConnectionClass
            status,result = self._queryCommand(test_command[1:])
            return status,result
        
        elif test_command[0] == "WRITECOMMAND": #redirect RAW write message to ConnectionClass
            status,result = self._writeCommand(test_command[1:])
            return status,result
        
        elif test_command[0] == "READCOMMAND":
            status,result = self.ConnectionClass.readMessage() #SIMPLE READ
            return status,result

        elif test_command[0] == "READALL":
            status,result = self._readAll()
            return status,result
        


        #for EX: CH1 SET Voltage 1.2
        #    EX: CH2 READ Current
        channel = test_command[0]
        set_read_selection = test_command[1]

        if str(set_read_selection).upper() == "SET":
            match str(test_command[2]).upper(): #and ignore case sensitivity
                case "VOLTAGE":
                    status,result = self._setVoltage(channel,test_command[3:])
                case "CURRENT":
                    status,result = self._setCurrent(channel,test_command[3:])
                case "OUTPUT":
                    status,result = self._setOutput(channel,test_command[3:])
                case "OVP_VALUE":
                    status,result = self._setOVPValue(channel,test_command[3:])
                case "OVP_STATE" | "OVP_STATUS":
                    status,result = self._setOVPState(channel,test_command[3:])
                case "OCP_VALUE":
                    status,result = self._setOCPValue(channel,test_command[3:])
                case "OCP_STATE" | "OCP_STATUS":
                    status,result = self._setOCPState(channel,test_command[3:])
                
                case _:
                    self.log.log(f"{self.config_dictionary['DEVICENAME']} - Unsupported Command: {' '.join(test_command)}","ERROR")
                    result = "UNSUPPORTED COMMAND"
                    status = False
            return status,result
        
        elif str(set_read_selection).upper() == "READ":
            match str(test_command[2]).upper(): #and ignore case sensitivity
                case "VOLTAGE":
                    status,result = self._readVoltage(channel)
                case "CURRENT":
                    status,result = self._readCurrent(channel)
                case "OUTPUT":
                    status,result = self._readOutput(channel)  
                case "OVP_VALUE" :
                    status,result = self._readOVPValue(channel)
                case "OVP_STATE" | "OVP_STATUS":
                    status,result = self._readOVPState(channel)
                case "OCP_VALUE":
                    status,result = self._readOCPValue(channel)
                case "OCP_STATE" | "OCP_STATUS":
                    status,result = self._readOCPState(channel)
                
                case _:
                    self.log.log(f"{self.config_dictionary['DEVICENAME']} - Unsupported Command: {' '.join(test_command)}","ERROR")
                    result = "UNSUPPORTED COMMAND"
                    status = False
            return status,result
        
        elif str(set_read_selection).upper() == "MEASURE":
            match str(test_command[2]).upper(): #and ignore case sensitivity
                case "VOLTAGE":
                    status,result = self._measVoltage(channel)
                case "CURRENT":
                    status,result = self._measCurrent(channel)
                case "POWER":
                    status,result = self._measPower(channel)

                case "RAMP":
                    status,result = self._measRamp(channel,test_command[3:])
                case _:
                    self.log.log(f"{self.config_dictionary['DEVICENAME']} - Unsupported Command: {' '.join(test_command)}","ERROR")
                    result = "UNSUPPORTED COMMAND"
                    status = False
            return status,result
        
        else:
            self.log.log(f"{self.config_dictionary['DEVICENAME']} - Unsupported Command: {' '.join(test_command)}","ERROR")
            result = "UNSUPPORTED COMMAND"
            status = False
            return status,result

    def _queryCommand(self,test_commands:list):
        command_to_send = " ".join(test_commands)
        self.log.log(f"{self.config_dictionary['DEVICENAME']}:: - Sending command: {command_to_send}","INFO")
        result = self.ConnectionClass.queryMessage(command_to_send)
        return result
    
    def _writeCommand(self,test_commands:list):
        command_to_send = " ".join(test_commands)
        self.log.log(f"{self.config_dictionary['DEVICENAME']}:: - Sending command: {command_to_send}","INFO")
        status,result = self.ConnectionClass.sendMessage(command_to_send)
        return status,result

    def _setVoltage(self,channel,test_commands:list):
        #VSET1:1.6 -> SET VOLTAGE ON CHANNEL 1 TO 1.6V
        message_to_send = f"VSET{channel[-1]}:{test_commands[0].replace('V','')}"
        status,result = self.ConnectionClass.sendMessage(message_to_send)
        return status,result
    
    def _setCurrent(self,channel,test_commands:list) -> str:
        message_to_send = f"ISET{channel[-1]}:{test_commands[0].replace('A','')}"
        status,result = self.ConnectionClass.sendMessage(message_to_send)
        return status,result
    
    def _setOutput(self,channel,test_commands:list) -> str:
        if channel == "ALL": ## SET OUTPUT ALL ON/OFF
            if test_commands[0].upper() == "ON" or test_commands[0] == "1":
                message_to_send = f":ALLOUTON"
            
            elif test_commands[0].upper() == "OFF" or test_commands[0] == "0":
                message_to_send = f":ALLOUTOFF"
            
            else:
                return False,"INVALID COMMAND"
            
            return self.ConnectionClass.sendMessage(message_to_send)

            
        #SET OUTPUT CH1|2|3|4 ON/OFF
        message_to_send = f":OUTPut{channel[-1]} {test_commands[0]}"
        status,result = self.ConnectionClass.sendMessage(message_to_send)
        return status,result
    
    def _setOVPValue(self,channel,test_commands:list) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OVP {test_commands[0].replace('V','')}"
        return self.ConnectionClass.sendMessage(message_to_send)
    
    def _setOVPState(self,channel,test_commands:list) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OVP:STATe {test_commands[0].replace('1','ON').replace('0','OFF')}"
        return self.ConnectionClass.sendMessage(message_to_send)
    
    def _setOCPValue(self,channel,test_commands:list) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OCP {test_commands[0].replace('A','')}"
        return self.ConnectionClass.sendMessage(message_to_send)
    
    def _setOCPState(self,channel,test_commands:list) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OCP:STATe {test_commands[0].replace('1','ON').replace('0','OFF')}"
        return self.ConnectionClass.sendMessage(message_to_send)
    
    def _readVoltage(self,channel):
        message_to_send = f":SOURce{channel[-1]}:VOLTage?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result
    
    def _readCurrent(self,channel) -> str:
        message_to_send = f":SOURce{channel[-1]}:CURRent?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result
    
    def _readOutput(self,channel) -> str:
        message_to_send = f":OUTPut{channel[-1]}:STATe?"
        return self.ConnectionClass.queryMessage(message_to_send)
    
    def _readOVPValue(self,channel) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OVP?"
        return self.ConnectionClass.queryMessage(message_to_send)
    
    def _readOVPState(self,channel) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OVP:STATe?"
        return self.ConnectionClass.queryMessage(message_to_send)
    
    def _readOCPValue(self,channel) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OCP?"
        return self.ConnectionClass.queryMessage(message_to_send)
    
    def _readOCPState(self,channel) -> str:
        message_to_send = f":OUTPut{channel[-1]}:OCP:STATe?"
        return self.ConnectionClass.queryMessage(message_to_send)

    def _readAll(self) -> str:
        message_to_send = f"*READALL?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result


    def _measVoltage(self,channel):
        message_to_send = f":MEASure{channel[-1]}:VOLTage?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result
    
    def _measCurrent(self,channel) -> str:
        message_to_send = f":MEASure{channel[-1]}:CURRent?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result
    
    def _measPower(self,channel) -> str:
        message_to_send = f":MEASure{channel[-1]}:POWEr?"
        status,result = self.ConnectionClass.queryMessage(message_to_send)
        return status,result

    def _measRamp(self,channel,test_commands:list) -> str:
        #MEAS RAMP UP/DOWN START STOP STEP DELAY
        direcetion = test_commands[1] #UP/DOWN
        start = float(test_commands[2]) #START VOLTAGE
        stop = float(test_commands[3]) #STOP VOLTAGE
        step = float(test_commands[4]) #STEP VOLTAGE
        delay = float(test_commands[5])/1000 #DELAY TIME

        if direcetion == "UP":
            iterate_range = arange(start,stop,step)
        elif direcetion == "DOWN":
            iterate_range = arange(stop,start,-step)
        else:
            return False,"INVALID DIRECTION"
        
        match test_commands[0].upper():
            case "VOLTAGE":
                real_measured_voltage_steps = []
                real_measured_current_steps = []
                for voltage in iterate_range:
                    voltage = f"{voltage:.4f}"
                    status, result = self._setVoltage(channel,[voltage])
                    if status == False:
                        return status,f"ERROR IN RANGE,SETTING VOLTAGE = {voltage} -> {result}"
                    
                    time.sleep(delay)
                    status,meas_voltage = self._measVoltage(channel)
                    if status == False:
                        return status,f"ERROR IN RANGE,READING VOLTAGE = {voltage}V -> {meas_voltage}"
                    real_measured_voltage_steps.append(meas_voltage)

                    status,meas_current = self._measCurrent(channel)
                    if status == False:
                        return status,f"ERROR IN RANGE,READING CURRENT for VOLTAGE = {voltage}V -> {meas_current}"
                    real_measured_current_steps.append(meas_current)

                    self.log.log(f"VOLTAGE:{voltage} -> MEASURED_VOLTAGE:{meas_voltage} -> MEASURED_CURRENT:{meas_current}", "CRITICAL")
                
                #print(iterate_range.tolist(), type(iterate_range.tolist()))
                return True, f"RAMP FINISHED OK-> RESULTS ARE SHOWN IN GRAPH" 
            
            case _:
                return False,"INVALID MEASURED PARAMETER"
