import time
from PyQt6 import QtCore
from LowLevelWidgetsPackage.defaultConfigForWidgets import LOGGER
from TestsRelatedPackage.TestOverviewPackage.TestOverviewModule import AllGroupsOverviewClass,_CommandResultClass,_TestResultClass
from typing import Dict,List
from DevicePackages.TestWidgedDataClass import TestWidgedDataClass
import subprocess
import numpy as np
if __name__ == "__main__":
    import main

class WorkerSignals(QtCore.QObject):
    signal_test_finished = QtCore.pyqtSignal(object,str)
    signal_test_started = QtCore.pyqtSignal(object,str)
    signal_command_finished = QtCore.pyqtSignal(object,str)
    signal_command_started = QtCore.pyqtSignal(object,str)
    signal_test_group_finished = QtCore.pyqtSignal(str,str)
    signal_test_group_started = QtCore.pyqtSignal(str,str)

class TestParserClass(QtCore.QRunnable):
    def __init__(self,log:LOGGER = LOGGER("ALL"), function_dictionary: Dict[str, callable] = {},
                 AllGroups:AllGroupsOverviewClass = AllGroupsOverviewClass, test: str|_CommandResultClass | _TestResultClass = "BASICGROUP", *args,**kwargs) -> None:
        super().__init__(*args,**kwargs)
        self.log = log
        self.function_dictionary = function_dictionary
        self.AllTests = AllGroups
        self.widget_data = TestWidgedDataClass()
        self.signals = WorkerSignals()
        #some basic functions are defined here, more has to be defined in mainClassReference
        #ADD SOME BASICS FUNCTIONS HERE
        if "DELAY" not in self.function_dictionary:
           self.function_dictionary["DELAY"] = self._delayFunctionCommand
        
        if "LOG" not in self.function_dictionary:
            self.function_dictionary["LOG"] = self._logFunctionCommand

        if "NUMBER_RANGE" not in self.function_dictionary:
            self.function_dictionary["NUMBER_RANGE"] = self._numberRangeCommand

        if "CMDLINE" not in self.function_dictionary:
            self.function_dictionary["CMDLINE"] = self._cmdlineCommand


        if type(test) == str: #its called as group name
            self.test = self.AllTests.getTestsFromGroup(test)[0] #just dummy test placeholder
            self.group_name = test 
            self.command = self.test.commands[0] #just dummy test placeholder
            self.function_to_be_called = self._runGroup

        if type(test) == _CommandResultClass:
            self.command = test
            self.test = self.command._parent_test
            self.group_name = self.test._parent_group_name
            self.function_to_be_called = self._runCommand

        elif type(test) == _TestResultClass:
            self.test = test
            self.group_name = self.test._parent_group_name
            self.command = test.commands[0] #just dummy test placeholder
            self.function_to_be_called = self._runTest

    def run(self):
        self.function_to_be_called() #selected function at init.... Distinguish between test and command

    def _runGroup(self):
        self.AllTests.loadDefaultsForGroup(self.group_name)
        self.AllTests.groups_dict[self.group_name].group_result = "RUNNING"
        self.signals.signal_test_group_started.emit(self.group_name, "TestParserClass")
        time_start = time.time()
        fault_flag = False
        for test in self.AllTests.getTestsFromGroup(self.group_name):
            self.test = test
            self._runTest()
            if self.test.result == "FAIL" or self.test.result == False:
                fault_flag = True
                break

        if fault_flag == False:
            group_result = "PASS"
        
        else:
            group_result = "FAIL"
        
        group_time = time.time() - time_start
        self.AllTests.updateGroupResult(group_name=self.group_name, time=group_time, result = group_result)
        self.signals.signal_test_group_finished.emit(self.group_name,"TestParserClass")


    def _updateWidgetValues(self):
        widget_properties = ["X_AXIS","Y1_LEFT_AXIS","Y2_RIGHT_AXIS","Y3_LEFT_AXIS","Y4_RIGHT_AXIS"]
        for widget_property in widget_properties:
            if widget_property == self.command.command_widget_information:
                if widget_property in self.test.test_widget_information:
                    self.test.test_widget_information[widget_property] += self.widget_data.list_of_floats
                else:
                    self.test.test_widget_information[widget_property] = self.widget_data.list_of_floats
                    
    
    def _runTest(self):
        self.AllTests.loadDefaultsForTest(self.test)
        self.test.result = "RUNNING"
        start_time = time.time()
        self.log.log(f"TestParserClass: Request for test group {self.test.name} received", "DEBUG")
        self.signals.signal_test_started.emit(self.test,"_threadRunTestWorker")
        fault_flag = False

        x_data = []
        y_data = []
        
        for command in self.test.commands:
            self.command = command
            self._runCommand() #this also updates command values
            if self.command.result == "FAIL" or self.command.result == False:
                fault_flag = True
                break
            self._updateWidgetValues()
        
        if fault_flag == False:
            self.test.result = "PASS"
        
        else:
            self.test.result = "FAIL"

        self.test.test_time = time.time() - start_time
        if "X_AXIS" in self.test.test_widget_information:
            print("FOUND X AXIS")
            x_data = self.test.test_widget_information["X_AXIS"]
            y_data = self.test.test_widget_information["Y1_LEFT_AXIS"]
            self.test.result_widget.updateGraphData(x_data,y_data)
        self.signals.signal_test_finished.emit(self.test,"TestParserClass")


    def _runCommand(self):
        self.log.log(f"TestParserClass: Request for test {self.command} received", "DEBUG")
        self.command.result = "RUNNING"
        self.signals.signal_command_started.emit(self.command,"TestParserClass")
        command_prefix = self.command.command.split(" ")[0]
        time_start = time.time()

        #CAUTION FOR - ELSE STRUCTURE -> if prefix is not found, the else statement is executed
        for keys in self.function_dictionary:
            if command_prefix.upper() == keys:
                status,response = self.function_dictionary[keys](self.command.command.split(" ")[1:],self.widget_data)
                break
        
        else:
            self.log.log(f"TestParserClass: Command prefix {command_prefix} not recognized", "CRITICAL")
            status = False
            response= "NOT SUPPORTED"

        if status == True:
            if self.command.expected == "" or self.command.expected == None or self.command.expected == "NULL" or self.command.expected == "None":
                status = self._checkIfValueIsInRange(response,self.command.lower_lim,self.command.upper_lim)
            else:
                status = self._checkIfValueIsSameAsExpected(response,self.command.expected)

        self.command.command_time = f"{time.time() - time_start:0.4f}s"
        self.command.result = str(status).replace("True","PASS").replace("False","FAIL")
        
        self.command.measured = response
        self.log.log(f"TestParserClass: Command {self.command.command} finished with result {self.command.result} and value {self.command.measured}", "DEBUG")
        self.signals.signal_command_finished.emit(self.command,"TestParserClass")

    def _checkIfValueIsSameAsExpected(self,value,expected):
        self.log.log(f"TestParserClass: Checking if {value} is same as {expected}", "INFO")
        if expected == "*****" or value == "VIRTUAL":
            return True
        
        if "||" in expected:
            expected = str(expected).split("||")[1]
            if expected in value:
                return True
        

        try:
            value = float(value)
            expected = float(expected)
            if value == expected:
                return True
        except:
            self.log.log(f"TestParserClass: Could not convert {value} or {expected} to float values will be compared as strings", "INFO")


        if str(value) == str(expected):
            return True
        else:
            return False

    def _checkIfValueIsInRange(self,value,lower_lim,upper_lim):
        self.log.log(f"TestParserClass: Checking if {value} is in range from {lower_lim} to {upper_lim}", "INFO")
        if value == "VIRTUAL":
            return True
        
        both_limits_are_empty = 0
        
        if lower_lim == "" or lower_lim == None or lower_lim == "NULL" or lower_lim == "None":
            lower_lim = -float("inf")
            both_limits_are_empty += 1

        if upper_lim == "" or upper_lim == None or upper_lim == "NULL" or upper_lim == "None":
            upper_lim = float("inf")
            both_limits_are_empty += 1

        if both_limits_are_empty == 2:
            return True

        if "||" in str(lower_lim):
            to_be_replaced = lower_lim.split("||") #string before ||
            value = str(value).replace(to_be_replaced[0],"").replace(to_be_replaced[2],"") #remove string before and after ||
            value = float(value) #convert to float
            lower_lim = float(to_be_replaced[1]) #number between ||
        
        if "||" in str(upper_lim):
            to_be_replaced = lower_lim.split("||") #string before ||
            value = str(value).replace(to_be_replaced[0],"").replace(to_be_replaced[2],"") #remove string before and after ||
            value = float(value) #convert to float
            upper_lim = float(to_be_replaced[1]) #number between ||

        try: #try to convert to float -> some tests may return incorrect strings, which are not convertable to float evenif limits are set correctly
            value = float(value)
        except Exception as e:
            self.log.log(f"TestParserClass: Could not convert {value} to float", "WARNING")
            return False

        if value >= float(lower_lim) and value <= float(upper_lim):
            self.log.log(f"TestParserClass: Value {value} is in range from {lower_lim} to {upper_lim}", "INFO")
            return True
        else:
            self.log.log(f"TestParserClass: Value {value} is not in range {lower_lim} to {upper_lim}", "WARNING")
            return False


    def _delayFunctionCommand(self, command:str, widget_data:TestWidgedDataClass):
        time.sleep(float(command[0])/1000)
        widget_data.loadSuccesfullResponseWithNoData()
        return True, f"DELAY {command[0]}ms"
    
    def _logFunctionCommand(self, command:str, widget_data:TestWidgedDataClass):
        widget_data.loadSuccesfullResponseWithNoData()
        return True, f"LOG {' '.join(command)}"
    
    def _numberRangeCommand(self, command:str, widget_data:TestWidgedDataClass):
        _from = command[0]
        _to = command[1]
        _number_of_samples = command[2]
        result_array = np.linspace(float(_from),float(_to),int(_number_of_samples))
        #result = np.array2string(result_array, separator=',', formatter={'float_kind':lambda x: "%.3f" % x})
        data = ",".join([f"{x:0.3f}" for x in result_array])
        widget_data.loadSuccesfullResponseWithNoData()
        self.widget_data.list_of_floats = data
        self.widget_data.string_response = data
        return True, data
    

    def _cmdlineCommand(self, command:str, widget_data:TestWidgedDataClass):
        self.log.log(f"TestParserClass: Executing command {command}", "INFO")
        try:
            cmd = command
            #cmd =["ipconfig"]
            p = subprocess.run(args = command, shell=True, text=True,capture_output=True)
            if p.returncode == 0:
                try:
                    message = p.stdout.split("\n")
                    data = [float(x) for x in message[1].split(";")]
                    widget_data.loadSuccesfullResponseWithNoData()
                    widget_data.string_response = p.stdout
                    widget_data.list_of_floats = data
                    return True, p.stdout
                except Exception as e:
                    self.log.log(f"TestParserClass: Couldnt parse cmdline result to list with exception: {e}", "WARNING")
                    widget_data.loadSuccesfullResponseWithNoData()
                    self.widget_data.string_response = p.stdout
                    return False, p.stdout
            else:
                self.log.log(f"TestParserClass: Command {command} failed with error from commandline script", "ERROR")
                widget_data.loadFailResponseWithNoData()
                return False, p.stderr

        except Exception as e:
            self.log.log(f"TestParserClass: Command {command} failed with error {e}", "ERROR")
            widget_data.loadFailResponseWithNoData()
            return False, e