#include <ps2dev.h>

PS2dev keyboard(18, 19); 
const int pinBilheteiro = 32; 

void sendKey(unsigned char scancode) {
  keyboard.write(scancode); 
  delay(50);                
  keyboard.write(0xF0);     
  keyboard.write(scancode); 
}

void sendExtendedKey(unsigned char scancode) {
  keyboard.write(0xE0);      
  keyboard.write(scancode); 
  delay(50);
  keyboard.write(0xE0);      
  keyboard.write(0xF0);     
  keyboard.write(scancode); 
}


void setup() {
  Serial.begin(115200); 
  pinMode(pinBilheteiro, OUTPUT);
  digitalWrite(pinBilheteiro, LOW); 
  keyboard.keyboard_init();

  Serial.println("[ARDUINO] Sistema Pronto.");
}


void loop() {
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    input.trim();   

    if (input.length() == 0) return;  
    
    Serial.print("[RECEBIDO] ");
    Serial.println(input);
    
    if (input.startsWith("CRED:")) {
      int totalPulsos = input.substring(5).toInt();
      Serial.print("[PULSOS] ");
      Serial.println(totalPulsos);
      
      for (int i = 0; i < totalPulsos; i++) {
        digitalWrite(pinBilheteiro, HIGH);
        delay(100); 
        digitalWrite(pinBilheteiro, LOW);
        delay(100); 
       // yield(); 
      }
      Serial.println("CREDITOS_OK"); 
    } 

    else if (input.startsWith("cred:")) {
      int totalPulsos = input.substring(5).toInt();
      Serial.print("[PULSOS] ");
      Serial.println(totalPulsos);
      
      for (int i = 0; i < totalPulsos; i++) {
        digitalWrite(pinBilheteiro, HIGH);
        delay(40); 
        digitalWrite(pinBilheteiro, LOW);
        delay(40); 
        yield(); 
      }
      Serial.println("CREDITOS_OK"); 
    } 
    else if (input.length() == 1) {
      char key = input.charAt(0);
      
      switch (key) {
        case 'a': sendKey(0x1C); break; // Aposta
        case 's': sendKey(0x1B); break; // Jogar
        case 'n': sendKey(0x31); break; // ver numeros
        case 'c': sendKey(0x21); break; // Cartelas
        case 'e': sendKey(0x24); break; // Extra/turbo   
        case 'h': sendKey(0x33); break; // Ajuda
        case '9': sendKey(0x46); break; // fazer leitura 
        case 'p': sendKey(0x4D); break; // cobrar pagto
        case 't': sendKey(0x0D); break; // tab entrar senha
        case 'o': sendKey(0x5A); break; // Enter (OK)
        // Numeros teclado virtual leitor maquinas
        case '0': sendKey(0x45); break; 
        case '1': sendKey(0x16); break;
        case '2': sendKey(0x1E); break;
        case '3': sendKey(0x26); break;
        case '4': sendKey(0x25); break;
        case '5': sendKey(0x2E); break;
        case '6': sendKey(0x36); break;
        case '7': sendKey(0x3D); break;
        case '8': sendKey(0x3E); break;

        default:
          Serial.print("[DESCONHECIDO] ");
          Serial.println(key);
          break;
      }
      Serial.println("TECLA_OK");  
    }
    else {
      Serial.print("[COMANDO DESCONHECIDO] ");
      Serial.println(input);
    }
  }
}


# NAO REMOVER CODIGO COMENTANDO, SERA ATIVO DEPOIS.
import sys, threading, base64, io, time, platform, os, requests, signal
import psutil, pyautogui, mss, soundcard as sc, socketio, warnings
from PIL import Image
import numpy as np

try:
    import pytesseract
except ImportError:
    print("[ERRO] Instale o pytesseract: pip install pytesseract")
    sys.exit(1)

warnings.filterwarnings("ignore", message=".*discontinuity.*")
pyautogui.FAILSAFE = False

try:
    import keyboard
    TECLADO_DISPONIVEL = True
except ImportError:
    TECLADO_DISPONIVEL = False

# ═══════════════════════════════════════════
# CONFIGURAÇÕES
# ═══════════════════════════════════════════
SISTEMA = platform.system()
PORTA_SERIAL = 'COM4' if SISTEMA == 'Windows' else '/dev/ttyUSB0'
pytesseract.pytesseract.tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'

CONFIG = {
    'maquina_id': sys.argv[1] if len(sys.argv) > 1 else None,
    'server_url': 'https://salagold.vip', 
    'server_port': 3000,
    'fps': 7,
    'qualidade_jpeg': 25,
    'largura_alvo': 1024,
    'porta_serial': PORTA_SERIAL, 
    'baud_rate': 115200, 
    'audio_samplerate': 24000, 
    'audio_blocksize': 1024,
    'audio_chunksize': 2048, 
    'audio_normalize': True,
    'ocr_roi': {'top': 800, 'left': 1200, 'width': 250, 'height': 60}
}

# ═══════════════════════════════════════════
# MAPEAMENTO CORRETO
# ═══════════════════════════════════════════
MAPA_TECLAS = {
    "a":"btn_aposta", "s":"btn_jogar", "n":"btn_numeros", "c":"btn_cartelas","e":"btn_extra", "h":"btn_ajuda", "p":"btn_cobrar", "9":"btn_leitura",
    "t":"btn_tab", "i":"btn_inserir", "b":"btn_b", "d":"btn_d", "f":"btn_f","g":"btn_g", "j":"btn_j", "k":"btn_k", "l":"btn_l", "m":"btn_m",
    "o":"btn_ok", "q":"btn_q", "r":"btn_r", "u":"btn_u", "v":"btn_v","w":"btn_w", "x":"btn_x", "y":"btn_y", "z":"btn_z", "0":"btn_0",
    "1":"btn_1", "2":"btn_2", "3":"btn_3", "4":"btn_4", "5":"btn_5", "6":"btn_6", "7":"btn_7", "8":"btn_8", "+":"btn_mais", "-":"btn_menos",
    "=":"btn_igual", "\b":"btn_backspace", " ":"btn_espaco", "\n":"btn_enter",
}
if not CONFIG['maquina_id']: 
    print("Uso: python script.py <MAQUINA_ID>") 
    sys.exit(1)

# ═══════════════════════════════════════════
# ESTADO GLOBAL
# ═══════════════════════════════════════════
estado = {
    'online': False, 'rodando': True, 'arduino': None,
    'sistema': platform.system(), 'ultimo_envio_video': 0
}
threads = []

# ═══════════════════════════════════════════
# SOCKET.IO CLIENT
# ═══════════════════════════════════════════
sio = socketio.Client(logger=False, engineio_logger=False)

# ═══════════════════════════════════════════
# INICIALIZAÇÃO
# ═══════════════════════════════════════════
def inicializar_hardware():
    try:
        import serial
        estado['arduino'] = serial.Serial(CONFIG['porta_serial'], CONFIG['baud_rate'], timeout=1)
        print(f'[HW] Conectado: {CONFIG["porta_serial"]}')
    except Exception as e:
        print(f'[HW] Erro: {e}')
        estado['arduino'] = None

def obter_token():
    try:
        r = requests.get(f'{CONFIG["server_url"]}:{CONFIG["server_port"]}/maquina/token/{CONFIG["maquina_id"]}', timeout=10)
        return r.json().get('token') if r.status_code == 200 else None
    except Exception:
        return None

def verificar_instancia_duplicada():
    pid_atual = os.getpid()
    script = os.path.basename(__file__)
    for proc in psutil.process_iter(['pid', 'cmdline']):
        try:
            cmd = proc.info['cmdline'] or []
            if proc.info['pid'] != pid_atual and script in ' '.join(cmd) and CONFIG['maquina_id'] in cmd:
                proc.terminate()
                proc.wait(timeout=5)
        except (psutil.NoSuchProcess, psutil.TimeoutExpired):
            pass

# ═══════════════════════════════════════════
# OCR
# ═══════════════════════════════════════════
def ler_creditos_da_tela_com_timeout(timeout_seconds=2.0):
    resultado = {'creditos': 0}
    
    def tarefa_ocr():
        try:
            with mss.MSS() as sct:
                regiao = CONFIG['ocr_roi']
                captura = sct.grab(regiao)
                img = Image.frombytes('RGB', captura.size, captura.bgra, 'raw', 'BGRX')
                img = img.convert('L')
                arr = np.array(img)
                img_bin = Image.fromarray(np.where(arr > 128, 255, 0).astype('uint8'))
                
                config = r'--psm 6 -c tessedit_char_whitelist=0123456789'
                texto = pytesseract.image_to_string(img_bin, config=config)
                digitos = ''.join(filter(str.isdigit, texto))
                resultado['creditos'] = int(digitos) if digitos else 0
        except Exception as e:
            print(f"[OCR ERRO] {e}")
    
    thread = threading.Thread(target=tarefa_ocr, daemon=True)
    thread.start()
    thread.join(timeout_seconds)
    
    if thread.is_alive():
        print(f"[OCR TIMEOUT] Excedeu {timeout_seconds}s → retornando 0")
        return 0
    
    return resultado['creditos']

# ═══════════════════════════════════════════
# ENVIO AO ARDUINO
# ═══════════════════════════════════════════
def enviar_ao_arduino(comando):
    if not (estado['arduino'] and estado['arduino'].is_open):
        print("⚠️ Arduino não disponível.")
        return False
    
    try:
        estado['arduino'].write(f"{comando}\n".encode('utf-8'))
        estado['arduino'].flush()
        print(f'📩 {comando}')
        return True
    except Exception as e:
        print(f"❌ Erro ao enviar para Arduino: {e}")
        return False

# ═══════════════════════════════════════════
# CAPTURA DE VÍDEO E ÁUDIO
# ═══════════════════════════════════════════
def processar_frame(screenshot):
    w, h = screenshot.size
    if w > CONFIG['largura_alvo']:
        ratio = h / w
        screenshot = screenshot.resize(
            (CONFIG['largura_alvo'], int(CONFIG['largura_alvo'] * ratio)),
            Image.Resampling.BILINEAR
        )
    buf = io.BytesIO()
    screenshot.save(buf, format='JPEG', quality=CONFIG['qualidade_jpeg'])
    return base64.b64encode(buf.getvalue()).decode()

def capturar_video():
    with mss.MSS() as sct:
        monitor = sct.monitors[1] if len(sct.monitors) > 1 else sct.monitors[0]
        intervalo = 1.0 / CONFIG['fps']
        while estado['rodando']:
            inicio = time.time()
            try:
                img = sct.grab(monitor)
                frame = Image.frombytes('RGB', img.size, img.bgra, 'raw', 'BGRX')
                b64 = processar_frame(frame)
                if sio.connected:
                    agora = time.time()
                    if agora - estado['ultimo_envio_video'] >= intervalo:
                        sio.emit('tela_maquina', {'maquina_id': CONFIG['maquina_id'], 'imagem': b64})
                        estado['ultimo_envio_video'] = agora
            except Exception:
                pass
            tempo = time.time() - inicio
            time.sleep(max(0, intervalo - tempo))

#def capturar_audio():
#    try:
#        mic = sc.default_microphone() if estado['sistema'] == 'Darwin' else sc.get_microphone(
#            id=str(sc.default_speaker().name), include_loopback=True
#        )
#        with mic.recorder(samplerate=CONFIG['audio_samplerate'], blocksize=CONFIG['audio_blocksize']) as rec:
#           print(f'[ÁUDIO] {CONFIG["audio_samplerate"]}Hz')
#            buffer = []
#            while estado['rodando']:
#                try:
#                    if CONFIG['audio_normalize']:
#                        max_val = np.max(np.abs(data))
#                        if max_val > 0:
#                            data = data / max_val * 0.8
#                    buffer.append(data)
#                    if len(buffer) >= 5:
#                        b64 = base64.b64encode(chunk.tobytes()).decode()
#                        if sio.connected:
#                            sio.emit('stream_audio_video', {
#                                'maquina_id': CONFIG['maquina_id'],
#                                'chunk': b64,
#                                'samplerate': CONFIG['audio_samplerate'],
#                                'channels': 1
#                            })
#                except Exception:
#                    time.sleep(0.001)
#    except Exception as e:
#        print(f'[ERRO ÁUDIO] {e}')

# ═══════════════════════════════════════════
# CAPTURA DE TECLAS 
# ═══════════════════════════════════════════
def capturar_teclas():
    if not TECLADO_DISPONIVEL:
        while estado['rodando']: time.sleep(1)
        return
    
    teclas_pressionadas = set()
    ultimo_envio = {}

    def on_press(e):
        nome_tecla = e.name.lower()
        
        if nome_tecla in MAPA_TECLAS:
            if e.event_type == 'down':
                agora = time.time()
                # Impede repetições super rápidas (debounce)
                if nome_tecla not in teclas_pressionadas and (agora - ultimo_envio.get(nome_tecla, 0)) > 0.3:
                    teclas_pressionadas.add(nome_tecla)
                    ultimo_envio[nome_tecla] = agora
                    
                    comando = MAPA_TECLAS[nome_tecla]
                    
                    # CORREÇÃO: Envia a tecla original pura pro Arduino (ex: 'n' ao invés de 'numeros')
                    enviar_ao_arduino(nome_tecla)
                    print(f'🕹️ Tecla: {nome_tecla} → Arduino: {nome_tecla}')
                    
                    # Envia a palavra traduzida pro Servidor Web atualizar os botões (ex: 'numeros')
                    if sio.connected:
                        sio.emit('executar_js', {
                            'funcao': 'ClickBtn',
                            'args': [CONFIG['maquina_id'], comando]
                        })
                        print(f'📤 Recebido: {comando}')
                        
            elif e.event_type == 'up':
                if nome_tecla in teclas_pressionadas:
                    teclas_pressionadas.remove(nome_tecla)

    keyboard.hook(on_press)
    while estado['rodando']: time.sleep(1)

# ═══════════════════════════════════════════
# SOCKET HANDLERS
# ═══════════════════════════════════════════
def configurar_socket():
    @sio.event
    def connect():
        estado['online'] = True
        estado['ultimo_envio_video'] = 0
        print('[CONECTADO] Registrando...')
        sio.emit('entrar_maquina', {'maquina_id': CONFIG['maquina_id']}, callback=on_registro)

    def on_registro(resp):
        if resp and resp.get('status') == 'success':
            print(f'[ONLINE] Máquina {CONFIG["maquina_id"]} registrada!')
        else:
            print(f'[ERRO] Registro negado: {resp}')
            estado['rodando'] = False

    @sio.on('conexao_duplicada')
    def on_dup(data):
        print('[INFO] Conexão duplicada')
        estado['rodando'] = False

    @sio.event
    def disconnect():
        estado['online'] = False
        print('[DESCONECTADO]')

    @sio.on('executar_clique')
    def on_click(data):
        try:
            x = int(float(data.get('x', 0)) * pyautogui.size()[0])
            y = int(float(data.get('y', 0)) * pyautogui.size()[1])
            pyautogui.click(x, y)
        except Exception:
            pass

    @sio.on('acionar_botao_maquina')
    def on_botao(data):
        tecla = data.get('tecla', '')
        if tecla:
            enviar_ao_arduino(tecla)

    @sio.on('creditos_atualizados')
    def on_creditos(data):
        try:
            creditos = data.get('creditos', 0)
            valor = data.get('valor', 0)
            print(f'💳 R$ {valor} → {creditos} pulsos')
            if creditos > 0:
                enviar_ao_arduino(f"CRED:{creditos}")
        except Exception as e:
            print(f'❌ ERRO: {e}')

    @sio.on('ping_maquina')
    def on_ping_maquina(data):
        try:
            return {'ok': True, 'timestamp': data.get('timestamp', 0)}
        except Exception as e:
            print(f"[PING ERRO] {e}")
            return {'ok': False}

# ═══════════════════════════════════════════
# EXECUTAR JS - RECEBE DO SERVIDOR
# ═══════════════════════════════════════════
@sio.on('executar_js')
def on_executar_js(data):
    try:
        if not data or 'args' not in data:
            return
        args = data.get('args', [])
        if len(args) < 2:
            return
        
        maquina_id, comando = args[0], args[1]
        zerar_mquina = '9'
        print(f'🔄 WebSocket recebido: {comando}')
        
        # ─── Se for 'cobrar', faz 3 leituras até confirmar ───
        if comando == 'cobrar':
            print(f"[COBRANÇA] Iniciando confirmação com leituras...")
            valores_confirmados = None
            tentativas = 0
            
            while valores_confirmados is None and tentativas < 5:
                tentativas += 1
                print(f"  Ciclo {tentativas}: fazendo 3 leituras...")
                valores = [ler_creditos_da_tela_com_timeout(2.0) for _ in range(3)]
                for i, v in enumerate(valores, 1):
                    print(f"    Leitura {i}: {v}")
                    time.sleep(0.3)
                
                if all(v == valores[0] for v in valores):
                    valores_confirmados = valores[0]
                    print(f"✅ Leituras consistentes: {valores_confirmados} créditos")
                else:
                    print(f"  ❌ Leituras divergentes: {valores}. Tentando novamente...")
            
            if valores_confirmados is not None:
                print(f"✅ Valor confirmado: {valores_confirmados} créditos. Enviando comando...")
                enviar_ao_arduino(zerar_maquina)  # tecla 9
            else:
                print(f"❌ Não foi possível confirmar após {tentativas} tentativas. Cobrança cancelada.")
                return
        
    except Exception as e:
        print(f'❌ ERRO: {e}')

# ═══════════════════════════════════════════
# ENCERRAMENTO
# ═══════════════════════════════════════════
def encerrar(signum=None, frame=None):
    print('\n[ENCERRANDO]')
    estado['rodando'] = False
    if sio.connected: sio.disconnect()
    if estado['arduino'] and estado['arduino'].is_open:
        estado['arduino'].close()
    sys.exit(0)

# ═══════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════
if __name__ == '__main__':
    signal.signal(signal.SIGINT, encerrar)
    signal.signal(signal.SIGTERM, encerrar)

    print(f'[INICIANDO] Máquina: {CONFIG["maquina_id"]} | OS: {estado["sistema"]}')
    verificar_instancia_duplicada()
    inicializar_hardware()
    configurar_socket()

    componentes = [
        ('Video', capturar_video),
       # ('Audio', capturar_audio),
        ('Teclas', capturar_teclas)
    ]
    for nome, func in componentes:
        t = threading.Thread(target=func, daemon=True, name=nome)
        t.start()
        threads.append(t)

    print(f'[RODANDO] Threads: {len(threads)}')

    while True:
        try:
            token = obter_token()
            if not token:
                print('[FALHA] Token não obtido. Tentando em 10s...')
                time.sleep(10)
                continue

            if not sio.connected:
                print(f'[CONECTANDO] Máquina {CONFIG["maquina_id"]}...')
                sio.connect(
                    f'{CONFIG["server_url"]}:{CONFIG["server_port"]}',
                    auth={'token': token}
                )

            estado['rodando'] = True
            sio.wait()

        except Exception as e:
            erro = str(e)
            print(f'[ERRO CONEXÃO] {erro}')
            if sio.connected: sio.disconnect()

            if "Acesso Negado" in erro:
                print('\n' + '═'*60)
                print(f'[ALERTA] ID {CONFIG["maquina_id"]} já online!')
                novo = input('Novo ID (ou Enter para sair): ').strip()
                if novo:
                    CONFIG["maquina_id"] = novo
                    print(f'[SISTEMA] ID alterado para {novo}')
                    time.sleep(2)
                    continue
                else:
                    encerrar()

        time.sleep(5)
        print('[SISTEMA] Reconectando em 5s...')