# mesa_movimento.py (VERSÃO FINAL: FORÇANDO RESET POR DTR/RTS)

import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import threading
import time
import serial
import sys
import queue
from datetime import datetime
import csv
import random
from .i18n import t
from .app_console import fc_log

# --- CONFIGURAÇÕES DE HARDWARE ---
ARDUINO_PORT = 'COM5' 
ARDUINO_BAUD = 9600
PASSOS_PER_REVOLUCAO = 2038 

# --- CLASSE DE CONTROLE PRINCIPAL ---
class MovimentoMesa(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title(f"Controle de Movimento Puro (Arduino {ARDUINO_PORT})")
        self.grid(row=0, column=0, sticky="nsew", padx=10, pady=10)
        self.master.columnconfigure(0, weight=1)
        self.master.rowconfigure(0, weight=1)

        self.test_running = False
        self.cancel_requested = False
        self.results_queue = queue.Queue()
        self.test_history = []
        self.current_position = 0.0 

        # Variáveis de entrada (APENAS ÂNGULOS)
        self.start_angle_var = tk.DoubleVar(value=0.0)
        self.end_angle_var = tk.DoubleVar(value=360.0)
        self.step_angle_var = tk.DoubleVar(value=45.0)

        self._create_widgets()
        self._check_hardware() 
        self.process_queue() 

    def _create_widgets(self):
        # Frame de Configuração
        config_frame = ttk.LabelFrame(self, text="Configuração do Movimento")
        config_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=(0, 10))
        config_frame.columnconfigure(1, weight=1)

        # Entradas
        ttk.Label(config_frame, text="Ângulo Inicial (°):").grid(row=0, column=0, sticky="w", pady=2, padx=5)
        ttk.Entry(config_frame, textvariable=self.start_angle_var, width=15).grid(row=0, column=1, sticky="ew", pady=2, padx=5)
        
        ttk.Label(config_frame, text="Ângulo Final (°):").grid(row=1, column=0, sticky="w", pady=2, padx=5)
        ttk.Entry(config_frame, textvariable=self.end_angle_var, width=15).grid(row=1, column=1, sticky="ew", pady=2, padx=5)
        
        ttk.Label(config_frame, text="Step (°):").grid(row=2, column=0, sticky="w", pady=2, padx=5)
        ttk.Entry(config_frame, textvariable=self.step_angle_var, width=15).grid(row=2, column=1, sticky="ew", pady=2, padx=5)

        # Botões
        command_button_frame = tk.Frame(config_frame)
        command_button_frame.grid(row=0, column=2, padx=10, rowspan=3, sticky="nswe")
        
        self.start_button = ttk.Button(command_button_frame, text="Iniciar Movimento", command=self.start_test, width=15)
        self.start_button.pack(pady=3, fill="x")

        self.export_button = ttk.Button(command_button_frame, text="Exportar Log CSV", command=self.export_to_csv, width=15)
        self.export_button.pack(pady=3, fill="x")
        
        # Frame de Status
        status_frame = ttk.LabelFrame(self, text="Status e Controle") 
        status_frame.grid(row=1, column=0, sticky="ew", padx=10, pady=(0, 10))
        self.status_label = ttk.Label(status_frame, text="Aguardando inicialização...", foreground="blue")
        self.status_label.pack(fill="x")
        self.cancel_button = ttk.Button(status_frame, text="Cancelar", command=self._request_cancel, state="disabled")
        self.cancel_button.pack(pady=5)

        # Frame do Histórico (Tabela)
        history_frame = ttk.LabelFrame(self, text="Log de Movimento")
        history_frame.grid(row=2, column=0, sticky="nsew", padx=10, pady=(0, 10))
        self.rowconfigure(2, weight=1) 

        # Tabela Treeview
        cols = ('Angulo', 'Status', 'Passos', 'Data/Hora')
        self.history_tree = ttk.Treeview(history_frame, columns=cols, show='headings', height=10)
        self.history_tree.heading('Angulo', text='Ângulo (°)')
        self.history_tree.heading('Status', text='Status')
        self.history_tree.heading('Passos', text='Passos')
        self.history_tree.heading('Data/Hora', text='Data/Hora')
        self.history_tree.column('Angulo', width=80, anchor='center')
        self.history_tree.column('Status', width=200, anchor='center')
        self.history_tree.column('Passos', width=120, anchor='center')
        self.history_tree.column('Data/Hora', width=120, anchor='center')

        # Scrollbar
        scrollbar = ttk.Scrollbar(history_frame, orient=tk.VERTICAL, command=self.history_tree.yview)
        self.history_tree.configure(yscrollcommand=scrollbar.set)
        
        self.history_tree.pack(side="left", fill="both", expand=True)
        scrollbar.pack(side="right", fill="y")
        
    def _check_hardware(self):
        """Verifica se a porta COM5 existe."""
        try:
            # Tenta APENAS abrir e fechar a porta COM5 para verificar sua existência
            temp_serial = serial.Serial(ARDUINO_PORT, ARDUINO_BAUD, timeout=1)
            temp_serial.close()
            self.status_label.config(text=f"Pronto. Arduino conectado em {ARDUINO_PORT}.", foreground="green")
            self.start_button.config(state="normal")
        except serial.SerialException:
            self.status_label.config(text=f"ERRO: Arduino não encontrado em {ARDUINO_PORT}.", foreground="red")
            self.start_button.config(state="disabled")
            
    def _request_cancel(self):
        """Solicita o cancelamento do teste."""
        self.cancel_requested = True
        self.status_label.config(text="Cancelamento solicitado...", foreground="orange")
        self.cancel_button.config(state="disabled")

    def _start_test_thread(self):
        """Inicia a thread principal do workflow de teste."""
        self.start_button.config(state="disabled")
        self.cancel_button.config(state="normal")
        self.test_running = True
        self.cancel_requested = False
        self.status_label.config(text="Teste em andamento...", foreground="blue")

        worker_thread = threading.Thread(target=self._movement_workflow, daemon=True)
        worker_thread.start()

    def start_test(self):
        """Valida entradas antes de iniciar o teste."""
        if self.test_running: return
        
        try:
            start_angle = self.start_angle_var.get()
            end_angle = self.end_angle_var.get()
            step_angle = self.step_angle_var.get()

            if start_angle >= end_angle:
                messagebox.showerror(
                    t('mesa_movimento.error_input_title'),
                    t('mesa_movimento.angle_order_msg'),
                    parent=self.master)
                return
            if step_angle <= 0.0:
                messagebox.showerror(
                    t('mesa_movimento.error_input_title'),
                    t('mesa_movimento.step_positive_msg'),
                    parent=self.master)
                return

            if self.start_button.cget('state') == 'disabled':
                messagebox.showerror(
                    t('mesa_movimento.hardware_not_ready_title'),
                    t('mesa_movimento.hardware_not_ready_msg'),
                    parent=self.master)
                return

            # Limpa o histórico anterior
            for item in self.history_tree.get_children():
                self.history_tree.delete(item)
            self.test_history = []
            
            # Define current_position para o ângulo inicial para começar a varredura
            self.current_position = start_angle 
            
            self._start_test_thread()

        except ValueError as e:
            messagebox.showerror(
                t('mesa_movimento.error_input_title'),
                t('mesa_movimento.check_input_msg').format(error=str(e)),
                parent=self.master)
        except Exception as e:
            messagebox.showerror(
                t('mesa_movimento.error_generic_title'),
                t('mesa_movimento.unexpected_error_msg').format(error=str(e)),
                parent=self.master)

    # --- FUNÇÕES DE COMUNICAÇÃO ---
    def _calcular_passos(self, graus):
        """Converte o ângulo desejado para passos."""
        passos = int((graus / 360) * PASSOS_PER_REVOLUCAO)
        return passos

    def _mover_mesa(self, graus_relativos):
        """Abre, FORÇA RESET, envia comando, espera PRONTO, fecha."""
        
        if self.cancel_requested:
            return False
            
        passos = self._calcular_passos(graus_relativos)
        
        try:
            # 1. Abre a porta serial para este comando
            # rtscts=False, dsrdtr=False (para não interferir com a comunicação)
            arduino = serial.Serial(
                port=ARDUINO_PORT, 
                baudrate=ARDUINO_BAUD, 
                timeout=3,
                rtscts=False, 
                dsrdtr=False  
            )
            
            # 2. ** FORÇAR RESET DE SOFTWARE **: Simula o reset do Monitor Serial
            # Abaixa e levanta o pino DTR (Data Terminal Ready), forçando o reset do Arduino.
            arduino.setDTR(False)
            time.sleep(0.02) # Espera 20ms para o reset
            arduino.setDTR(True) 
            time.sleep(1.5) # Espera o Arduino rebotar e inicializar (Serial.begin)

            # Limpeza
            arduino.reset_input_buffer()
            arduino.reset_output_buffer()
            time.sleep(0.1) 
            
            # Limpa o Buffer Inicial do Arduino (Geralmente "ARDUINO-PRONTO\r\n")
            while arduino.in_waiting > 0:
                arduino.readline()
                
            # 3. Envia o comando
            comando = str(passos) + '\r\n' 
            
            fc_log("mesa_movimento", f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] SEND {repr(comando)} steps={passos}")
            
            arduino.write(comando.encode('utf-8'))
            self.results_queue.put({'type': 'STATUS', 'text': f"Movendo mesa {graus_relativos:.1f} graus ({passos} passos)..."})

            # 4. Espera pela resposta 'PRONTO'
            start_time = time.time()
            TIMEOUT = 5.0 
            full_buffer = "" 

            while time.time() - start_time < TIMEOUT:
                if self.cancel_requested: 
                    arduino.close()
                    return False
                
                if arduino.in_waiting > 0:
                    
                    raw_data = arduino.read(arduino.in_waiting)
                    full_buffer += raw_data.decode('utf-8', errors='ignore')
                    
                    if "PRONTO" in full_buffer:
                        fc_log("mesa_movimento", f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] READY buffer={repr(full_buffer)}")
                        arduino.close()
                        return True
                        
                time.sleep(0.01)
            
            # Se atingir o timeout
            arduino.close()
            fc_log("mesa_movimento", f"[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}] MOVEMENT FAILED")
            fc_log("mesa_movimento", f"Final partial buffer: {repr(full_buffer)}")
            
            self.results_queue.put({'type': 'ERROR', 'msg': f"FALHA DE MOVIMENTO: Mesa não girou em {graus_relativos:.1f} graus. Falha de Driver/DTR/RTS."})
            return False

        except serial.SerialException as e:
            self.results_queue.put({'type': 'ERROR', 'msg': f"ERRO SERIAL (COM5) - Falha ao abrir a porta: {e}"})
            return False
        except Exception as e:
            self.results_queue.put({'type': 'ERROR', 'msg': f"Erro inesperado durante _mover_mesa: {e}"})
            return False


    # --- WORKFLOW PRINCIPAL DE MOVIMENTO ---
    def _movement_workflow(self):
        """Workflow para movimentar a mesa e logar o sucesso/falha."""
        
        # 1. Parâmetros do Teste
        start_angle = self.start_angle_var.get()
        end_angle = self.end_angle_var.get()
        step_angle = self.step_angle_var.get()
        
        current_angle = self.current_position
        success = True
        
        try:
            # 1.1. Reset Inicial (Para o ângulo inicial)
            # Executamos o primeiro movimento para zerar a posição e confirmar a conexão
            rel_move_init = start_angle - self.current_position
            if not self._mover_mesa(rel_move_init):
                 self.results_queue.put({'type': 'ERROR', 'msg': "Falha crítica no reset de posição. Verifique a COM5."})
                 return 

            self.current_position = start_angle
            current_angle = self.current_position
            
            # 2. Loop de Movimento
            while current_angle <= end_angle and success and not self.cancel_requested:
                
                # 2.1. Adiciona Resultado
                status_log = "PRONTO (Mesa Parada)"
                self.results_queue.put({
                    'type': 'RESULT',
                    'angle': current_angle,
                    'status_log': status_log,
                    'passos': self._calcular_passos(step_angle) if current_angle != end_angle else 0,
                    'timestamp': datetime.now().strftime('%H:%M:%S')
                })
                
                # 2.2. Verifica Cancelamento e Próximo Passo
                if self.cancel_requested:
                    break
                    
                # 2.3. Prepara para o próximo ângulo
                next_angle = current_angle + step_angle
                
                if next_angle > end_angle:
                    break 

                # 2.4. Movimenta a mesa
                if not self._mover_mesa(step_angle):
                    self.results_queue.put({'type': 'ERROR', 'msg': "Movimento falhou. Verifique conexão."})
                    success = False
                    break
                
                self.current_position += step_angle
                current_angle = self.current_position
            
            # 3. Movimento Final (volta para 0)
            if success and not self.cancel_requested:
                self.results_queue.put({'type': 'STATUS', 'text': "Teste concluído. Voltando para 0°."})
                rel_move_final = -self.current_position
                if not self._mover_mesa(rel_move_final): 
                    self.results_queue.put({'type': 'STATUS', 'text': "Falha ao mover para 0°, encerrando."})
                else:
                    self.current_position = 0.0
                    self.results_queue.put({'type': 'STATUS', 'text': "Posição final 0° atingida."})
                
        except Exception as e:
            self.results_queue.put({'type': 'ERROR', 'msg': f"Erro no workflow de teste: {e}"})
            success = False
            
        finally:
            self.results_queue.put({'type': 'FINISH', 'status': 'Cancelled' if self.cancel_requested else ('Success' if success else 'Error')})


    # --- PROCESSAMENTO DA FILA DA GUI ---
    def process_queue(self):
        """Processa a fila de resultados e eventos da thread de teste."""
        try:
            while not self.results_queue.empty():
                result = self.results_queue.get_nowait()
                
                if result.get('type') == 'STATUS':
                    self.status_label.config(text=result['text'], foreground="blue")
                
                elif result.get('type') == 'ERROR':
                    error_msg = result['msg']
                    self.status_label.config(text=f"ERRO: {error_msg}", foreground="red")
                    messagebox.showerror(
                        t('mesa_movimento.execution_error_title'),
                        error_msg,
                        parent=self.master)
                    self._end_test_ui(status='Error')

                elif result.get('type') == 'RESULT':
                    # Adiciona resultado à tabela
                    self.history_tree.insert('', 'end', values=(
                        f"{result['angle']:.1f}", 
                        result['status_log'], 
                        result['passos'], 
                        result['timestamp']
                    ))
                    self.history_tree.see('end') 
                    self.test_history.append(result)

                elif result.get('type') == 'FINISH':
                    self._end_test_ui(status='Success')

        except queue.Empty:
            pass
        except Exception as e:
            if "Item end not found" not in str(e):
                 fc_log("mesa_movimento", f"Queue processing error: {e}")

        # Agenda a próxima verificação da fila
        self.after(100, self.process_queue)
        
    def _end_test_ui(self, status):
        """Finaliza a interface após o teste."""
        self.test_running = False
        self.cancel_requested = False
        self.start_button.config(state="normal")
        self.cancel_button.config(state="disabled")

        if status == 'Success':
            self.status_label.config(text="Teste concluído com Sucesso.", foreground="green")
        elif status == 'Cancelled':
            self.status_label.config(text=f"Teste Cancelado. Mesa em {self.current_position:.1f}°.", foreground="orange")
        else:
            self.status_label.config(text="Teste Encerrado devido a um ERRO.", foreground="red")


    def export_to_csv(self):
        """Salva os dados do histórico de testes em um arquivo CSV."""
        if not self.test_history:
            messagebox.showinfo(
                t('mesa_movimento.export_no_data_title'),
                t('mesa_movimento.export_no_data_msg'),
                parent=self.master)
            return

        filename = filedialog.asksaveasfilename(
            defaultextension=".csv",
            filetypes=[
                (t('export_popup.filetype_csv'), "*.csv"),
                (t('export_popup.filetype_all'), "*.*"),
            ],
            title=t('mesa_movimento.save_log_title'),
            parent=self.master,
        )
        
        if not filename:
            return

        try:
            with open(filename, mode='w', newline='', encoding='utf-8') as file:
                writer = csv.writer(file, delimiter=';')
                
                writer.writerow(["Angulo (°)", "Status", "Passos Enviados", "Data_Hora"])
                
                for entry in self.test_history:
                    writer.writerow([
                        f"{entry['angle']:.1f}",
                        entry['status_log'],
                        entry['passos'],
                        entry['timestamp']
                    ])

            messagebox.showinfo(
                t('mesa_movimento.export_success_title'),
                t('mesa_movimento.export_success_msg').format(filename=filename),
                parent=self.master)
            
        except Exception as e:
            messagebox.showerror(
                t('mesa_movimento.export_error_title'),
                t('mesa_movimento.export_error_msg').format(error=str(e)),
                parent=self.master)


# --- INICIALIZAÇÃO DA GUI ---
if __name__ == "__main__":
    root = tk.Tk()
    app = MovimentoMesa(master=root)
    root.protocol("WM_DELETE_WINDOW", lambda: sys.exit(0))
    root.mainloop()