import tkinter as tk
from tkinter import ttk, messagebox
import serial
import serial.tools.list_ports
import threading
import time
import datetime
import os
import re

class DualScaleRS232Reader:
    def __init__(self, root):
        self.root = root
        self.root.title("Scale RS232 Reader - Hiển thị 2 cân đồng thời")
        self.root.geometry("1600x900")  # Tăng kích thước
        self.root.minsize(1400, 800)
        
        # Màu sắc
        self.colors = {
            'white': '#FFFFFF',
            'light_gray': '#E8E8E8', 
            'light_blue': '#90CAF9',
            'light_green': '#A5D6A7',
            'text_dark': '#2E2E2E',
            'text_light': '#666666',
            'border': '#CCCCCC'
        }
        
        self.root.configure(bg=self.colors['white'])
        
        # Thiết lập styles
        self.setup_styles()
        
        # Biến lưu trữ kết nối và dữ liệu
        self.serial_connections = {}
        self.running = {}
        self.data_widgets = {}
        
        # Tạo thư mục lịch sử
        self.history_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "history")
        os.makedirs(self.history_dir, exist_ok=True)
        
        # Tạo giao diện
        self.create_main_interface()
        
        # Cập nhật danh sách cổng COM
        self.update_com_ports()
    
    def setup_styles(self):
        """Thiết lập styles cho giao diện"""
        style = ttk.Style()
        style.theme_use('clam')
        
        # Frame styles
        style.configure("Main.TFrame", 
                       background=self.colors['white'])
        
        style.configure("Header.TFrame", 
                       background=self.colors['light_blue'])
        
        style.configure("Control.TFrame", 
                       background=self.colors['light_gray'])
        
        # Label styles
        style.configure("Title.TLabel", 
                       background=self.colors['light_blue'],
                       foreground=self.colors['text_dark'],
                       font=("Segoe UI", 18, "bold"))
        
        style.configure("Header.TLabel", 
                       background=self.colors['light_gray'],
                       foreground=self.colors['text_dark'],
                       font=("Segoe UI", 12, "bold"))
        
        style.configure("Info.TLabel", 
                       background=self.colors['white'],
                       foreground=self.colors['text_light'],
                       font=("Segoe UI", 11))
        
        # Button styles
        style.configure("Action.TButton", 
                       font=("Segoe UI", 9, "bold"),
                       padding=(8, 4))
        
        style.configure("Connect.TButton", 
                       font=("Segoe UI", 10, "bold"),
                       padding=(10, 6))
        
        # Combobox style
        style.configure("Compact.TCombobox",
                       font=("Segoe UI", 9))
    
    def create_main_interface(self):
        """Tạo giao diện chính"""
        # Container chính
        main_container = ttk.Frame(self.root, style="Main.TFrame", padding="15")
        main_container.pack(fill=tk.BOTH, expand=True)
        
        # Header
        self.create_header(main_container)
        
        # Control panel compact
        self.create_control_panel(main_container)
        
        # Display area cho 2 cân (chiếm phần lớn màn hình)
        self.create_dual_display_area(main_container)
        
        # Status bar
        self.create_status_bar(main_container)
    
    def create_header(self, parent):
        """Tạo header"""
        header_frame = tk.Frame(parent, bg=self.colors['light_blue'], height=50)
        header_frame.pack(fill=tk.X, pady=(0, 10))
        header_frame.pack_propagate(False)
        
        title_label = tk.Label(header_frame, 
                              text="Scale RS232 Reader - Hiển thị đồng thời 2 cân",
                              font=("Segoe UI", 18, "bold"),
                              fg=self.colors['text_dark'], 
                              bg=self.colors['light_blue'])
        title_label.pack(side=tk.LEFT, padx=15, pady=10)
        
        version_label = tk.Label(header_frame,
                               text="v2.2 - Ultra Large Display",
                               font=("Segoe UI", 11),
                               fg=self.colors['text_light'], 
                               bg=self.colors['light_blue'])
        version_label.pack(side=tk.RIGHT, padx=15, pady=12)
    
    def create_control_panel(self, parent):
        """Tạo panel điều khiển compact"""
        control_frame = tk.Frame(parent, bg=self.colors['light_gray'], 
                               relief="solid", bd=1, height=130)  # Đủ cao cho 2 cân
        control_frame.pack(fill=tk.X, pady=(0, 10))
        control_frame.pack_propagate(False)  # Cố định chiều cao
        
        # Main container với grid layout
        main_container = tk.Frame(control_frame, bg=self.colors['light_gray'])
        main_container.pack(fill=tk.BOTH, padx=10, pady=6)
        
        # Configure grid weights
        main_container.grid_columnconfigure(0, weight=3)  # Controls area
        main_container.grid_columnconfigure(1, weight=1)  # Actions area
        
        # Left side - Scale controls
        controls_frame = tk.Frame(main_container, bg=self.colors['light_gray'])
        controls_frame.grid(row=0, column=0, sticky="nsew", padx=(0, 15))
        
        # Title compact
        title_label = tk.Label(controls_frame, text="QUẢN LÝ KẾT NỐI",
                             font=("Segoe UI", 10, "bold"),
                             fg=self.colors['text_dark'], bg=self.colors['light_gray'])
        title_label.pack(pady=(0, 5))
        
        # Controls container
        controls_container = tk.Frame(controls_frame, bg=self.colors['light_gray'])
        controls_container.pack(fill=tk.X)
        
        # Cân 1
        self.create_scale_controls("Scale_1", "CÂN 1", controls_container, 0)
        
        # Cân 2  
        self.create_scale_controls("Scale_2", "CÂN 2", controls_container, 1)
        
        # Right side - Action buttons
        actions_frame = tk.Frame(main_container, bg=self.colors['light_gray'])
        actions_frame.grid(row=0, column=1, sticky="nsew")
        
        # Actions title
        actions_title = tk.Label(actions_frame, text="THAO TÁC",
                               font=("Segoe UI", 10, "bold"),
                               fg=self.colors['text_dark'], bg=self.colors['light_gray'])
        actions_title.pack(pady=(0, 5))
        
        # Action buttons - vertical layout
        ttk.Button(actions_frame, text="Làm mới cổng COM", 
                  command=self.update_com_ports, style="Action.TButton").pack(fill=tk.X, pady=1)
        
        ttk.Button(actions_frame, text="Xem lịch sử", 
                  command=self.view_history, style="Action.TButton").pack(fill=tk.X, pady=1)
        
        ttk.Button(actions_frame, text="Ngắt tất cả", 
                  command=self.disconnect_all, style="Action.TButton").pack(fill=tk.X, pady=1)
    
    def create_scale_controls(self, scale_id, scale_name, parent, row):
        """Tạo controls compact cho một cân"""
        # Scale name
        name_label = tk.Label(parent, text=f"{scale_name}:", 
                            font=("Segoe UI", 10, "bold"),
                            fg=self.colors['text_dark'], 
                            bg=self.colors['light_gray'])
        name_label.grid(row=row, column=0, sticky="w", padx=(0, 10), pady=3)
        
        # COM Port
        com_label = tk.Label(parent, text="COM:", 
                           font=("Segoe UI", 9),
                           fg=self.colors['text_light'], 
                           bg=self.colors['light_gray'])
        com_label.grid(row=row, column=1, sticky="w", padx=(0, 5), pady=3)
        
        com_var = tk.StringVar()
        com_combo = ttk.Combobox(parent, textvariable=com_var, 
                               width=12, style="Compact.TCombobox", state="readonly")
        com_combo.grid(row=row, column=2, padx=(0, 10), pady=3)
        
        # Baudrate
        baud_label = tk.Label(parent, text="Baud:", 
                            font=("Segoe UI", 9),
                            fg=self.colors['text_light'], 
                            bg=self.colors['light_gray'])
        baud_label.grid(row=row, column=3, sticky="w", padx=(0, 5), pady=3)
        
        baud_var = tk.StringVar(value="9600")
        baud_combo = ttk.Combobox(parent, textvariable=baud_var,
                                values=["1200", "2400", "4800", "9600", "19200", "38400"],
                                width=8, style="Compact.TCombobox", state="readonly")
        baud_combo.grid(row=row, column=4, padx=(0, 10), pady=3)
        
        # Connect button
        connect_btn = ttk.Button(parent, text="Kết nối", 
                               command=lambda: self.toggle_connection(scale_id),
                               style="Connect.TButton")
        connect_btn.grid(row=row, column=5, pady=3)
        
        # Lưu controls
        if not hasattr(self, 'scale_controls'):
            self.scale_controls = {}
        
        self.scale_controls[scale_id] = {
            'scale_name': scale_name,
            'com_var': com_var,
            'com_combo': com_combo,
            'baud_var': baud_var,
            'connect_btn': connect_btn
        }
    
    def create_dual_display_area(self, parent):
        """Tạo khu vực hiển thị 2 cân đồng thời - tối đa hóa"""
        display_frame = tk.Frame(parent, bg=self.colors['white'])
        display_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
        
        # Configure grid weights
        display_frame.grid_columnconfigure(0, weight=1)
        display_frame.grid_columnconfigure(1, weight=1)
        display_frame.grid_rowconfigure(0, weight=1)
        
        # Cân 1
        self.create_scale_display("Scale_1", "CÂN 1", display_frame, 0, 0)
                
        # Cân 2
        self.create_scale_display("Scale_2", "CÂN 2", display_frame, 0, 1)
    
    def create_scale_display(self, scale_id, scale_name, parent, row, col):
        """Tạo màn hình hiển thị cho một cân - tối đa hóa số"""
        # Main container cho cân
        scale_frame = tk.Frame(parent, bg=self.colors['white'], 
                             relief="solid", bd=1, highlightbackground=self.colors['border'])
        scale_frame.grid(row=row, column=col, sticky="nsew", padx=3, pady=3)
        
        # Header compact
        header_frame = tk.Frame(scale_frame, bg=self.colors['light_green'], height=40)
        header_frame.pack(fill=tk.X)
        header_frame.pack_propagate(False)
        
        header_label = tk.Label(header_frame, text=scale_name,
                              font=("Segoe UI", 14, "bold"),
                              fg=self.colors['text_dark'], bg=self.colors['light_green'])
        header_label.pack(expand=True, pady=8)
        
        # Status compact
        status_frame = tk.Frame(scale_frame, bg=self.colors['white'], height=30)
        status_frame.pack(fill=tk.X)
        status_frame.pack_propagate(False)
        
        status_var = tk.StringVar(value="Chưa kết nối")
        status_label = tk.Label(status_frame, textvariable=status_var,
                              font=("Segoe UI", 10),
                              fg=self.colors['text_light'], bg=self.colors['white'])
        status_label.pack(pady=5)
        
        # Weight display - SIÊU LỚN (tối đa hóa)
        weight_frame = tk.Frame(scale_frame, bg=self.colors['white'])
        weight_frame.pack(fill=tk.BOTH, expand=True, padx=15, pady=15)
        
        weight_var = tk.StringVar(value="0.000")
        weight_label = tk.Label(weight_frame, textvariable=weight_var,
                              font=("Arial", 150, "bold"),  # Font siêu lớn để nhìn từ xa
                              fg=self.colors['text_dark'], bg=self.colors['white'])
        weight_label.pack(expand=True)
        
        # Unit
        unit_label = tk.Label(weight_frame, text="kg",
                            font=("Segoe UI", 32, "bold"),  # Tăng size đơn vị
                            fg=self.colors['text_light'], bg=self.colors['white'])
        unit_label.pack()
        
        # Statistics compact
        stats_frame = tk.Frame(scale_frame, bg=self.colors['light_gray'], height=60)
        stats_frame.pack(fill=tk.X, padx=8, pady=(0, 8))
        stats_frame.pack_propagate(False)
        
        # Stats grid
        stats_grid = tk.Frame(stats_frame, bg=self.colors['light_gray'])
        stats_grid.pack(expand=True, pady=8)
        
        stats_vars = {}
        stat_items = [
            ("Đọc:", "count", 0, 0),
            ("Max:", "max", 0, 2),
            ("Min:", "min", 1, 0),
            ("Avg:", "avg", 1, 2)
        ]
        
        for label_text, key, row, col in stat_items:
            tk.Label(stats_grid, text=label_text, 
                   font=("Segoe UI", 8, "bold"),
                   fg=self.colors['text_dark'], bg=self.colors['light_gray']).grid(
                row=row, column=col, sticky="w", padx=(0, 3), pady=1)
            
            stats_vars[key] = tk.StringVar(value="0")
            tk.Label(stats_grid, textvariable=stats_vars[key], 
                   font=("Segoe UI", 8),
                   fg=self.colors['text_light'], bg=self.colors['light_gray']).grid(
                row=row, column=col+1, sticky="w", padx=(0, 10), pady=1)
        
        # Lưu widgets
        self.data_widgets[scale_id] = {
            'status_var': status_var,
            'weight_var': weight_var,
            'weight_label': weight_label,
            'stats_vars': stats_vars,
            'values': [],
            'scale_name': scale_name
        }
    
    def create_status_bar(self, parent):
        """Tạo status bar"""
        status_frame = tk.Frame(parent, bg=self.colors['light_gray'], height=30)
        status_frame.pack(fill=tk.X)
        status_frame.pack_propagate(False)
        
        self.status_var = tk.StringVar(value="Sẵn sàng - Chọn cổng COM và kết nối")
        self.status_label = tk.Label(status_frame, textvariable=self.status_var,
                                   font=("Segoe UI", 9),
                                   fg=self.colors['text_dark'], 
                                   bg=self.colors['light_gray'])
        self.status_label.pack(side=tk.LEFT, padx=10, pady=6)
        
        # Time display
        self.time_var = tk.StringVar()
        self.time_label = tk.Label(status_frame, textvariable=self.time_var,
                                 font=("Segoe UI", 9),
                                 fg=self.colors['text_light'], 
                                 bg=self.colors['light_gray'])
        self.time_label.pack(side=tk.RIGHT, padx=10, pady=6)
        
        self.update_time()
    
    def update_time(self):
        """Cập nhật thời gian"""
        current_time = datetime.datetime.now().strftime("%H:%M:%S - %d/%m/%Y")
        self.time_var.set(current_time)
        self.root.after(1000, self.update_time)
    
    def update_com_ports(self):
        """Cập nhật danh sách cổng COM"""
        ports = [port.device for port in serial.tools.list_ports.comports()]
        
        for scale_id, controls in self.scale_controls.items():
            current_value = controls['com_var'].get()
            controls['com_combo']['values'] = ports
            
            if current_value in ports:
                controls['com_var'].set(current_value)
            elif ports:
                controls['com_combo'].current(0)
        
        self.status_var.set(f"Đã tìm thấy {len(ports)} cổng COM")
    
    def toggle_connection(self, scale_id):
        """Kết nối/ngắt kết nối cân"""
        controls = self.scale_controls[scale_id]
        port = controls['com_var'].get()
        
        if not port:
            messagebox.showerror("Lỗi", f"Vui lòng chọn cổng COM cho {controls['scale_name']}")
            return
        
        if scale_id in self.serial_connections:
            self.disconnect_scale(scale_id)
        else:
            self.connect_scale(scale_id)
    
    def connect_scale(self, scale_id):
        """Kết nối cân"""
        controls = self.scale_controls[scale_id]
        widgets = self.data_widgets[scale_id]
        port = controls['com_var'].get()
        
        try:
            controls['connect_btn'].config(text="Đang kết nối...", state="disabled")
            widgets['status_var'].set(f"Đang kết nối với {port}...")
            self.root.update()
            
            # Tạo kết nối serial
            baudrate = int(controls['baud_var'].get())
            ser = serial.Serial(port, baudrate, timeout=1)
            self.serial_connections[scale_id] = ser
            
            # Tạo file lịch sử
            timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
            history_file = os.path.join(self.history_dir, f"{scale_id}_{port.replace('/', '_')}_{timestamp}.csv")
            
            with open(history_file, 'w', encoding='utf-8') as f:
                f.write("Timestamp,Raw Data,Weight Value (kg)\n")
            
            widgets['history_file'] = history_file
            
            # Bắt đầu thread đọc dữ liệu
            self.running[scale_id] = True
            thread = threading.Thread(target=self.read_data, args=(scale_id,), daemon=True)
            thread.start()
            
            # Cập nhật UI
            controls['connect_btn'].config(text="Ngắt kết nối", state="normal")
            widgets['status_var'].set(f"Đã kết nối với {port}")
            self.status_var.set(f"{controls['scale_name']} đã kết nối với {port}")
            
        except Exception as e:
            messagebox.showerror("Lỗi kết nối", f"Không thể kết nối {controls['scale_name']} với {port}:\n{str(e)}")
            controls['connect_btn'].config(text="Kết nối", state="normal")
            widgets['status_var'].set("Lỗi kết nối")
    
    def disconnect_scale(self, scale_id):
        """Ngắt kết nối cân"""
        controls = self.scale_controls[scale_id]
        widgets = self.data_widgets[scale_id]
        
        # Dừng thread
        if scale_id in self.running:
            self.running[scale_id] = False
        
        time.sleep(0.3)
        
        # Đóng serial connection
        try:
            if scale_id in self.serial_connections:
                self.serial_connections[scale_id].close()
                del self.serial_connections[scale_id]
        except:
            pass
        
        # Reset UI
        controls['connect_btn'].config(text="Kết nối")
        widgets['status_var'].set("Chưa kết nối")
        widgets['weight_var'].set("0.000")
        widgets['weight_label'].config(fg=self.colors['text_dark'])
        
        # Reset stats
        for key in widgets['stats_vars']:
            widgets['stats_vars'][key].set("0")
        
        widgets['values'] = []
        
        self.status_var.set(f"Đã ngắt kết nối {controls['scale_name']}")
    
    def read_data(self, scale_id):
        """Thread đọc dữ liệu từ cân"""
        ser = self.serial_connections[scale_id]
        
        while self.running.get(scale_id, False):
            try:
                if ser.in_waiting > 0:
                    data = ser.readline().decode('utf-8', errors='ignore').strip()
                    
                    if data:
                        try:
                            value = self.parse_weight(data)
                            self.update_display(scale_id, value, data)
                        except Exception as e:
                            print(f"Lỗi xử lý dữ liệu {scale_id}: {str(e)}")
                
                time.sleep(0.1)
                
            except Exception as e:
                print(f"Lỗi đọc dữ liệu {scale_id}: {str(e)}")
                self.running[scale_id] = False
                break
    
    def parse_weight(self, data):
        """Parse giá trị trọng lượng từ dữ liệu thô"""
        weight_match = re.search(r'\d+\s+(\d+\.\d+)', data)
        
        if weight_match:
            return float(weight_match.group(1))
        else:
            wt_kg_match = re.search(r'WT/kg\s+\+?(\d+\.\d+)', data)
            if wt_kg_match:
                return float(wt_kg_match.group(1))
            else:
                all_numbers = re.findall(r'[-+]?\d*\.\d+|\d+', data)
                if len(all_numbers) >= 2:
                    return float(all_numbers[1])
                else:
                    number_match = re.search(r'[-+]?\d*\.\d+|\d+', data)
                    if number_match:
                        return float(number_match.group())
                    else:
                        return 0.0
    
    def update_display(self, scale_id, value, raw_data):
        """Cập nhật hiển thị cho cân"""
        widgets = self.data_widgets[scale_id]
        
        # Cập nhật giá trị trọng lượng
        widgets['weight_var'].set(f"{value:.3f}")
        
        # Đổi màu theo giá trị
        if value > 0:
            widgets['weight_label'].config(fg='#2E7D32')  # Xanh
        elif value < 0:
            widgets['weight_label'].config(fg='#C62828')  # Đỏ
        else:
            widgets['weight_label'].config(fg='#F57C00')  # Cam
        
        # Cập nhật thống kê
        values = widgets['values']
        values.append(value)
        
        if len(values) > 1000:
            values = values[-1000:]
        
        stats = widgets['stats_vars']
        stats['count'].set(str(len(values)))
        stats['max'].set(f"{max(values):.3f}")
        stats['min'].set(f"{min(values):.3f}")
        stats['avg'].set(f"{sum(values)/len(values):.3f}")
        
        # Lưu vào file
        try:
            timestamp = datetime.datetime.now().strftime("%H:%M:%S")
            with open(widgets['history_file'], 'a', encoding='utf-8') as f:
                f.write(f"{timestamp},{raw_data},{value:.3f}\n")
        except Exception as e:
            print(f"Lỗi ghi file: {str(e)}")
        
        # Cập nhật status
        scale_name = widgets['scale_name']
        self.status_var.set(f"{scale_name}: {value:.3f} kg | Tổng: {len(values)} lần đọc")
    
    def view_history(self):
        """Mở thư mục lịch sử"""
        try:
            if os.name == 'nt':
                os.startfile(self.history_dir)
            else:
                os.system(f'xdg-open "{self.history_dir}"')
            
            self.status_var.set(f"Đã mở thư mục lịch sử: {self.history_dir}")
        except Exception as e:
            messagebox.showerror("Lỗi", f"Không thể mở thư mục lịch sử:\n{str(e)}")
    
    def disconnect_all(self):
        """Ngắt tất cả kết nối"""
        if messagebox.askyesno("Xác nhận", "Bạn có chắc muốn ngắt tất cả kết nối?"):
            for scale_id in list(self.serial_connections.keys()):
                self.disconnect_scale(scale_id)
            
            self.status_var.set("Đã ngắt tất cả kết nối")

if __name__ == "__main__":
    root = tk.Tk()
    app = DualScaleRS232Reader(root)
    root.mainloop()
