#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Sistema de Atendimento Integral - Script de Integração Raspberry Pi GPIO (Versão GPIOZERO)
------------------------------------------------------------------------------------------
Este script é uma alternativa moderna e recomendada para Raspberry Pi 4 e Raspberry Pi 5
utilizando a biblioteca oficial 'gpiozero'. O 'gpiozero' é mais estável, gerencia a CPU
e resistores pull-up internos de maneira muito mais eficiente, e previne o clássico erro:
"RuntimeError: Failed to add edge detection".

Instalação de Dependências na Raspberry Pi:
-------------------------------------------
$ sudo apt-get update
$ sudo apt-get install python3-pip python3-gpiozero mpg123 espeak -y
$ pip3 install requests
"""

import time
import sys
import threading
import subprocess
import requests

try:
    from gpiozero import Button, LED
    GPIOZERO_DISPONIVEL = True
except ImportError:
    GPIOZERO_DISPONIVEL = False
    print("\n[ALERTA] Biblioteca 'gpiozero' não instalada!")
    print("Para utilizar este script nativamente na Raspberry Pi, instale-a rodando:")
    print("--> sudo apt-get install python3-gpiozero\n")

# ==========================================
# CONFIGURAÇÕES DA INTEGRAÇÃO
# ==========================================
API_BASE_URL = "http://SEU_IP_OU_DOMINIO/php-mysql-code"

# MAPEAMENTO DE HARDWARE (GPIO no padrão BCM - Pinos de sinal físico)
PIN_BOTAO_SENHA_NORMAL     = 17   # Pino físico 11 -> botão para o totem imprimir Normal
PIN_BOTAO_SENHA_PRIORIDADE = 27   # Pino físico 13 -> botão para o totem imprimir Prioritário
PIN_BOTAO_PROXIMO_NORMAL   = 22   # Pino físico 15 -> botão no guichê para chamar Próximo Normal
PIN_BOTAO_PROXIMO_PRIORIDADE = 23 # Pino físico 16 -> botão no guichê para chamar Próximo Prioritário

PIN_ALERTA_LED_RELE        = 18   # Pino físico 12 -> Saída para LED gigante ou Relé de Giroflex
PIN_BUZZER_SONORO          = 24   # Pino físico 18 -> Saída para Buzzer ativo de 5v

# CONFIGURAÇÕES DO OPERADOR GERAL
NUMERO_GUICHE_PADRAO = "Guichê 01"
ATIVAR_VOZ_TTS = True       # Syntetiza voz ("Senha ... Vá para o ...") via espeak
BOUNCE_TIME_SEGUNDOS = 0.4  # No gpiozero, o debounce (bounce_time) é medido em segundos!

# ==========================================
# CLIENTE DE COMUNICAÇÃO DA API do PHP
# ==========================================

class RaspberryQueueClient:
    def __init__(self):
        self.api_url = f"{API_BASE_URL}/api.php"
        self.ultimo_ticket_chamado_id = None
        self.rodando = True
        self.led_rele = None
        self.buzzer = None

    def inicializar_saidas(self):
        if GPIOZERO_DISPONIVEL:
            try:
                # Inicializa as saídas utilizando gpiozero
                self.led_rele = LED(PIN_ALERTA_LED_RELE)
                self.buzzer = LED(PIN_BUZZER_SONORO)
            except Exception as e:
                print(f"[Aviso] Não foi possível ativar saídas físicas: {e}")

    def requisitar(self, params, payload=None, metodo="GET"):
        try:
            if metodo == "POST":
                res = requests.post(self.api_url, params=params, json=payload, timeout=5)
            else:
                res = requests.get(self.api_url, params=params, timeout=5)
            
            if res.status_code == 200:
                try:
                    return res.json()
                except ValueError:
                    return res.text
            else:
                print(f"[ERRO API] Código HTTP inválido: {res.status_code}")
                return None
        except Exception as e:
            print(f"[ERRO REDE] Não foi possível conectar ao servidor: {e}")
            return None

    # Totem (Emissão)
    def emitir_senha_normal(self):
        print("\n[TOTEM - GPIOZERO] Solicitando Senha Normal...")
        resposta = self.requisitar(params={"action": "gerar"}, payload={"type": "Normal"}, metodo="POST")
        if resposta and "ticket" in resposta:
            num = resposta["ticket"].get("numero", "---")
            print(f"[TOTEM - GPIOZERO] Senha Normal Gerada: {num}")
            self.imprimir_fisicamente(num, "NORMAL")
        else:
            print("[TOTEM - GPIOZERO] Erro ao obter resposta do servidor.")

    def emitir_senha_prioridade(self):
        print("\n[TOTEM - GPIOZERO] Solicitando Senha Prioritária...")
        resposta = self.requisitar(params={"action": "gerar"}, payload={"type": "Prioritário"}, metodo="POST")
        if resposta and "ticket" in resposta:
            num = resposta["ticket"].get("numero", "---")
            print(f"[TOTEM - GPIOZERO] Senha Prioridade Gerada: {num}")
            self.imprimir_fisicamente(num, "PRIORIDADE")
        else:
            print("[TOTEM - GPIOZERO] Erro ao obter resposta do servidor.")

    def imprimir_fisicamente(self, numero_senha, tipo_senha):
        print(f"----- IMPRESSÃO DE SENHA -----")
        print(f"  {tipo_senha.upper()}")
        print(f"  SENHA: {numero_senha}")
        print(f"  Aguarde sua chamada na TV.")
        print(f"  Data/Hora: {time.strftime('%d/%m/%Y %H:%M:%S')}")
        print(f"------------------------------")

    # Guichê (Atendimento)
    def chamar_proximo_normal(self):
        print(f"\n[GUICHÊ - GPIOZERO] Próximo Normal solicitado para {NUMERO_GUICHE_PADRAO}")
        resposta = self.requisitar(params={"action": "chamar"}, payload={"desk": NUMERO_GUICHE_PADRAO, "typeSelection": "normalOnly"}, metodo="POST")
        if resposta and "ticket" in resposta:
            ticket = resposta["ticket"]
            print(f"[GUICHÊ - GPIOZERO] Chamado com sucesso: {ticket.get('numero')}")
        else:
            print("[GUICHÊ - GPIOZERO] Fila Normal vazia.")

    def chamar_proximo_prioridade(self):
        print(f"\n[GUICHÊ - GPIOZERO] Próximo Prioritário solicitado para {NUMERO_GUICHE_PADRAO}")
        resposta = self.requisitar(params={"action": "chamar"}, payload={"desk": NUMERO_GUICHE_PADRAO, "typeSelection": "prioritarioOnly"}, metodo="POST")
        if resposta and "ticket" in resposta:
            ticket = resposta["ticket"]
            print(f"[GUICHÊ - GPIOZERO] Chamado com sucesso: {ticket.get('numero')}")
        else:
            print("[GUICHÊ - GPIOZERO] Fila Prioritária vazia.")

    # Alertas Físicos
    def acionar_alertas_hardware(self, numero_ticket, guiche_chamado):
        print(f"\n>>> ATENÇÃO: SENHA {numero_ticket} CHAMADA PARA O {guiche_chamado}! <<<")
        
        # 1. Piscar LED/Relé
        def piscar_led_rele():
            if self.led_rele:
                for _ in range(8):
                    self.led_rele.on()
                    time.sleep(0.15)
                    self.led_rele.off()
                    time.sleep(0.12)
        
        # 2. Tocar Buzzer
        def tocar_buzzer():
            if self.buzzer:
                for _ in range(3):
                    self.buzzer.on()
                    time.sleep(0.1)
                    self.buzzer.off()
                    time.sleep(0.05)
                    self.buzzer.on()
                    time.sleep(0.1)
                    self.buzzer.off()
                    time.sleep(0.4)

        threading.Thread(target=piscar_led_rele, daemon=True).start()
        threading.Thread(target=tocar_buzzer, daemon=True).start()

        # 3. Voz em Português
        if ATIVAR_VOZ_TTS:
            tipo_letra = "Normal" if numero_ticket.startswith("N") else "Prioritária"
            num_clean = numero_ticket[1:]
            try:
                num_int = int(num_clean)
                numero_pronunciado = str(num_int)
            except ValueError:
                numero_pronunciado = numero_ticket
                
            texto_voz = f"Atenção. Senha {tipo_letra}, número {numero_pronunciado}. Compareça ao {guiche_chamado}."
            threading.Thread(target=self.falar_texto, args=(texto_voz,), daemon=True).start()

    def falar_texto(self, texto):
        try:
            print(f"[TTS] Sintetizando: '{texto}'")
            subprocess.run(["espeak", "-v", "pt-br", "-s", "145", "-p", "45", texto], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except Exception as e:
            print(f"[TTS] Erro espeak: {e}")

    # Polling em Loop
    def loop_monitoramento_api(self):
        print("[MONITOR - GPIOZERO] Escutando mudanças no painel PHP...")
        while self.rodando:
            estado = self.requisitar(params={"action": "estado"})
            if estado and isinstance(estado, dict):
                ticket_ativo = estado.get("activeTicket")
                if ticket_ativo:
                    ticket_id = ticket_ativo.get("ticketId")
                    numero = ticket_ativo.get("numero")
                    guiche = ticket_ativo.get("desk")
                    
                    if ticket_id != self.ultimo_ticket_chamado_id:
                        if self.ultimo_ticket_chamado_id is not None:
                            self.acionar_alertas_hardware(numero, guiche)
                        self.ultimo_ticket_chamado_id = ticket_id
            time.sleep(1.0)

# ==========================================
# EXECUÇÃO PRINCIPAL
# ==========================================

if __name__ == "__main__":
    print("=====================================================================")
    print("      SERVIÇO RASPBERRY PI GPIO (VERSÃO GPIOZERO) PARA FILAS         ")
    print("=====================================================================")
    print(f"Endereço do Servidor Alvo: {API_BASE_URL}\n")

    if not GPIOZERO_DISPONIVEL:
        print("[CRÍTICO] Este script exige a biblioteca 'gpiozero' instalada.")
        print("Para computadores comuns (Windows/Mac/Linux sem pinos), use a emulação.")
        print("Finalizando execução.")
        sys.exit(1)

    client = RaspberryQueueClient()
    client.inicializar_saidas()

    # Configuração de botões com resistores integrados de PULL-UP via GPIOZERO
    # O Gpiozero lida com resistores internos de Pull-Up, debounce e threads de eventos nativamente.
    print("[HARDWARE] Configurando botões físicos...")
    btn_normal = Button(PIN_BOTAO_SENHA_NORMAL, pull_up=True, bounce_time=BOUNCE_TIME_SEGUNDOS)
    btn_prioridade = Button(PIN_BOTAO_SENHA_PRIORIDADE, pull_up=True, bounce_time=BOUNCE_TIME_SEGUNDOS)
    btn_prox_normal = Button(PIN_BOTAO_PROXIMO_NORMAL, pull_up=True, bounce_time=BOUNCE_TIME_SEGUNDOS)
    btn_prox_prioridade = Button(PIN_BOTAO_PROXIMO_PRIORIDADE, pull_up=True, bounce_time=BOUNCE_TIME_SEGUNDOS)

    # Atribuir os callbacks de clique simples
    btn_normal.when_pressed = client.emitir_senha_normal
    btn_prioridade.when_pressed = client.emitir_senha_prioridade
    btn_prox_normal.when_pressed = client.chamar_proximo_normal
    btn_prox_prioridade.when_pressed = client.chamar_proximo_prioridade

    print("[HARDWARE] Todos os botões mapeados. Inicializando Polling...")
    
    try:
        # Iniciar thread do visor a cada 1 segundo
        thread_escuta = threading.Thread(target=client.loop_monitoramento_api, daemon=True)
        thread_escuta.start()

        print("\n[OK] Script rodando com GPIOZERO com sucesso!")
        print("Pressione Ctrl+C para encerrar.")
        
        while True:
            time.sleep(1)
            
    except KeyboardInterrupt:
        print("\n[SISTEMA] Encerrando conexões e finalizando...")
    finally:
        client.rodando = False
        print("[SISTEMA] Portas GPIO desativadas de forma limpa. Até logo!")
