#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Gerenciador Centralizado de Portas COM
Evita conflitos quando múltiplos módulos tentam acessar a porta simultaneamente
"""

import threading
import time
import ctypes
import os
import sys
from typing import Optional, Dict, Any
from enum import Enum

class PortStatus(Enum):
    AVAILABLE = "available"
    IN_USE = "in_use"
    ERROR = "error"
    LOCKED = "locked"

class COMPortManager:
    """
    Gerenciador centralizado da porta COM
    Evita conflitos entre módulos durante mudança de abas
    """
    
    _instance = None
    _lock = threading.Lock()
    
    def __new__(cls):
        if cls._instance is None:
            with cls._lock:
                if cls._instance is None:
                    cls._instance = super(COMPortManager, cls).__new__(cls)
        return cls._instance
    
    def __init__(self):
        if hasattr(self, '_initialized'):
            return
            
        self._initialized = True
        self.port_lock = threading.RLock()  # Reentrant lock para permitir chamadas aninhadas
        self.current_user = None
        self.port_status = PortStatus.AVAILABLE
        self.last_operation_time = 0
        self.min_interval = 0.05  # 50ms entre operações
        self.operation_timeout = 10.0  # 10 segundos timeout
        self.port_operations = 0
        
        # Configuração da DLL
        self.dll_name = "UHFRFID.dll"
        self.com_port = 4
        self.baud_rate = 115200
        
        try:
            if hasattr(sys, '_MEIPASS'):
                dll_path = os.path.join(sys._MEIPASS, self.dll_name)
            else:
                dll_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), self.dll_name)
            self.rfid_sdk = ctypes.CDLL(dll_path)
            
            # Configuração das funções
            self.rfid_sdk.UHF_RFID_Open.argtypes = [ctypes.c_ubyte, ctypes.c_int]
            self.rfid_sdk.UHF_RFID_Open.restype = ctypes.c_int
            self.rfid_sdk.UHF_RFID_Close.argtypes = [ctypes.c_ubyte]
            self.rfid_sdk.UHF_RFID_Close.restype = ctypes.c_int
            self.rfid_sdk.UHF_RFID_Set.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint, ctypes.c_char_p, ctypes.POINTER(ctypes.c_uint)]
            self.rfid_sdk.UHF_RFID_Set.restype = ctypes.c_int
            
            print(f"[OK] COMPortManager inicializado - DLL carregada: {dll_path}")
        except Exception as e:
            print(f"[ERRO] Erro ao carregar DLL: {e}")
            self.rfid_sdk = None
    
    def acquire_port(self, user_name: str, timeout: float = 5.0) -> bool:
        """
        Adquire o controle da porta COM
        
        Args:
            user_name: Nome do módulo/usuário que está solicitando a porta
            timeout: Tempo máximo para aguardar (segundos)
        
        Returns:
            bool: True se conseguiu adquirir a porta
        """
        start_time = time.time()
        
        while time.time() - start_time < timeout:
            with self.port_lock:
                if self.port_status == PortStatus.AVAILABLE:
                    self.current_user = user_name
                    self.port_status = PortStatus.IN_USE
                    self.last_operation_time = time.time()
                    print(f"[INFO] Porta COM{self.com_port} adquirida por: {user_name}")
                    return True
                elif self.port_status == PortStatus.IN_USE:
                    print(f"[INFO] Porta COM{self.com_port} em uso por: {self.current_user} - Aguardando...")
                    time.sleep(0.1)
                else:
                    print(f"[AVISO] Porta COM{self.com_port} em estado: {self.port_status.value}")
                    time.sleep(0.1)
        
        print(f"[ERRO] Timeout ao tentar adquirir porta COM{self.com_port} para: {user_name}")
        return False
    
    def release_port(self, user_name: str) -> bool:
        """
        Libera o controle da porta COM
        
        Args:
            user_name: Nome do módulo/usuário que está liberando a porta
        
        Returns:
            bool: True se conseguiu liberar a porta
        """
        with self.port_lock:
            if self.current_user == user_name:
                self.current_user = None
                self.port_status = PortStatus.AVAILABLE
                print(f"[INFO] Porta COM{self.com_port} liberada por: {user_name}")
                return True
            else:
                print(f"[AVISO] Tentativa de liberar porta por {user_name}, mas está em uso por: {self.current_user}")
                return False
    
    def execute_with_port(self, operation: callable, user_name: str, *args, **kwargs) -> Any:
        """
        Executa uma operação com controle automático da porta
        
        Args:
            operation: Função a ser executada
            user_name: Nome do módulo/usuário
            *args, **kwargs: Argumentos para a operação
        
        Returns:
            Resultado da operação ou None se falhar
        """
        if not self.acquire_port(user_name):
            return None
        
        try:
            # Aguardar intervalo mínimo entre operações
            current_time = time.time()
            if current_time - self.last_operation_time < self.min_interval:
                time.sleep(self.min_interval - (current_time - self.last_operation_time))
            
            # Executar operação
            result = operation(*args, **kwargs)
            self.last_operation_time = time.time()
            self.port_operations += 1
            
            return result
            
        except Exception as e:
            print(f"[ERRO] Erro durante operação na porta COM{self.com_port}: {e}")
            return None
        finally:
            self.release_port(user_name)
    
    def open_port(self, user_name: str) -> bool:
        """
        Abre a porta COM com controle de acesso
        
        Args:
            user_name: Nome do módulo/usuário
        
        Returns:
            bool: True se conseguiu abrir a porta
        """
        def _open_operation():
            if not self.rfid_sdk:
                return False
            
            # Fechar porta primeiro para garantir estado limpo
            try:
                self.rfid_sdk.UHF_RFID_Close(self.com_port)
                time.sleep(0.05)
            except:
                pass
            
            # Tentar abrir a porta
            for attempt in range(3):
                try:
                    if self.rfid_sdk.UHF_RFID_Open(self.com_port, self.baud_rate) == 0:
                        print(f"[OK] Porta COM{self.com_port} aberta com sucesso (tentativa {attempt + 1})")
                        return True
                    else:
                        if attempt < 2:
                            time.sleep(0.3)
                except Exception as e:
                    if attempt < 2:
                        time.sleep(0.3)
            
            print(f"[ERRO] Falha ao abrir porta COM{self.com_port} apos 3 tentativas")
            return False
        
        return self.execute_with_port(_open_operation, user_name)
    
    def close_port(self, user_name: str) -> bool:
        """
        Fecha a porta COM com controle de acesso
        
        Args:
            user_name: Nome do módulo/usuário
        
        Returns:
            bool: True se conseguiu fechar a porta
        """
        def _close_operation():
            if not self.rfid_sdk:
                return False
            
            try:
                self.rfid_sdk.UHF_RFID_Close(self.com_port)
                print(f"[OK] Porta COM{self.com_port} fechada com sucesso")
                return True
            except Exception as e:
                print(f"[ERRO] Erro ao fechar porta COM{self.com_port}: {e}")
                return False
        
        return self.execute_with_port(_close_operation, user_name)
    
    def send_command(self, user_name: str, command: int, data: bytes = None, 
                    output_buffer_size: int = 256) -> Optional[tuple]:
        """
        Envia um comando para o hardware com controle de acesso
        
        Args:
            user_name: Nome do módulo/usuário
            command: Código do comando
            data: Dados do comando
            output_buffer_size: Tamanho do buffer de saída
        
        Returns:
            tuple: (status, output_data, output_length) ou None se falhar
        """
        def _send_operation():
            if not self.rfid_sdk:
                return None
            
            try:
                output_buffer = ctypes.create_string_buffer(output_buffer_size)
                output_len = ctypes.c_uint(0)
                
                if data:
                    status = self.rfid_sdk.UHF_RFID_Set(command, ctypes.c_char_p(data), 
                                                       len(data), output_buffer, ctypes.byref(output_len))
                else:
                    status = self.rfid_sdk.UHF_RFID_Set(command, None, 0, 
                                                       output_buffer, ctypes.byref(output_len))
                
                return (status, output_buffer.raw[:output_len.value], output_len.value)
                
            except Exception as e:
                print(f"[ERRO] Erro ao enviar comando {command:02X}: {e}")
                return None
        
        return self.execute_with_port(_send_operation, user_name)
    
    def get_status(self) -> Dict[str, Any]:
        """
        Retorna o status atual do gerenciador de portas
        
        Returns:
            dict: Status atual
        """
        with self.port_lock:
            return {
                'status': self.port_status.value,
                'current_user': self.current_user,
                'operations_count': self.port_operations,
                'last_operation_time': self.last_operation_time
            }
    
    def force_reset(self) -> bool:
        """
        Força um reset da porta COM
        
        Returns:
            bool: True se conseguiu fazer o reset
        """
        print(f"[INFO] Forcando reset da porta COM{self.com_port}...")
        
        with self.port_lock:
            try:
                if self.rfid_sdk:
                    # Segurança: garantir STOP e CW OFF
                    out_buf = ctypes.create_string_buffer(64)
                    out_len = ctypes.c_uint(0)
                    try:
                        self.rfid_sdk.UHF_RFID_Set(0x8C, None, 0, out_buf, ctypes.byref(out_len))  # STOP INVENTORY
                    except Exception:
                        pass
                    try:
                        self.rfid_sdk.UHF_RFID_Set(0x24, ctypes.c_char_p(b'\x00'), 1, out_buf, ctypes.byref(out_len))  # CW OFF
                    except Exception:
                        pass
                    # Fechar porta
                    self.rfid_sdk.UHF_RFID_Close(self.com_port)
                    time.sleep(0.1)
                
                # Resetar estado
                self.current_user = None
                self.port_status = PortStatus.AVAILABLE
                self.last_operation_time = time.time()

                print(f"[OK] Reset da porta COM{self.com_port} concluido")
                return True
            except Exception as e:
                print(f"[ERRO] Erro durante reset da porta COM{self.com_port}: {e}")
                return False
    
    def is_available(self) -> bool:
        """
        Verifica se a porta está disponível
        
        Returns:
            bool: True se a porta está disponível
        """
        with self.port_lock:
            return self.port_status == PortStatus.AVAILABLE

# Instância global do gerenciador de portas
port_manager = COMPortManager()

def get_port_manager():
    """
    Retorna a instância global do gerenciador de portas
    
    Returns:
        COMPortManager: Instância do gerenciador de portas
    """
    return port_manager 