# hardware_controller.py
# Versão: 3.1
# Data: 13/07/2025 16:55 (Campinas, Brasil)
# Descrição: Otimização da função scan_for_tags para interromper o laço ao encontrar um EPC específico.

import ctypes
import time
import os
import sys
import struct

class TagInfo:
    """Estrutura para armazenar os dados de uma tag lida."""
    def __init__(self, epc, rssi):
        self.epc = epc
        self.rssi = rssi
    def __repr__(self):
        return f"Tag(EPC={self.epc}, RSSI={self.rssi}dBm)"

# Comandos da DLL
RFID_CMD_SET_TXPOWER = 0x10
RFID_CMD_SET_FREQ_TABLE = 0x14
RFID_CMD_INV_TAG = 0x80
RFID_CMD_INVENTORY_MUL = 0x81  # Inventário contínuo (múltiplo)
RFID_CMD_STOP_INVENTORY = 0x8C  # Parar inventário contínuo
RFID_CMD_SET_SOFTRESET = 0x68

# Configurações de Conexão
COM_PORT = 4
BAUD_RATE = 115200

# Carregamento da DLL
try:
    if hasattr(sys, '_MEIPASS'):
        dll_path = os.path.join(sys._MEIPASS, "UHFRFID.dll")
    else:
        dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "UHFRFID.dll")
    rfid_dll = ctypes.CDLL(dll_path)
    
    rfid_dll.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
    rfid_dll.UHF_RFID_Open.restype = ctypes.c_int
    rfid_dll.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
    rfid_dll.UHF_RFID_Close.restype = ctypes.c_int
    rfid_dll.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]
    rfid_dll.UHF_RFID_Set.restype = ctypes.c_int
    print("UHFRFID.dll carregada e funções configuradas com sucesso.")
except Exception as e:
    print(f"AVISO: Não foi possível carregar ou configurar UHFRFID.dll: {e}")
    rfid_dll = None

class HardwareController:
    """
    Controlador de hardware que gerencia a comunicação com o leitor.
    """
    def __init__(self, com_port=None):
        self.dll = rfid_dll
        self.com_port = com_port if com_port is not None else COM_PORT
        self.baud_rate = BAUD_RATE
        self.is_connected = False
    
    def find_and_open_port(self, enable_beep=True):
        """Abre a porta e envia um comando de reset para o leitor.
        
        Args:
            enable_beep (bool): Se True, faz reset com bip. Se False, abre sem bip.
        """
        if not self.dll: return False
        if self.is_connected: 
            return True
        
        status = self.dll.UHF_RFID_Open(self.com_port, self.baud_rate)
        if status == 0:
            self.is_connected = True
            print(f"Leitor conectado com sucesso na porta COM{self.com_port}.")
            
            if enable_beep:
                print("Enviando comando de Soft Reset para o leitor...")
                if self.reset_reader():
                    print("Leitor resetado com sucesso.")
                else:
                    print("AVISO: Falha ao resetar o leitor.")
            else:
                print("Conexão estabelecida sem bip sonoro.")
            
            time.sleep(0.1) #
            return True
        return False
    
    def close_port(self):
        if not self.is_connected: return False
        self.dll.UHF_RFID_Close(self.com_port)
        self.is_connected = False
        print(f"Porta COM{self.com_port} fechada com sucesso.")
        return True
    
    def reset_reader(self):
        """Envia um comando de software reset para o leitor."""
        if not self.is_connected: return False
        dummy_buffer = ctypes.create_string_buffer(32)
        dummy_len = ctypes.c_uint(0)
        status = self.dll.UHF_RFID_Set(RFID_CMD_SET_SOFTRESET, None, 0, dummy_buffer, ctypes.byref(dummy_len))
        return status == 0

    def set_power(self, power_dbm):
        """Configura a potência de transmissão com retry e validação."""
        if not self.is_connected: 
            return False
        
        # Validar potência
        if power_dbm < 0 or power_dbm > 30:
            return False
        
        # Tentar configuração com retry
        max_retries = 3
        for attempt in range(max_retries):
            try:
                power_val = int(power_dbm * 100)
                power_data = bytes([0x00, 0x00]) + power_val.to_bytes(2, 'big') * 2
                dummy_buffer = ctypes.create_string_buffer(32)
                dummy_len = ctypes.c_uint(0)
                
                status = self.dll.UHF_RFID_Set(RFID_CMD_SET_TXPOWER, ctypes.c_char_p(power_data), 6, dummy_buffer, ctypes.byref(dummy_len))
                
                if status == 0:
                    return True
                else:
                    # Delay mínimo entre tentativas
                    if attempt < max_retries - 1:
                        time.sleep(0.001)  # Reduzido para 1ms
                        
            except Exception as e:
                if attempt < max_retries - 1:
                    time.sleep(0.001)  # Reduzido para 1ms
        
        return False
    
    def set_frequency(self, freq_mhz):
        """Configura a frequência de operação."""
        if not self.is_connected: 
            return False
        
        # Validar frequência
        if freq_mhz < 800 or freq_mhz > 1000:
            return False
        
        try:
            # Usar a mesma abordagem que funciona no teste direto
            freq_data = (1).to_bytes(1, 'big') + int(freq_mhz * 1000).to_bytes(3, 'big')
            dummy_buffer = ctypes.create_string_buffer(32)
            dummy_len = ctypes.c_uint(0)
            
            # Delay mínimo antes de configurar frequência
            time.sleep(0.001)  # Reduzido para 1ms
            
            status = self.dll.UHF_RFID_Set(RFID_CMD_SET_FREQ_TABLE, ctypes.c_char_p(freq_data), 4, dummy_buffer, ctypes.byref(dummy_len))
            
            # Delay mínimo após configurar frequência
            time.sleep(0.001)  # Reduzido para 1ms
            
            return status == 0
                
        except Exception as e:
            return False

    def set_inventory_power(self, power_dbm=15.0):
        """Configura a potência para inventário de tags."""
        return self.set_power(power_dbm)
    
    def get_temperature(self):
        """Lê a temperatura do leitor RFID."""
        if not self.is_connected:
            return None
        
        try:
            output_buffer = ctypes.create_string_buffer(64)
            output_len = ctypes.c_uint(0)
            
            # Comando para ler temperatura (0x34)
            RFID_CMD_GET_TEMPERATURE = 0x34
            status = self.dll.UHF_RFID_Set(RFID_CMD_GET_TEMPERATURE, None, 0, output_buffer, ctypes.byref(output_len))
            
            if status == 0 and output_len.value >= 3:
                temp_val = struct.unpack('>h', output_buffer.raw[1:3])[0]
                temp = temp_val / 100.0
                return temp
            else:
                print(f"DEBUG: Falha ao ler temperatura. Status: {status}, Length: {output_len.value}")
                return None
                
        except Exception as e:
            print(f"DEBUG: Erro ao ler temperatura: {e}")
            return None

    def scan_for_tags(self, epc_to_find_and_stop=None, continuous_mode=False):
        """Executa uma varredura geral, parando se um EPC específico for encontrado."""
        if not self.is_connected: return []

        tags_encontradas = []
        epcs_vistos = set()

        # Reduzido para 3 tentativas para máxima velocidade
        max_attempts = 3  # Reduzido de 5 para 3
        consecutive_empty = 0
        
        for attempt in range(max_attempts):
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # SOLUÇÃO SIMPLES: Remove a condição que para após tentativas vazias
            input_data_inv = bytes([0x00, 0x64])  # Igual ao FastSurance
            status_inv = self.dll.UHF_RFID_Set(RFID_CMD_INV_TAG, ctypes.c_char_p(input_data_inv), 2, output_buffer, ctypes.byref(output_len))
            
            # IGUAL AO FASTSURANCE: condição > 5
            if status_inv == 0 and output_len.value > 5:
                # IGUAL AO FASTSURANCE: extração dinâmica
                epc_string = output_buffer.raw[2:output_len.value - 3].hex().upper()
                if epc_string not in epcs_vistos:
                    rssi_bytes = output_buffer.raw[14:16]
                    try:
                        rssi_int = struct.unpack('>h', rssi_bytes)[0]
                        rssi_val = rssi_int / 10.0
                        if -100 <= rssi_val <= 0:
                            tags_encontradas.append(TagInfo(epc=epc_string, rssi=rssi_val))
                            epcs_vistos.add(epc_string)
                    except Exception as e:
                        continue
                
                # Se um EPC específico foi solicitado e o encontramos, para o laço.
                if epc_to_find_and_stop and epc_string == epc_to_find_and_stop:
                    break
                
                consecutive_empty = 0
            else:
                consecutive_empty += 1
                # SOLUÇÃO SIMPLES: Nunca para por tentativas vazias
                # if consecutive_empty >= 1:
                #     break
            
            # Delay mínimo entre tentativas
            time.sleep(0.001)  # Reduzido para 1ms

        return tags_encontradas

    def stop_continuous_inventory(self):
        """Para o inventário contínuo"""
        if not self.is_connected:
            return False
        
        try:
            output_buffer = ctypes.create_string_buffer(256)
            output_len = ctypes.c_uint(0)
            
            # Comando para parar inventário contínuo
            status = self.dll.UHF_RFID_Set(RFID_CMD_STOP_INVENTORY, None, 0, output_buffer, ctypes.byref(output_len))
            return status == 0
        except Exception as e:
            print(f"Erro ao parar inventário contínuo: {e}")
            return False

def get_hardware_controller():
    """Retorna a instância única do HardwareController (singleton)."""
    if not hasattr(get_hardware_controller, "_instance"):
        get_hardware_controller._instance = HardwareController()
    return get_hardware_controller._instance