import sys
import serial
import serial.tools.list_ports
from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, 
                            QWidget, QLabel, QPushButton, QComboBox, QDoubleSpinBox,
                            QGroupBox, QStatusBar, QMessageBox)
from PyQt6.QtCore import QTimer, Qt
from PyQt6.QtGui import QFont

class PHControllerGUI(QMainWindow):
    def __init__(self):
        super().__init__()
        self.serial_connection = None
        self.setWindowTitle("Arduino pH Controller")
        self.setGeometry(100, 100, 600, 400)
        
        # Central Widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        
        # Connection Group
        connection_group = QGroupBox("Verbindung")
        connection_layout = QHBoxLayout()
        
        self.port_combo = QComboBox()
        self.refresh_ports()
        
        self.connect_btn = QPushButton("Verbinden")
        self.connect_btn.clicked.connect(self.toggle_connection)
        
        connection_layout.addWidget(QLabel("Port:"))
        connection_layout.addWidget(self.port_combo, 1)
        connection_layout.addWidget(self.connect_btn)
        connection_group.setLayout(connection_layout)
        
        # Status Group
        status_group = QGroupBox("Status")
        status_layout = QHBoxLayout()
        
        self.ph_label = QLabel("pH: --")
        self.ph_label.setFont(QFont('Arial', 24))
        self.ph_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        self.pump_status = QLabel("Pumpe: Aus")
        self.pump_status.setFont(QFont('Arial', 16))
        self.pump_status.setAlignment(Qt.AlignmentFlag.AlignCenter)
        
        status_layout.addWidget(self.ph_label)
        status_layout.addWidget(self.pump_status)
        status_group.setLayout(status_layout)
        
        # Control Group
        control_group = QGroupBox("Steuerung")
        control_layout = QVBoxLayout()
        
        # Pump Control
        pump_control_layout = QHBoxLayout()
        self.pump_on_btn = QPushButton("Pumpe Ein")
        self.pump_on_btn.clicked.connect(lambda: self.send_pump_command(1))
        self.pump_off_btn = QPushButton("Pumpe Aus")
        self.pump_off_btn.clicked.connect(lambda: self.send_pump_command(0))
        
        pump_control_layout.addWidget(self.pump_on_btn)
        pump_control_layout.addWidget(self.pump_off_btn)
        
        # Tube Selection
        tube_layout = QHBoxLayout()
        self.tube_combo = QComboBox()
        self.tube_combo.addItems([
            "0.13 mm", "0.19 mm", "0.25 mm", "0.38 mm", "0.44 mm", 
            "0.51 mm", "0.57 mm", "0.64 mm", "0.76 mm", "0.89 mm", 
            "0.95 mm", "1.02 mm", "1.09 mm", "1.14 mm", "1.22 mm", 
            "1.30 mm", "1.42 mm", "1.52 mm", "1.65 mm", "1.75 mm", 
            "1.85 mm", "2.06 mm", "2.29 mm", "2.54 mm", "2.79 mm", "3.17 mm"
        ])
        self.tube_combo.currentIndexChanged.connect(self.send_tube_command)
        
        tube_layout.addWidget(QLabel("Schlauch:"))
        tube_layout.addWidget(self.tube_combo, 1)
        
        # Calibration
        calibration_layout = QHBoxLayout()
        self.cal_ph4_btn = QPushButton("Kalibrieren pH4")
        self.cal_ph7_btn = QPushButton("Kalibrieren pH7")
        self.cal_ph4_btn.clicked.connect(lambda: self.send_calibration_command(4))
        self.cal_ph7_btn.clicked.connect(lambda: self.send_calibration_command(7))
        
        calibration_layout.addWidget(self.cal_ph4_btn)
        calibration_layout.addWidget(self.cal_ph7_btn)
        
        # Auto Mode
        auto_mode_layout = QHBoxLayout()
        self.volume_spin = QDoubleSpinBox()
        self.volume_spin.setRange(0.1, 1000)
        self.volume_spin.setValue(10)
        self.volume_spin.setSuffix(" ml")
        
        self.auto_dose_btn = QPushButton("Automatisch dosieren")
        self.auto_dose_btn.clicked.connect(self.start_auto_dose)
        
        auto_mode_layout.addWidget(QLabel("Menge:"))
        auto_mode_layout.addWidget(self.volume_spin)
        auto_mode_layout.addWidget(self.auto_dose_btn)
        
        # Add to control layout
        control_layout.addLayout(pump_control_layout)
        control_layout.addLayout(tube_layout)
        control_layout.addLayout(calibration_layout)
        control_layout.addLayout(auto_mode_layout)
        control_group.setLayout(control_layout)
        
        # Add groups to main layout
        main_layout.addWidget(connection_group)
        main_layout.addWidget(status_group)
        main_layout.addWidget(control_group)
        
        # Status Bar
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("Bereit")
        
        # Timer for reading data
        self.read_timer = QTimer()
        self.read_timer.timeout.connect(self.read_serial_data)
        self.read_timer.start(1000)  # Update every second
        
        # Enable/disable controls based on connection
        self.set_controls_enabled(False)
    
    def refresh_ports(self):
        self.port_combo.clear()
        ports = serial.tools.list_ports.comports()
        for port in ports:
            self.port_combo.addItem(port.device)
    
    def toggle_connection(self):
        if self.serial_connection and self.serial_connection.is_open:
            self.close_connection()
        else:
            self.open_connection()
    
    def open_connection(self):
        port = self.port_combo.currentText()
        if not port:
            QMessageBox.critical(self, "Fehler", "Kein Port ausgewählt!")
            return
            
        try:
            self.serial_connection = serial.Serial(port, 9600, timeout=1)
            self.connect_btn.setText("Trennen")
            self.status_bar.showMessage(f"Verbunden mit {port}")
            self.set_controls_enabled(True)
            
            # Request initial data
            self.send_command("<114>")  # Request pH value
            self.send_command("<113>")  # Request tube size
        except Exception as e:
            QMessageBox.critical(self, "Fehler", f"Verbindungsfehler: {str(e)}")
    
    def close_connection(self):
        if self.serial_connection and self.serial_connection.is_open:
            self.serial_connection.close()
        self.serial_connection = None
        self.connect_btn.setText("Verbinden")
        self.status_bar.showMessage("Getrennt")
        self.set_controls_enabled(False)
        self.ph_label.setText("pH: --")
        self.pump_status.setText("Pumpe: --")
    
    def set_controls_enabled(self, enabled):
        self.pump_on_btn.setEnabled(enabled)
        self.pump_off_btn.setEnabled(enabled)
        self.tube_combo.setEnabled(enabled)
        self.cal_ph4_btn.setEnabled(enabled)
        self.cal_ph7_btn.setEnabled(enabled)
        self.auto_dose_btn.setEnabled(enabled)
        self.volume_spin.setEnabled(enabled)
    
    def send_command(self, command):
        if self.serial_connection and self.serial_connection.is_open:
            try:
                self.serial_connection.write(command.encode('utf-8'))
            except Exception as e:
                self.status_bar.showMessage(f"Fehler beim Senden: {str(e)}")
    
    def send_pump_command(self, state):
        if state == 1:
            self.send_command("<111>")  # Pump on
            self.pump_status.setText("Pumpe: Ein")
        else:
            self.send_command("<112>")  # Pump off
            self.pump_status.setText("Pumpe: Aus")
    
    def send_tube_command(self, index):
        # The Arduino expects the tube index (0-25)
        self.send_command(f"<11{index+3}>")  # <11[3-28]>
    
    def send_calibration_command(self, ph):
        if ph == 4:
            self.send_command("<114>")  # Start pH4 calibration
            QMessageBox.information(self, "Kalibrierung", 
                                   "pH4 Kalibrierung gestartet. Sensor in pH4-Lösung tauchen und warten bis der Wert stabil ist.")
        else:
            self.send_command("<115>")  # Start pH7 calibration
            QMessageBox.information(self, "Kalibrierung", 
                                   "pH7 Kalibrierung gestartet. Sensor in pH7-Lösung tauchen und warten bis der Wert stabil ist.")
    
    def start_auto_dose(self):
        volume = self.volume_spin.value()
        # Calculate pump run time based on tube size and volume
        # This would need to be implemented based on your specific setup
        QMessageBox.information(self, "Automatische Dosierung", 
                              f"Es werden {volume} ml dosiert. Bitte warten...")
        # Implementation would need to send appropriate commands to Arduino
    
    def read_serial_data(self):
        if not self.serial_connection or not self.serial_connection.is_open:
            return
            
        try:
            while self.serial_connection.in_waiting:
                line = self.serial_connection.readline().decode('utf-8').strip()
                if line.startswith('<') and line.endswith('>'):
                    data = line[1:-1]
                    if data.startswith('#'):  # pH value
                        try:
                            ph_value = float(data[1:])
                            self.ph_label.setText(f"pH: {ph_value:.2f}")
                        except ValueError:
                            pass
                    elif data.startswith('P'):  # Pump status
                        pump_state = data[1:]
                        self.pump_status.setText(f"Pumpe: {'Ein' if pump_state == '1' else 'Aus'}")
                    elif data.startswith('T'):  # Tube index
                        tube_index = int(data[1:])
                        self.tube_combo.setCurrentIndex(tube_index)
        except Exception as e:
            self.status_bar.showMessage(f"Lesefehler: {str(e)}")
    
    def closeEvent(self, event):
        self.close_connection()
        event.accept()

def main():
    app = QApplication(sys.argv)
    window = PHControllerGUI()
    window.show()
    sys.exit(app.exec())

if __name__ == "__main__":
    main()