please check for errors
import sys
import re  # Import regex module for value extraction
from PySide6 import QtWidgets, QtCore  # Ensure to import QtCore for QTimer and threading
from utils import resource_path
from MainWindow import Ui_MainWindow  # Import the generated UI
from start_up import start_up  # Import the function to initialize UI components
from comms import list_open_com_ports, send_string_to_ports, send_mdf_and_check  # Import functions from comms.py
from dialog import TextDialog # Import the dialog widget

class SerialWorker(QtCore.QThread):
    data_received = QtCore.Signal(str)  # Signal to send data back to the main thread

    def __init__(self, port):
        super().__init__()
        self.port = port
        self.running = True

    def run(self):
        while self.running:
            response = send_mdf_and_check([self.port], "wei.mdf")  # Replace with actual reading logic
            if response:
                self.data_received.emit(response)  # Emit the response for processing
            self.msleep(100)  # Delay between requests to reduce load

    def stop(self):
        self.running = False
        self.wait()  # Wait for the thread to fully stop

class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.adc_factor_value = 0  # Default value, will be updated in start_up
        
        # List to hold currently open ports
        self.open_ports = []
        
        # Variable to track the connected port
        self.connected_port = None
        
        # Variable to track the state of BtnInitialize
        self.cancel_mode = False

        # Initialize the timer
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.send_mdf_message)  # Connect the timer to the send_mdf_message method
        self.adc_factor_value = 0
        # Initialize UI components
        start_up(
            self.LblStatusVersionNo, 
            self.BtnStartTest, 
            self.ComboBoxLang, 
            self.ComboBoxMaxSwl, 
            self.FldJtfDeviceModelNo, 
            self.FldJtfDeviceSerialNo, 
            self.FldJtfDeviceManufacturer, 
            self.FldJtfDeviceMaxCapacitySwl, 
            self.FldTestDataToleranceThreshold, 
            self.FldTestDataTestCycleTime, 
            self.FldTestConductedByCompany, 
            self.FldTestConductedByPlace, 
            self.FldTestDataDateOfTest, 
            self.FldTestDataNextTestDue,
            self.FldDataFlowDisplay,
            self.LblAdcFactor,
            self.LblPaperSize,
            self.ComboBoxTestTime,
            self.LblStabilizeTesting,
            self
         )
        
        def open_dialog(self):
            text = "Hello from Main Application!"
            self.dialog = TextDialog(text)
            self.dialog.exec()
        
        # Connect the BtnInitialize to the handle button click method
        self.BtnInitialize.clicked.connect(self.on_initialize_clicked)

        # Connect status checkbox
        self.CbStatusPortOPen.clicked.connect(self.on_status_port_open_changed)

        # Connect BtnStartTest to start the countdown
        self.BtnStartTest.clicked.connect(self.start_countdown)

        self.worker = None  # Initialize worker

    def on_initialize_clicked(self):
        """Event handler for BtnInitialize click."""
        # Reset FldDataFlowDisplay when initializing
        self.FldDataFlowDisplay.clear()

        if not self.cancel_mode:
            available_ports = list_open_com_ports()
            self.open_ports = available_ports
            
            if self.open_ports:
                print("Available COM ports:", self.open_ports)
                responding_port, received_data = send_string_to_ports(self.open_ports, "wei.zrp")
                if responding_port is not None:
                    self.LblStatusComPort.setText(responding_port)
                    self.CbStatusPortOPen.setChecked(True)
                    
                    # Store the connected port
                    self.connected_port = responding_port

                    # Start the worker thread
                    self.worker = SerialWorker(responding_port)
                    self.worker.data_received.connect(self.handle_received_data)  # Connect signal
                    self.worker.start()  # Start the thread
                    
                    self.BtnStartTest.show()  # Display the start test button

                    # Change button text to "Cancel"
                    self.BtnInitialize.setText("Cancel")
                    self.cancel_mode = True  # Set the cancel mode to True
                else:
                    self.LblStatusComPort.setText("--")
                    self.CbStatusPortOPen.setChecked(False)
            else:
                self.LblStatusComPort.setText("No available ports.")
        else:
            # If already in Cancel mode, stop the worker
            self.on_cancel_clicked()

    def handle_received_data(self, response):
        """Handle data received from the serial worker."""
        self.LblStatusLiveData.setText(response)  # Update the status label
        self.update_data_flow_display(response)  # Update the display
        
        # Extract the specific value
        extracted_value = self.extract_value(response)
        if extracted_value is not None:
            # Get the currently selected option from ComboBoxMaxSwl
            selected_unit = self.ComboBoxMaxSwl.currentText()  # Gets the selected option
            # Convert the extracted value to float
            data_value = float(extracted_value)  # Ensure it's a float for calculations

            # Apply conversion factors based on the selected unit
            if selected_unit == "kN":
                converted_value = data_value * 0.00980665
            elif selected_unit == "T":
                converted_value = data_value * 0.001
            elif selected_unit == "US tn":
                converted_value = data_value * 0.001102317
            elif selected_unit == "kg":
                converted_value = data_value  # No conversion needed
            else:
                converted_value = data_value  # Default case, if no valid unit is found

            # Set the converted value with the unit to LblValueDisplay
            converted_value = converted_value * float(self.adc_factor_value)
            display_value = f"{converted_value:.2f} {selected_unit}"    # Format to 2 decimal places
            
            self.LblValueDisplay.setText(display_value)  # Set to LblValueDisplay
            print("Converted Value:", display_value)  # Print for testing
            
            # Call toggle_live_data_radio to toggle the radio button self.toggle_live_data_radio()
            self.toggle_live_data_radio()
            
    def send_mdf_message(self):
        """This method is now redundant due to threading. Keep for future logic if needed."""
        pass  # You can choose to implement this if you need timed operation or more data handling.

    def extract_value(self, data):
        """Extract the specific numerical value from the 'wei.mdf' data."""
        match = re.search(r'GRO:\+\s*(\d+)', data)  # Using regex to match 'GRO: + <number>'
        if match:
            return match.group(1)  # Return the captured number as a string
        return None  # Return None if no match is found

    def toggle_live_data_radio(self):
        """Toggle the RbLiveData radio button state."""
        if self.RbLiveData.isChecked():
            self.RbLiveData.setChecked(False)  # Uncheck if it's currently checked
            #self.RbLiveData.setStyleSheet("background-color: none")
        else:
            self.RbLiveData.setChecked(True)   # Check it if it's currently unchecked
            #self.RbLiveData.setStyleSheet("background-color: green")
            
    def update_data_flow_display(self, data):
        """Update the FldDataFlowDisplay with new data, prepending it to the display."""
        current_display = self.FldDataFlowDisplay.toPlainText()  # Get current text in the display field
        new_display = data + "\n" + current_display  # Prepend new data
        self.FldDataFlowDisplay.setPlainText(new_display)  # Update the display

    def on_cancel_clicked(self):
        """Handle canceling the operation and resetting the button."""
        if self.worker:
            self.worker.data_received.disconnect(self.handle_received_data)  # Disconnect signal
            self.worker.stop()  # Stop the worker thread
            self.worker.quit()  # Request the thread to quit
            self.worker.wait()  # Wait for the thread to finish
            self.worker = None  # Ensure worker is set to None

        self.FldDataFlowDisplay.setPlainText("")  # Clear the display immediately after stopping the worker
        self.LblStatusComPort.setText("--")  # Reset com port display
        self.BtnInitialize.setText("Initialize")  # Reset button text to Initialize
        self.CbStatusPortOPen.setChecked(False)  # Reset port open status unchecked
        self.LblValueDisplay.setText("")  # Clear the value display on cancel
        self.LblStatusLiveData.setText("")  # Clear the live data display on cancel
        self.cancel_mode = False  # Reset cancel mode
        self.BtnStartTest.hide()  # Hide the start test button
        
        # stop and reset timer
        self.countdown_timer.stop()
        self.LblTimerDisplay.setText("00:00")  # Stop at 00:00

    def on_status_port_open_changed(self):
        """Event handler for CbStatusPortOPen click."""
        if self.CbStatusPortOPen.isChecked():
            if self.connected_port:
                try:
                    print(f"Closing port: {self.connected_port}")
                    # Close the specific port logic should go here
                except Exception as e:
                    print(f"Error closing port {self.connected_port}: {e}")

            self.LblStatusComPort.setText("--")
            self.CbStatusPortOPen.setChecked(False)
        else:
            self.LblStatusComPort.setText("--")

    def start_countdown(self):
        """Start the countdown timer based on the selected time in ComboBoxTestTime."""
        required_fields = [
            self.FldHydraulicJackModelNo, self.FldHydraulicJackSerialNo, self.FldHydraulicJackManufacturer, self.FldHydraulicJackMaxCapacitySwl,
            self.FldJtfDeviceModelNo, self.FldJtfDeviceSerialNo, self.FldJtfDeviceManufacturer, self.FldJtfDeviceMaxCapacitySwl,
            self.FldTestDataReasonForTest, self.FldTestDataDateOfTest, self.FldTestDataNextTestDue, self.FldTestDataToleranceThreshold,
            self.FldTestDataTestCycleTime, self.FldTestDataTestOperator,
            self.FldTestConductedByCompany, self.FldTestConductedByPlace
        ]

        # Check if all required fields are populated
        for field in required_fields:
            if not field.text():
                error_message = f"Please fill in all required fields. Missing: {field.objectName()}"
                self.show_error_dialog(error_message)
                return

        selected_time = self.ComboBoxTestTime.currentText()  # Get the selected time in mm:ss format

        # Check if the selected time is in the correct format
        if ':' in selected_time:
            minutes, seconds = map(int, selected_time.split(':'))  # Split and convert to integers
            self.remaining_time = minutes * 60 + seconds  # Convert to total seconds

            # Initialize the countdown timer
            self.countdown_timer = QtCore.QTimer(self)
            self.countdown_timer.timeout.connect(self.update_countdown)
            self.countdown_timer.start(1000)  # Update every second
        else:
            print("Invalid time format. Please select a valid time in mm:ss format.")


    def update_countdown(self):
        """Update the countdown timer display."""
        if self.remaining_time > 0:
            self.remaining_time -= 1
            minutes, seconds = divmod(self.remaining_time, 60)
            self.LblTimerDisplay.setText(f"{minutes:02}:{seconds:02}")  # Update the display
            print (f"{minutes:02}:{seconds:02}")
        else:
            self.countdown_timer.stop()
            self.LblTimerDisplay.setText("00:00")  # Stop at 00:00

# Start the application
if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())