package sd.model;

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

/**
 * Representa uma interseção na simulação de tráfego.
 * 
 * <p>Uma interseção funciona como um nó central da rede. Não controla lógica diretamente,
 * mas gere um conjunto de semáforos ({@link TrafficLight}).</p>
 * 
 * <p>Responsabilidades principais:</p>
 * <ul>
 *   <li>Manter um {@link TrafficLight} para cada direção (Norte, Este, etc.)</li>
 *   <li>Gerir uma tabela de encaminhamento que mapeia destinos para direções</li>
 *   <li>Receber veículos e colocá-los na fila do semáforo correto</li>
 *   <li>Acompanhar estatísticas agregadas do tráfego</li>
 * </ul>
 */
public class Intersection {

    /** Identificador único da interseção (ex: "Cr1", "Cr2") */
    private final String id;
    
    /**
     * Mapa com todos os semáforos desta interseção.
     * Chave: Direção (String, ex: "Norte", "Este")
     * Valor: Objeto {@link TrafficLight} correspondente
     */
    private final Map<String, TrafficLight> trafficLights;
    
    /**
     * Tabela de encaminhamento da interseção.
     * Chave: Próximo destino (String, ex: "Cr3", "S" para saída)
     * Valor: Direção que o veículo deve tomar nesta interseção
     */
    private final Map<String, String> routing;
    /** Número total de veículos recebidos por esta interseção */
    private int totalVehiclesReceived;
    
    /** Número total de veículos que partiram desta interseção */
    private int totalVehiclesSent;
    
    /** Média acumulada do tempo de espera dos veículos nesta interseção */
    private double averageWaitingTime;

    /**
     * Cria uma nova interseção.
     * Inicializa mapas vazios para semáforos e encaminhamento.
     *
     * @param id identificador único da interseção (ex: "Cr1")
     */
    public Intersection(String id) {
        this.id = id;
        this.trafficLights = new HashMap<>();
        this.routing = new HashMap<>();
        this.totalVehiclesReceived = 0;
        this.totalVehiclesSent = 0;
        this.averageWaitingTime = 0.0;
    }

    /**
     * Regista um novo semáforo nesta interseção.
     * O semáforo é mapeado pela sua direção.
     *
     * @param trafficLight o semáforo a adicionar
     */
    public void addTrafficLight(TrafficLight trafficLight) {
        trafficLights.put(trafficLight.getDirection(), trafficLight);
    }

    /**
     * Define uma regra de encaminhamento para esta interseção.
     * 
     * <p>Por exemplo, {@code configureRoute("Cr3", "Este")} significa:
     * "Qualquer veículo que chegue aqui com destino 'Cr3' deve ser enviado
     * para a fila do semáforo da direção Este."</p>
     *
     * @param nextDestination ID da próxima interseção ou saída (ex: "Cr3", "S")
     * @param direction direção (e respetivo semáforo) a usar nesta interseção
     */
    public void configureRoute(String nextDestination, String direction) {
        routing.put(nextDestination, direction);
    }

    /**
     * Recebe um novo veículo e coloca-o na fila do semáforo apropriado.
     * A direção é escolhida com base na tabela de encaminhamento.
     * 
     * @param vehicle o veículo que está a chegar a esta interseção
     * @param simulationTime o tempo de simulação atual (em segundos)
     */
    public void receiveVehicle(Vehicle vehicle, double simulationTime) {
        totalVehiclesReceived++;
        
        String nextDestination = vehicle.getCurrentDestination();
        
        // Check if vehicle reached final destination
        if (nextDestination == null) {
            System.out.printf("[%s] Vehicle %s reached final destination%n", 
                this.id, vehicle.getId());
            return;
        }
        
        String direction = routing.get(nextDestination);

        if (direction != null && trafficLights.containsKey(direction)) {
            // Found a valid route and light, add vehicle to the queue
            trafficLights.get(direction).addVehicle(vehicle, simulationTime);
        } else {
            // Routing error: No rule for this destination or no light for that direction
            System.err.printf(
                "Routing error at %s: could not place vehicle %s (destination: %s, found direction: %s)%n",
                this.id, vehicle.getId(), nextDestination, direction
            );
        }
    }    /**
     * Retorna a direção que um veículo deve tomar para alcançar um destino.
     *
     * @param destination o próximo destino (ex: "Cr3", "S")
     * @return a direção (ex: "Este"), ou null se não houver rota configurada
     */
    public String getDirectionForDestination(String destination) {
        return routing.get(destination);
    }

    /**
     * Retorna o semáforo que controla uma determinada direção.
     *
     * @param direction a direção (ex: "Norte")
     * @return o objeto {@link TrafficLight}, ou null se não existir
     */
    public TrafficLight getTrafficLight(String direction) {
        return trafficLights.get(direction);
    }

    /**
     * Retorna uma lista com todos os semáforos desta interseção.
     *
     * @return uma nova {@link List} com todos os semáforos
     */
    public List<TrafficLight> getTrafficLights() {
        return new ArrayList<>(trafficLights.values());
    }

    /**
     * Retorna o número total de veículos em fila em todos os semáforos.
     * Usa Java Stream API para somar os tamanhos de todas as filas.
     *
     * @return a soma dos tamanhos de todas as filas
     */
    public int getTotalQueueSize() {
        return trafficLights.values().stream()
                .mapToInt(TrafficLight::getQueueSize)
                .sum();
    }

    /**
     * @return o identificador único desta interseção
     */
    public String getId() {
        return id;
    }

    /**
     * @return o número total de veículos que chegaram a esta interseção
     */
    public int getTotalVehiclesReceived() {
        return totalVehiclesReceived;
    }

    /**
     * @return o número total de veículos que partiram desta interseção
     */
    public int getTotalVehiclesSent() {
        return totalVehiclesSent;
    }

    /**
     * Incrementa o contador de veículos que partiram com sucesso.
     * Tipicamente chamado após um veículo completar a travessia.
     */
    public void incrementVehiclesSent() {
        totalVehiclesSent++;
    }

    /**
     * @return a média do tempo de espera dos veículos nesta interseção
     */
    public double getAverageWaitingTime() {
        return averageWaitingTime;
    }

    /**
     * Atualiza a média do tempo de espera com uma nova amostra.
     * Usa a fórmula: Nova Média = (Média Antiga * (N-1) + Novo Valor) / N
     *
     * @param newTime tempo de espera (em segundos) do veículo que acabou de partir
     */
    public void updateAverageWaitingTime(double newTime) {
        if (totalVehiclesSent > 0) {
            averageWaitingTime = (averageWaitingTime * (totalVehiclesSent - 1) + newTime)
                               / totalVehiclesSent;
        } else if (totalVehiclesSent == 1) {
            averageWaitingTime = newTime;
        }
    }

    /**
     * @return representação textual do estado atual da interseção
     */
    @Override
    public String toString() {
        return String.format(
            "Intersection{id='%s', lights=%d, queues=%d, received=%d, sent=%d}",
            id,
            trafficLights.size(),
            getTotalQueueSize(),
            totalVehiclesReceived,
            totalVehiclesSent
        );
    }
}