import sys
from PyQt6.QtWidgets import (
    QApplication,
    QWidget,
    QLabel,
    QComboBox,
    QPushButton,
    QVBoxLayout,
    QHBoxLayout,
    QSpinBox,
    QGridLayout,
    QGroupBox,
    QTabWidget,
    QTextEdit,
    QLineEdit,
    QRadioButton,
)
from PyQt6.QtCore import Qt
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class GPUData:
    def __init__(self):
        self.core_data = {
            "RDNA3": {
                "Navi 31": {
                    "transistors_per_core": 150e9,
                    "die_size_per_core": 600,
                    "shaders_per_core": 128,  # Shaders per core
                    "nodes": {
                        "5nm": {"cost": 100, "size": 0.5, "clock_range": (1.4, 2.8)},
                        "4nm": {"cost": 120, "size": 0.4, "clock_range": (1.4, 3.2)},
                    }
                },
                "Navi 32": {
                    "transistors_per_core": 100e9,
                    "die_size_per_core": 400,
                    "shaders_per_core": 128, # Shaders per core
                    "nodes": {
                        "6nm": {"cost": 80, "size": 0.6, "clock_range": (1.2, 2.6)},
                        "5nm": {"cost": 90, "size": 0.5, "clock_range": (1.4, 2.8)},
                    }
                }
            },
            "Ada Lovelace": {
                "AD102": {
                    "transistors_per_core": 180e9,
                    "die_size_per_core": 600,
                    "shaders_per_core": 128, # Shaders per core
                    "nodes": {
                        "5nm": {"cost": 110, "size": 0.5, "clock_range": (1.4, 2.8)},
                        "4nm": {"cost": 130, "size": 0.4, "clock_range": (1.4, 3.2)},
                    }
                },
                "AD103": {
                    "transistors_per_core": 120e9,
                    "die_size_per_core": 450,
                    "shaders_per_core": 128, # Shaders per core
                    "nodes": {
                        "5nm": {"cost": 90, "size": 0.5, "clock_range": (1.4, 2.8)},
                        "4nm": {"cost": 110, "size": 0.4, "clock_range": (1.4, 3.2)},
                    }
                }
            }
        }

        self.shader_variants = {
            "Ultimate Power Saving": 0.9,  # Multiplier for end of clock range
            "Power Efficient": 1.0,      # Multiplier for middle of clock range
            "Performance": 1.1           # Multiplier for top of clock range 
        }

        self.shader_data = {
            "RDNA3": {"transistors": 1e6, "power": 0.1},  # Example data
            "Ada Lovelace": {"transistors": 1.2e6, "power": 0.12},
        }

        self.rt_core_data = {
            "RDNA3": {"transistors": 5e6, "power": 0.5},  # Example data
            "Ada Lovelace": {"transistors": 6e6, "power": 0.6},
        }

        self.tensor_core_data = {
            "RDNA3": {"transistors": 8e6, "power": 0.8},  # Example data
            "Ada Lovelace": {"transistors": 10e6, "power": 1},
        }

        # Cache Data (Base values per unit)
        self.cache_data = {
            "L1": {"transistors_per_kb": 1e6, "size_per_kb": 1, "latency": 0.5, "bandwidth": 200},
            "L2": {"transistors_per_mb": 5e9, "size_per_mb": 1, "latency": 1, "bandwidth": 100},
            "L3": {"transistors_per_mb": 30e9, "size_per_mb": 1, "latency": 2, "bandwidth": 500}
        }

        self.memory_data = {
            "GDDR5": {"density": 8, "cost_per_gb": 5},  # Cost is per GB
            "GDDR6": {"density": 8, "cost_per_gb": 8},
            "GDDR6X": {"density": 16, "cost_per_gb": 12},
            "HBM2": {"cost_per_gb": 20},  # Cost is per GB
            "HBM2e": {"cost_per_gb": 30},
        }

        self.rop_data = {
            "ROP8": {"transistors": 2e9, "count": 8, "performance": 100},  # Example data
            "ROP16": {"transistors": 4e9, "count": 16, "performance": 200},
        }

        self.tmu_data = {
            "TMU64": {"transistors": 3e9, "count": 64, "performance": 100},  # Example data
            "TMU128": {"transistors": 6e9, "count": 128, "performance": 200},
        }

        self.media_engine_data = {
            "6th Gen": {
                "streams": 8,
                "resolution": "4K",
                "fps": 60,
                "features": "AV1, VP9, HEVC, H.264"
            },
            "7th Gen": {
                "streams": 12,
                "resolution": "8K",
                "fps": 60,
                "features": "AV1, VP9, HEVC, H.264, HDR10+"
            },
            "8th Gen": {
                "streams": 16,
                "resolution": "8K",
                "fps": 120,
                "features": "AV1, VP9, HEVC, H.264, HDR10+, Dolby Vision"
            }
        }

    def calculate_gpu_stats(self, selected_data):
        total_transistors = 0
        total_die_size = 0
        cost = 0
        power_consumption = 0
        performance = {}

        core_arch = selected_data["core"]
        core_version = selected_data["core_version"]
        shader_variant = selected_data["shader_variant"]
        num_shaders = selected_data["num_shaders"]
        num_rt_cores = selected_data["num_rt_cores"]
        num_tensor_cores = selected_data["num_tensor_cores"]
        num_memory_controllers = selected_data["num_memory_controllers"]
        memory_type = selected_data["memory_type"]
        memory_density = selected_data["memory_density"]
        hbm_bus_width = selected_data["hbm_bus_width"]
        memory_frequency = float(selected_data["memory_frequency"])
        selected_node = selected_data["node"]
        num_rops = selected_data["num_rops"]
        num_tmus = selected_data["num_tmus"]

        # --- Core Calculations ---
        num_cores = num_shaders // self.core_data[core_arch][core_version]["shaders_per_core"]
        total_transistors += self.core_data[core_arch][core_version]["transistors_per_core"] * num_cores
        total_die_size += self.core_data[core_arch][core_version]["die_size_per_core"] * num_cores

        # --- Clock Speed Calculation ---
        clock_min, clock_max = self.core_data[core_arch][core_version]["nodes"][selected_node]["clock_range"]
        clock_speed = clock_min + (clock_max - clock_min) * self.shader_variants[shader_variant]
        performance["Clock Speed"] = f"{clock_speed:.2f} GHz"

        # RT Core Transistors and Power (same as before)
        if num_rt_cores:
            total_transistors += self.rt_core_data[core_arch]["transistors"] * num_rt_cores
            power_consumption += self.rt_core_data[core_arch]["power"] * num_rt_cores

        # Tensor Core Transistors and Power (same as before)
        if num_tensor_cores:
            total_transistors += (
                self.tensor_core_data[core_arch]["transistors"] * num_tensor_cores
            )
            power_consumption += (
                self.tensor_core_data[core_arch]["power"] * num_tensor_cores
            )

        # Cache Transistor, Die Size, and Performance
        l1_cache_per_shader = selected_data["l1_cache_per_shader"]
        l2_cache_size = selected_data["l2_cache_size"]
        l3_cache_size = selected_data["l3_cache_size"]

        # L1 Cache
        total_transistors += self.cache_data["L1"]["transistors_per_kb"] * l1_cache_per_shader * num_shaders
        total_die_size += self.cache_data["L1"]["size_per_kb"] * l1_cache_per_shader * num_shaders
        performance["L1 Latency"] = self.cache_data["L1"]["latency"]
        performance["L1 Bandwidth"] = self.cache_data["L1"]["bandwidth"]

        # L2 Cache
        total_transistors += self.cache_data["L2"]["transistors_per_mb"] * l2_cache_size
        total_die_size += self.cache_data["L2"]["size_per_mb"] * l2_cache_size
        performance["L2 Latency"] = self.cache_data["L2"]["latency"]
        performance["L2 Bandwidth"] = self.cache_data["L2"]["bandwidth"]

        # L3 Cache (Optional)
        if l3_cache_size:
            total_transistors += self.cache_data["L3"]["transistors_per_mb"] * l3_cache_size
            total_die_size += self.cache_data["L3"]["size_per_mb"] * l3_cache_size
            performance["L3 Latency"] = self.cache_data["L3"]["latency"]
            performance["L3 Bandwidth"] = self.cache_data["L3"]["bandwidth"]

        # Memory Transistor, Die Size, Cost, and Bandwidth (same as before)
        if memory_type in ["GDDR5", "GDDR6", "GDDR6X"]:
            total_transistors += (
                self.memory_data[memory_type]["density"] * 1e9 * num_memory_controllers
            )
            total_die_size += (
                self.memory_data[memory_type]["density"] * 0.1 * num_memory_controllers
            )
            cost += (
                self.memory_data[memory_type]["cost_per_gb"]
                * num_memory_controllers
                * self.memory_data[memory_type]["density"]
                * 2  # Assuming 2 chips per controller
                / 8  # Converting from Gbit to GB
            )
            performance["Memory Bandwidth"] = (
                num_memory_controllers * 32 * memory_frequency
            )  # Bits per second
        elif memory_type in ["HBM2", "HBM2e"]:
            total_transistors += (
                self.memory_data[memory_type]["density"] * 1e9 * num_memory_controllers
            )
            total_die_size += (
                self.memory_data[memory_type]["density"] * 0.1 * num_memory_controllers
            )
            cost += (
                self.memory_data[memory_type]["cost_per_gb"]
                * num_memory_controllers
                * self.memory_data[memory_type]["density"]
                * 2  # Assuming 2 chips per controller
                / 8  # Converting from Gbit to GB
            )
            performance["Memory Bandwidth"] = (
                hbm_bus_width * memory_frequency
            )  # Bits per second

        # ROP calculations
        total_transistors += self.rop_data[selected_data["rop_variant"]]["transistors"] * num_rops
        performance["ROP Performance"] = self.rop_data[selected_data["rop_variant"]]["performance"]

        # TMU calculations
        total_transistors += self.tmu_data[selected_data["tmu_variant"]]["transistors"] * num_tmus
        performance["TMU Performance"] = self.tmu_data[selected_data["tmu_variant"]]["performance"]

        # Media Engine (same as before)
        media_engine = selected_data["media_engine"]
        if media_engine:
            performance["Media Engine"] = self.media_engine_data[media_engine]

        # ... Add calculations for other units (e.g., stream processors, etc.)

        return total_transistors, total_die_size, cost, power_consumption, performance

class GPUConfigurator(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("GPU Maker Simulation")
        self.gpu_data = GPUData()

        # Layout
        main_layout = QVBoxLayout()

        # Core Selection
        core_layout = QHBoxLayout()
        core_label = QLabel("Core Architecture:")
        self.core_combo = QComboBox()
        self.core_combo.addItems(self.gpu_data.core_data.keys())
        self.core_combo.currentIndexChanged.connect(self.update_core_version_options)
        core_layout.addWidget(core_label)
        core_layout.addWidget(self.core_combo)

        # --- Core Version Selection (Updated) ---
        self.shader_variant_combo = QComboBox()
        self.shader_variant_combo.addItems(self.gpu_data.shader_variants.keys())
        shader_variant_layout = QHBoxLayout()
        shader_variant_label = QLabel("Shader Variant:")
        shader_variant_layout.addWidget(shader_variant_label)
        shader_variant_layout.addWidget(self.shader_variant_combo)
        main_layout.addLayout(shader_variant_layout)  # Add to main layout


        # --- Number of Shaders ---
        shader_count_layout = QHBoxLayout()  # Create layout only once
        shader_count_label = QLabel("Number of Shaders (x1024):")
        self.shader_count_spin = QSpinBox()
        self.shader_count_spin.setValue(1)
        self.shader_count_spin.setMinimum(1)
        self.shader_count_spin.setMaximum(1000000)
        self.shader_count_spin.valueChanged.connect(self.round_shader_count)
        shader_count_layout.addWidget(shader_count_label)
        shader_count_layout.addWidget(self.shader_count_spin)
        main_layout.addLayout(shader_count_layout)

        # Number of RT Cores
        rt_core_count_layout = QHBoxLayout()
        rt_core_count_label = QLabel("Number of RT Cores:")
        self.rt_core_count_spin = QSpinBox()
        self.rt_core_count_spin.setValue(0)
        self.rt_core_count_spin.setMinimum(0)
        rt_core_count_layout.addWidget(rt_core_count_label)
        rt_core_count_layout.addWidget(self.rt_core_count_spin)

        # Number of Tensor Cores
        tensor_core_count_layout = QHBoxLayout()
        tensor_core_count_label = QLabel("Number of Tensor Cores:")
        self.tensor_core_count_spin = QSpinBox()
        self.tensor_core_count_spin.setValue(0)
        self.tensor_core_count_spin.setMinimum(0)
        tensor_core_count_layout.addWidget(tensor_core_count_label)
        tensor_core_count_layout.addWidget(self.tensor_core_count_spin)

        # Node Selection
        node_layout = QHBoxLayout()
        node_label = QLabel("TSMC Node:")
        self.node_combo = QComboBox()
        self.node_combo.addItems(["5nm", "4nm", "6nm"])
        node_layout.addWidget(node_label)
        node_layout.addWidget(self.node_combo)

        # Cache Selection
        cache_group = QGroupBox("Cache")
        cache_layout = QGridLayout()

        # L1 Cache Options
        self.l1_128_radio = QRadioButton("L1 Cache per 128 Shaders (KB):")
        self.l1_128_radio.setChecked(True)  # Default to per 128 shaders
        self.l1_64_radio = QRadioButton("L1 Cache per 64 Shaders (KB):")
        cache_layout.addWidget(self.l1_128_radio, 0, 0)
        cache_layout.addWidget(self.l1_64_radio, 1, 0)

        self.l1_cache_edit = QLineEdit()
        self.l1_cache_edit.setText("32")  # Default L1 cache size
        cache_layout.addWidget(self.l1_cache_edit, 0, 1) 

        # L2 Cache
        l2_cache_label = QLabel("L2 Cache (MB):")
        self.l2_cache_edit = QLineEdit()
        self.l2_cache_edit.setText("2")  # Default L2 cache size
        cache_layout.addWidget(l2_cache_label, 2, 0)
        cache_layout.addWidget(self.l2_cache_edit, 2, 1)

        # L3 Cache (Optional)
        l3_cache_label = QLabel("L3 Cache (MB, Optional):")
        self.l3_cache_edit = QLineEdit()
        cache_layout.addWidget(l3_cache_label, 3, 0)
        cache_layout.addWidget(self.l3_cache_edit, 3, 1)

        cache_group.setLayout(cache_layout)

        # Memory Selection
        memory_group = QGroupBox("Memory")
        memory_layout = QVBoxLayout()

        # Memory Type Selection
        self.memory_type_combo = QComboBox()
        self.memory_type_combo.addItems(
            ["GDDR5", "GDDR6", "GDDR6X", "HBM2", "HBM2e"]
        )
        self.memory_type_combo.currentIndexChanged.connect(
            self.update_memory_options
        )
        memory_layout.addWidget(self.memory_type_combo)

        # Memory Density or HBM Bus Width
        self.memory_density_combo = QComboBox()
        memory_layout.addWidget(self.memory_density_combo)

        # Number of Memory Controllers
        memory_controller_layout = QHBoxLayout()
        memory_controller_label = QLabel("Number of 32-bit Controllers:")
        self.memory_controller_spin = QSpinBox()
        self.memory_controller_spin.setValue(1)
        self.memory_controller_spin.setMinimum(1)
        memory_controller_layout.addWidget(memory_controller_label)
        memory_controller_layout.addWidget(self.memory_controller_spin)

        # Memory Frequency
        memory_frequency_layout = QHBoxLayout()
        memory_frequency_label = QLabel("Memory Frequency (GHz):")
        self.memory_frequency_edit = QLineEdit()
        self.memory_frequency_edit.setText("18")  # Default value
        memory_frequency_layout.addWidget(memory_frequency_label)
        memory_frequency_layout.addWidget(self.memory_frequency_edit)

        # HBM Bus Width
        hbm_bus_width_layout = QHBoxLayout()
        hbm_bus_width_label = QLabel("HBM Bus Width (bits):")
        self.hbm_bus_width_spin = QSpinBox()
        self.hbm_bus_width_spin.setValue(1024)
        self.hbm_bus_width_spin.setMinimum(1024)
        hbm_bus_width_layout.addWidget(hbm_bus_width_label)
        hbm_bus_width_layout.addWidget(self.hbm_bus_width_spin)

        # Add memory options to layout
        memory_layout.addLayout(memory_controller_layout)
        memory_layout.addLayout(memory_frequency_layout)
        memory_layout.addLayout(hbm_bus_width_layout)
        memory_group.setLayout(memory_layout)

        # --- ROP Selection ---
        rop_layout = QHBoxLayout()  # Create layout only once
        rop_label = QLabel("Number of ROPs:")
        self.rop_spin = QSpinBox()
        self.rop_spin.setValue(8)
        self.rop_spin.setMinimum(1)
        rop_layout.addWidget(rop_label)
        rop_layout.addWidget(self.rop_spin)
        main_layout.addLayout(rop_layout)  # Add layout to main_layout

        # --- TMU Selection ---
        tmu_layout = QHBoxLayout()  # Create layout only once
        tmu_label = QLabel("Number of TMUs:")
        self.tmu_spin = QSpinBox()
        self.tmu_spin.setValue(64)
        self.tmu_spin.setMinimum(1)
        tmu_layout.addWidget(tmu_label)
        tmu_layout.addWidget(self.tmu_spin)
        main_layout.addLayout(tmu_layout)  # Add layout to main_layout

        # Media Engine Selection
        media_engine_group = QGroupBox("Media Engine")
        media_engine_layout = QVBoxLayout()
        self.media_engine_combo = QComboBox()
        self.media_engine_combo.addItems(self.gpu_data.media_engine_data.keys())
        media_engine_layout.addWidget(self.media_engine_combo)

        # Add a label to display specs for the selected media engine
        self.media_engine_specs_label = QLabel("")
        media_engine_layout.addWidget(self.media_engine_specs_label)
        self.media_engine_combo.currentIndexChanged.connect(
            self.update_media_engine_specs
        )
        media_engine_group.setLayout(media_engine_layout)

        # Results Tab Widget
        self.results_tab = QTabWidget()
        main_layout.addWidget(self.results_tab)

        # Results Tab 1: Textual Results
        self.text_results_widget = QTextEdit()
        self.results_tab.addTab(self.text_results_widget, "Text Results")

        # Results Tab 2: Performance Graph
        self.figure, self.ax = plt.subplots()
        self.canvas = FigureCanvas(self.figure)
        self.results_tab.addTab(self.canvas, "Performance Graph")

        # Calculate Button
        calculate_button = QPushButton("Calculate")
        calculate_button.clicked.connect(self.calculate_gpu_stats)
        main_layout.addWidget(calculate_button)

        # Add layout elements to the main layout
        main_layout.addLayout(core_layout)
        main_layout.addLayout(shader_variant_layout)
        main_layout.addLayout(shader_count_layout)
        main_layout.addLayout(rt_core_count_layout)
        main_layout.addLayout(tensor_core_count_layout)
        main_layout.addLayout(node_layout)
        main_layout.addWidget(cache_group)
        main_layout.addWidget(memory_group)
        main_layout.addLayout(rop_layout)
        main_layout.addLayout(tmu_layout)
        main_layout.addWidget(media_engine_group)

        # Set layout for the window
        self.setLayout(main_layout)

        # Initial updates
        self.update_core_version_options()
        self.update_memory_options()
        self.update_media_engine_specs()

    def round_shader_count(self):
        """Round the shader count to the nearest multiple of 128."""
        current_value = self.shader_count_spin.value() * 1024  # Multiply by 1024
        rounded_value = round(current_value / 128) * 128  # Round to nearest 128 multiple
        self.shader_count_spin.setValue(int(rounded_value / 1024))  # Convert back to int and divide by 1024

    # Function to update core version options based on selected architecture
    def update_core_version_options(self):
        selected_architecture = self.core_combo.currentText()
        if selected_architecture in self.gpu_data.core_data:
            self.shader_variant_combo.clear()
            self.shader_variant_combo.addItems(
                self.gpu_data.shader_variants.keys()
            )
        else:
            self.core_version_combo.clear()

    def update_memory_options(self):
        selected_memory_type = self.memory_type_combo.currentText()
        if selected_memory_type in ["GDDR5", "GDDR6", "GDDR6X"]:
            self.memory_density_combo.clear()
            self.memory_density_combo.addItems(
                [f"{self.gpu_data.memory_data[selected_memory_type]['density']} Gbit"]  # Fix: Put density inside []
            )
            self.hbm_bus_width_spin.hide()
        elif selected_memory_type in ["HBM2", "HBM2e"]:
            self.memory_density_combo.hide()
            self.hbm_bus_width_spin.show()
        else:
            self.memory_density_combo.hide()
            self.hbm_bus_width_spin.hide()

    def update_media_engine_specs(self):
        selected_media_engine = self.media_engine_combo.currentText()
        if selected_media_engine in self.gpu_data.media_engine_data:
            specs = self.gpu_data.media_engine_data[selected_media_engine]
            self.media_engine_specs_label.setText(
                f"Streams: {specs['streams']}\nResolution: {specs['resolution']}\nFPS: {specs['fps']}\nFeatures: {specs['features']}"
            )
        else:
            self.media_engine_specs_label.setText("")

    # Function to calculate and display GPU stats
    def calculate_gpu_stats(self):
        selected_core = self.core_combo.currentText()
        selected_core_version = self.shader_variant_combo.currentText()
        num_shaders = self.shader_count_spin.value() * 1024
        num_rt_cores = self.rt_core_count_spin.value()
        num_tensor_cores = self.tensor_core_count_spin.value()
        selected_node = self.node_combo.currentText()
        num_memory_controllers = self.memory_controller_spin.value()
        selected_memory_type = self.memory_type_combo.currentText()
        selected_memory_density = self.memory_density_combo.currentText()
        hbm_bus_width = self.hbm_bus_width_spin.value()
        memory_frequency = self.memory_frequency_edit.text()
        num_rops = self.rop_spin.value()
        num_tmus = self.tmu_spin.value()

        # --- Get shader values ---
        shader_variant = self.shader_variant_combo.currentText()
        num_shaders = self.shader_count_spin.value() * 1024  # Multiply by 1024

        # Cache Values
        if self.l1_128_radio.isChecked():
            l1_cache_per_shader = int(self.l1_cache_edit.text()) / 128
        else:
            l1_cache_per_shader = int(self.l1_cache_edit.text()) / 64
        l2_cache_size = int(self.l2_cache_edit.text())
        l3_cache_size = int(self.l3_cache_edit.text()) if self.l3_cache_edit.text() else 0

        selected_data = {
            "core": selected_core,
            "core_version": selected_core_version,  # This is the core version
            "shader_variant": shader_variant,  # This is the shader variant
            "num_shaders": num_shaders,        # Use corrected shader count
            "num_rt_cores": num_rt_cores,
            "num_tensor_cores": num_tensor_cores,
            "node": selected_node,
            "memory_type": selected_memory_type,
            "memory_density": int(selected_memory_density.split()[0]),
            "hbm_bus_width": hbm_bus_width,
            "memory_frequency": memory_frequency,
            "num_memory_controllers": num_memory_controllers,
            "l1_cache_per_shader": l1_cache_per_shader,
            "l2_cache_size": l2_cache_size,
            "l3_cache_size": l3_cache_size,
            "num_rops": num_rops,
            "num_tmus": num_tmus
        }

        # --- Core Calculations ---
        num_cores = num_shaders // self.gpu_data.core_data[selected_core][selected_core_version]["shaders_per_core"]
        total_transistors += self.gpu_data.core_data[selected_core][selected_core_version]["transistors_per_core"] * num_cores
        total_die_size += self.gpu_data.core_data[selected_core][selected_core_version]["die_size_per_core"] * num_cores

        # --- Clock Speed Calculation ---
        clock_min, clock_max = self.gpu_data.core_data[selected_core][selected_core_version]["nodes"][selected_node]["clock_range"]
        clock_speed = clock_min + (clock_max - clock_min) * selected_data[shader_variant]
        performance["Clock Speed"] = f"{clock_speed:.2f} GHz"

        selected_data["media_engine"] = self.media_engine_combo.currentText()

        total_transistors, total_die_size, cost, power_consumption, performance = self.gpu_data.calculate_gpu_stats(
            selected_data
        )

        # Display Textual Results
        self.text_results_widget.setText(
            f"Transistors: {total_transistors:.2e}\n"
            f"Die Size: {total_die_size} mm²\n"
            f"Cost: ${cost:.2f}\n"
            f"Power Consumption: {power_consumption:.2f}W\n"
            f"Performance:\n"
            f"{performance}"
        )

        # Update Performance Graph
        self.ax.clear()  # Clear previous data
        labels = performance.keys()
        values = [value if isinstance(value, (int, float)) else None for value in performance.values()]
        self.ax.bar(labels, values)
        self.ax.set_xlabel("Metrics")
        self.ax.set_ylabel("Values")
        self.ax.set_title("GPU Performance Metrics")
        self.canvas.draw()  # Redraw the canvas

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = GPUConfigurator()
    window.show()
    sys.exit(app.exec())