package sd.model;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Representa um semáforo numa interseção.
 * 
 * <p>Cada semáforo controla uma direção específica e mantém uma fila de veículos à espera.
 * Alterna entre os estados VERDE e VERMELHO de acordo com a temporização configurada.</p>
 * 
 * <p><strong>Thread-safety:</strong> Usa locks para permitir acesso concorrente seguro entre
 * a thread de processamento de eventos e as threads de I/O de rede.</p>
 */
public class TrafficLight {

    /** Identificador único do semáforo (ex: "Cr1-N") */
    private final String id;

    /** Direção que este semáforo controla (ex: "Norte", "Sul") */
    private final String direction;

    /** Estado atual do semáforo (VERDE ou VERMELHO) */
    private TrafficLightState state;

    /** Fila de veículos à espera neste semáforo */
    private final Queue<Vehicle> queue;

    /**
     * Lock para proteger o estado mutável ({@link #queue} e {@link #state})
     * de acesso concorrente.
     */
    private final Lock lock;

    /** Variável de condição para sinalizar adição de veículos (uso futuro) */
    private final Condition vehicleAdded;

    /** Variável de condição para sinalizar que o semáforo ficou verde (uso futuro) */
    private final Condition lightGreen;

    /** Duração (segundos) que o semáforo permanece VERDE */
    private double greenTime;

    /** Duração (segundos) que o semáforo permanece VERMELHO */
    private double redTime;

    /** Número total de veículos processados por este semáforo */
    private int totalVehiclesProcessed;

    /**
     * Regista quando os veículos chegam ao semáforo para cálculo do tempo de espera.
     * Mapeia ID do veículo para tempo de simulação de chegada (segundos).
     */
    private final Map<String, Double> vehicleArrivalTimes;

    /**
     * Cria um novo semáforo.
     *
     * @param id identificador único (ex: "Cr1-N")
     * @param direction direção controlada (ex: "Norte")
     * @param greenTime duração do estado VERDE em segundos
     * @param redTime duração do estado VERMELHO em segundos
     */
    public TrafficLight(String id, String direction, double greenTime, double redTime) {
        this.id = id;
        this.direction = direction;
        this.state = TrafficLightState.RED;
        this.queue = new LinkedList<>();

        this.lock = new ReentrantLock();
        this.vehicleAdded = lock.newCondition();
        this.lightGreen = lock.newCondition();

        this.greenTime = greenTime;
        this.redTime = redTime;
        this.vehicleArrivalTimes = new HashMap<>();
        this.totalVehiclesProcessed = 0;
    }

    /**
     * Coloca um veículo na fila deste semáforo.
     * 
     * Registamos a hora de chegada para podermos calcular mais tarde quanto tempo o
     * veículo esperou.
     * 
     * @param vehicle O veículo que chega ao semáforo.
     * @param simulationTime O tempo de simulação atual (em segundos).
     */
    public void addVehicle(Vehicle vehicle, double simulationTime) {
        lock.lock();
        try {
            queue.offer(vehicle);
            vehicleArrivalTimes.put(vehicle.getId(), simulationTime);
            vehicleAdded.signalAll();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Remove um veículo da fila para travessia.
     * 
     * <p>Só remove se:</p>
     * <ul>
     *   <li>O semáforo estiver VERDE</li>
     *   <li>Existir pelo menos um veículo na fila</li>
     * </ul>
     * 
     * <p>Atualiza automaticamente as estatísticas de tempo de espera do veículo.</p>
     * 
     * @param simulationTime O tempo de simulação atual (em segundos).
     * @return o veículo que vai atravessar, ou null se não for possível
     */
    public Vehicle removeVehicle(double simulationTime) {
        lock.lock();
        try {
            if (state == TrafficLightState.GREEN && !queue.isEmpty()) {
                Vehicle vehicle = queue.poll();
                if (vehicle != null) {
                    totalVehiclesProcessed++;

                    Double arrivalTime = vehicleArrivalTimes.remove(vehicle.getId());
                    if (arrivalTime != null) {
                        double waitTimeSeconds = simulationTime - arrivalTime;
                        vehicle.addWaitingTime(waitTimeSeconds);
                    }
                }
                return vehicle;
            }
            return null;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Muda o estado do semáforo.
     * 
     * @param newState novo estado (VERDE ou VERMELHO)
     */
    public void changeState(TrafficLightState newState) {
        lock.lock();
        try {
            this.state = newState;
            if (newState == TrafficLightState.GREEN) {
                lightGreen.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Retorna quantos veículos estão atualmente na fila.
     * Método thread-safe.
     * 
     * @return tamanho da fila
     */
    public int getQueueSize() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Verifica se a fila está vazia.
     * Método thread-safe.
     *
     * @return {@code true} se não houver veículos, {@code false} caso contrário
     */
    public boolean isQueueEmpty() {
        lock.lock();
        try {
            return queue.isEmpty();
        } finally {
            lock.unlock();
        }
    }

    /** @return identificador único do semáforo */
    public String getId() {
        return id;
    }

    /** @return direção controlada por este semáforo */
    public String getDirection() {
        return direction;
    }

    /**
     * Obtém o estado atual do semáforo.
     * Método thread-safe.
     *
     * @return estado atual (VERDE ou VERMELHO)
     */
    public TrafficLightState getState() {
        lock.lock();
        try {
            return state;
        } finally {
            lock.unlock();
        }
    }

    /** @return duração configurada do sinal verde em segundos */
    public double getGreenTime() {
        return greenTime;
    }

    /**
     * Define a duração do sinal verde.
     * 
     * @param greenTime nova duração em segundos
     */
    public void setGreenTime(double greenTime) {
        this.greenTime = greenTime;
    }

    /** @return duração configurada do sinal vermelho em segundos */
    public double getRedTime() {
        return redTime;
    }

    /**
     * Define a duração do sinal vermelho.
     * 
     * @param redTime nova duração em segundos
     */
    public void setRedTime(double redTime) {
        this.redTime = redTime;
    }

    /** @return número total de veículos processados por este semáforo */
    public int getTotalVehiclesProcessed() {
        return totalVehiclesProcessed;
    }

    /** @return objeto {@link Lock} para sincronização avançada */
    public Lock getLock() {
        return lock;
    }

    /** @return condição para adição de veículos */
    public Condition getVehicleAdded() {
        return vehicleAdded;
    }

    /** @return condição para semáforo ficar verde */
    public Condition getLightGreen() {
        return lightGreen;
    }

    /** @return representação textual do estado atual do semáforo */
    @Override
    public String toString() {
        return String.format(
                "TrafficLight{id='%s', direction='%s', state=%s, queueSize=%d}",
                id, direction, getState(), getQueueSize()
        );
    }
}