package sd.dashboard;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import sd.config.SimulationConfig;

/**
 * Servidor central de agregação de telemetria e estatísticas.
 * <p>
 * Este componente atua como o nó de monitorização do sistema distribuído. 
 * Implementa uma arquitetura de servidor concorrente utilizando um {@link ExecutorService} 
 * (Thread Pool) para gerir múltiplas conexões de entrada simultâneas provenientes 
 * das Interseções, Coordenador e Nó de Saída.
 * <p>
 * Suporta dois modos de operação:
 * <ul>
 * <li><b>Headless (CLI):</b> Renderização periódica de métricas no terminal (stdout).</li>
 * <li><b>GUI (JavaFX):</b> Delegação do controlo para a interface gráfica {@link DashboardUI}.</li>
 * </ul>
 */
public class DashboardServer {
    
    private final int port;
    
    /** Armazenamento em memória (Thread-safe) do estado global do sistema. */
    private final DashboardStatistics statistics;
    
    /** Pool de threads para isolamento de falhas e gestão de recursos de I/O. */
    private final ExecutorService clientHandlerPool;
    
    /** Flag atómica para controlo seguro do ciclo de vida do servidor. */
    private final AtomicBoolean running;
    
    private ServerSocket serverSocket;
    
    /**
     * Ponto de entrada (Bootstrap) da aplicação de monitorização.
     * <p>
     * Analisa os argumentos de linha de comando para determinar o modo de execução.
     * Se a flag {@code --gui} ou {@code -g} estiver presente, inicia o subsistema JavaFX.
     * Caso contrário, inicia o modo servidor de terminal padrão.
     *
     * @param args Argumentos de CLI (ex: caminho do config, flags de modo).
     */
    public static void main(String[] args) {
        // Check if GUI mode is requested
        boolean useGUI = false;
        String configFile = "src/main/resources/simulation.properties";
        
        for (int i = 0; i < args.length; i++) {
            if (args[i].equals("--gui") || args[i].equals("-g")) {
                useGUI = true;
            } else {
                configFile = args[i];
            }
        }
        
        if (useGUI) {
            // Launch JavaFX UI
            System.out.println("Launching Dashboard with JavaFX GUI...");
            DashboardUI.main(args);
        } else {
            // Traditional terminal mode
            System.out.println("=".repeat(60));
            System.out.println("DASHBOARD SERVER - DISTRIBUTED TRAFFIC SIMULATION");
            System.out.println("=".repeat(60));
            
            try {
                System.out.println("Loading configuration from: " + configFile);
                
                SimulationConfig config = new SimulationConfig(configFile);
                DashboardServer server = new DashboardServer(config);
                
                // Start the server
                System.out.println("\n" + "=".repeat(60));
                server.start();
                
                // Keep running until interrupted
                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
                    System.out.println("\n\nShutdown signal received...");
                    server.stop();
                }));
                
                // Display statistics periodically
                server.displayLoop();
                
            } catch (IOException e) {
                System.err.println("Failed to start Dashboard Server: " + e.getMessage());
                System.exit(1);
            }
        }
    }
    
    /**
     * Inicializa a infraestrutura do servidor.
     *
     * @param config A configuração carregada contendo a porta de escuta.
     */
    public DashboardServer(SimulationConfig config) {
        this.port = config.getDashboardPort();
        this.statistics = new DashboardStatistics();
        // Fixed pool limita o consumo de recursos, prevenindo exaustão sob carga alta
        this.clientHandlerPool = Executors.newFixedThreadPool(10);
        this.running = new AtomicBoolean(false);
    }
    
    /**
     * Inicia a escuta por conexões (Bind & Listen) e a thread de despacho.
     *
     * @throws IOException Se a porta já estiver em uso ou ocorrer erro de bind.
     */
    public void start() throws IOException {
        if (running.get()) {
            System.out.println("Dashboard Server is already running.");
            return;
        }
        
        serverSocket = new ServerSocket(port);
        running.set(true);
        
        System.out.println("Dashboard Server started on port " + port);
        System.out.println("Waiting for statistics updates from simulation processes...");
        System.out.println("=".repeat(60));
        
        Thread acceptThread = new Thread(this::acceptConnections, "DashboardServer-Accept");
        acceptThread.setDaemon(false);
        acceptThread.start();
    }
    
    /**
     * Loop principal de aceitação de conexões (Dispatcher).
     * <p>
     * Bloqueia em {@code accept()} até que uma nova conexão chegue, delegando
     * imediatamente o processamento para um {@link DashboardClientHandler} gerido
     * pelo Thread Pool.
     */
    private void acceptConnections() {
        while (running.get()) {
            try {
                Socket clientSocket = serverSocket.accept();
                System.out.println("[Connection] New client connected: " + 
                    clientSocket.getInetAddress().getHostAddress() + ":" + clientSocket.getPort());
                
                clientHandlerPool.execute(new DashboardClientHandler(clientSocket, statistics));
                
            } catch (IOException e) {
                if (running.get()) {
                    System.err.println("[Error] Failed to accept client connection: " + e.getMessage());
                }
            }
        }
    }
    
    /**
     * Ciclo de renderização de métricas para o modo CLI (Headless).
     * Atualiza o ecrã a cada 5 segundos.
     */
    @SuppressWarnings("BusyWait")
    private void displayLoop() {
        final long DISPLAY_INTERVAL_MS = 5000;
        
        while (running.get()) {
            try {
                Thread.sleep(DISPLAY_INTERVAL_MS);
                displayStatistics();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                break;
            }
        }
    }
    
    /**
     * Renderiza o snapshot atual das estatísticas no stdout.
     */
    public void displayStatistics() {
        System.out.println("\n" + "=".repeat(60));
        System.out.println("REAL-TIME SIMULATION STATISTICS");
        System.out.println("=".repeat(60));
        statistics.display();
        System.out.println("=".repeat(60));
    }
    
    /**
     * Procedimento de encerramento gracioso (Graceful Shutdown).
     * <p>
     * 1. Altera flag de execução.
     * 2. Fecha o socket do servidor para desbloquear a thread de aceitação.
     * 3. Força o encerramento do pool de threads de clientes.
     */
    public void stop() {
        if (!running.get()) {
            return;
        }
        
        System.out.println("\nStopping Dashboard Server...");
        running.set(false);
        
        try {
            if (serverSocket != null && !serverSocket.isClosed()) {
                serverSocket.close();
            }
        } catch (IOException e) {
            System.err.println("Error closing server socket: " + e.getMessage());
        }
        
        clientHandlerPool.shutdownNow();
        System.out.println("Dashboard Server stopped.");
    }
    
    public DashboardStatistics getStatistics() {
        return statistics;
    }
    
    public boolean isRunning() {
        return running.get();
    }
}