package sd.routing;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * Implementação da política de roteamento por menor congestionamento.
 * 
 * <p>Esta política escolhe dinamicamente a rota que passa pelos cruzamentos
 * menos congestionados, com base no tamanho atual das filas em cada interseção.
 * É uma política dinâmica que adapta as decisões ao estado da rede.</p>
 * 
 * <p>Objetivo: Distribuir o tráfego pela rede, evitando bottlenecks e
 * minimizando o tempo de espera total.</p>
 * 
 * <p><strong>Algoritmo:</strong></p>
 * <ol>
 *   <li>Para cada rota possível, calcula a carga total (soma das filas)</li>
 *   <li>Escolhe a rota com menor carga total</li>
 *   <li>Em caso de empate ou falta de informação, usa a rota mais curta</li>
 * </ol>
 */
public class LeastCongestedRouteSelector implements RouteSelector {
    
    /** Rotas possíveis a partir do ponto de entrada E1 */
    private final List<List<String>> e1Routes;
    /** Rotas possíveis a partir do ponto de entrada E2 */
    private final List<List<String>> e2Routes;
    /** Rotas possíveis a partir do ponto de entrada E3 */
    private final List<List<String>> e3Routes;
    
    /**
     * Cria um novo seletor de rotas baseado em menor congestionamento.
     */
    public LeastCongestedRouteSelector() {
        this.e1Routes = new ArrayList<>();
        this.e2Routes = new ArrayList<>();
        this.e3Routes = new ArrayList<>();
        initializeRoutes();
    }
    
    /**
     * Inicializa as rotas possíveis para cada ponto de entrada.
     */
    private void initializeRoutes() {
        // Rotas de E1 (entrada Norte)
        e1Routes.add(Arrays.asList("Cr1", "Cr4", "Cr5", "S"));
        e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr5", "S"));
        e1Routes.add(Arrays.asList("Cr1", "Cr2", "Cr3", "S"));
        
        // Rotas de E2 (entrada Oeste)
        e2Routes.add(Arrays.asList("Cr2", "Cr5", "S"));
        e2Routes.add(Arrays.asList("Cr2", "Cr3", "S"));
        e2Routes.add(Arrays.asList("Cr2", "Cr1", "Cr4", "Cr5", "S"));
        
        // Rotas de E3 (entrada Sul)
        e3Routes.add(Arrays.asList("Cr3", "S"));
        e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr5", "S"));
        e3Routes.add(Arrays.asList("Cr3", "Cr2", "Cr1", "Cr4", "Cr5", "S"));
    }
    
    @Override
    public List<String> selectRoute(String entryPoint, Map<String, Integer> queueSizes) {
        List<List<String>> availableRoutes = getRoutesForEntryPoint(entryPoint);
        
        // Se não temos informação sobre filas, usa a rota mais curta como fallback
        if (queueSizes == null || queueSizes.isEmpty()) {
            return selectShortestRoute(availableRoutes);
        }
        
        // Calcula a carga de cada rota e escolhe a menos congestionada
        List<String> bestRoute = null;
        int minLoad = Integer.MAX_VALUE;
        
        for (List<String> route : availableRoutes) {
            int routeLoad = calculateRouteLoad(route, queueSizes);
            
            if (routeLoad < minLoad) {
                minLoad = routeLoad;
                bestRoute = route;
            }
        }
        
        // Fallback: se não conseguimos calcular carga, usa a primeira rota
        if (bestRoute == null) {
            bestRoute = availableRoutes.get(0);
        }
        
        return new ArrayList<>(bestRoute);
    }
    
    /**
     * Calcula a carga total de uma rota (soma do tamanho das filas em todos os cruzamentos).
     * 
     * @param route rota a avaliar
     * @param queueSizes mapa com tamanho das filas por interseção
     * @return carga total da rota (soma das filas)
     */
    private int calculateRouteLoad(List<String> route, Map<String, Integer> queueSizes) {
        int totalLoad = 0;
        
        for (String intersection : route) {
            // Ignora "S" (saída) e apenas conta cruzamentos reais
            if (!intersection.equals("S") && queueSizes.containsKey(intersection)) {
                totalLoad += queueSizes.get(intersection);
            }
        }
        
        return totalLoad;
    }
    
    /**
     * Seleciona a rota mais curta (menor número de nós) como fallback.
     * 
     * @param routes lista de rotas disponíveis
     * @return a rota mais curta
     */
    private List<String> selectShortestRoute(List<List<String>> routes) {
        List<String> shortest = routes.get(0);
        
        for (List<String> route : routes) {
            if (route.size() < shortest.size()) {
                shortest = route;
            }
        }
        
        return new ArrayList<>(shortest);
    }
    
    /**
     * Obtém as rotas disponíveis para um ponto de entrada.
     * 
     * @param entryPoint ponto de entrada (E1, E2 ou E3)
     * @return lista de rotas disponíveis
     */
    private List<List<String>> getRoutesForEntryPoint(String entryPoint) {
        switch (entryPoint.toUpperCase()) {
            case "E1":
                return e1Routes;
            case "E2":
                return e2Routes;
            case "E3":
                return e3Routes;
            default:
                System.err.printf("Unknown entry point: %s, defaulting to E1%n", entryPoint);
                return e1Routes;
        }
    }
}
