package sd.des;

import java.io.Serializable;

/**
 * Representa um evento atómico e imutável no contexto da Simulação de Eventos Discretos (DES).
 * <p>
 * Esta classe é a unidade fundamental de processamento. Numa arquitetura DES, o estado do sistema
 * não muda continuamente, mas sim em instantes discretos definidos por estes eventos.
 * <p>
 * Características principais:
 * <ul>
 * <li><b>Ordenação Temporal:</b> Implementa {@link Comparable} para ser armazenado numa Fila de
 * Eventos Futuros (FEL - Future Event List), garantindo execução cronológica.</li>
 * <li><b>Distribuído:</b> Implementa {@link Serializable} para permitir que eventos gerados num nó
 * (ex: Coordenador) sejam transmitidos e executados noutro (ex: Interseção).</li>
 * <li><b>Polimórfico:</b> Transporta um {@code payload} genérico, permitindo associar qualquer
 * entidade (Veículo, Sinal, etc.) ao evento.</li>
 * </ul>
 */
public class SimulationEvent implements Comparable<SimulationEvent>, Serializable {
    private static final long serialVersionUID = 1L;
    
    /** O instante virtual exato em que o evento deve ser processado. */
    private final double timestamp;
    
    /** A categoria do evento (ex: VEHICLE_ARRIVAL, LIGHT_CHANGE). */
    private final DESEventType type;
    
    /** Dados contextuais associados (ex: o objeto Vehicle que chegou). */
    private final Object payload;
    
    /**
     * O identificador do nó de destino onde o evento deve ser executado.
     * (ex: "Cr1", "Coordinator", "ExitNode"). Se null, é um evento local.
     */
    private final String location;
    
    /**
     * Instancia um novo evento de simulação completo.
     *
     * @param timestamp Instante de execução (segundos virtuais).
     * @param type Tipo enumerado do evento.
     * @param payload Objeto de dados associado (pode ser null).
     * @param location ID do processo alvo para execução distribuída.
     */
    public SimulationEvent(double timestamp, DESEventType type, Object payload, String location) {
        this.timestamp = timestamp;
        this.type = type;
        this.payload = payload;
        this.location = location;
    }
    
    /**
     * Construtor de conveniência para eventos locais (dentro do mesmo processo).
     * Define {@code location} como null.
     *
     * @param timestamp Instante de execução.
     * @param type Tipo do evento.
     * @param payload Objeto de dados associado.
     */
    public SimulationEvent(double timestamp, DESEventType type, Object payload) {
        this(timestamp, type, payload, null);
    }
    
    public double getTimestamp() {
        return timestamp;
    }
    
    public DESEventType getType() {
        return type;
    }
    
    public Object getPayload() {
        return payload;
    }
    
    public String getLocation() {
        return location;
    }
    
    /**
     * Define a ordem natural de processamento na Fila de Prioridade.
     * <p>
     * <b>Lógica de Ordenação:</b>
     * <ol>
     * <li><b>Primária (Tempo):</b> Eventos com menor timestamp ocorrem primeiro.</li>
     * <li><b>Secundária (Determinismo):</b> Em caso de empate temporal (simultaneidade),
     * ordena alfabeticamente pelo nome do tipo. Isto garante que execuções repetidas
     * da simulação produzam exatamente o mesmo resultado (determinismo estrito).</li>
     * </ol>
     *
     * @param other O outro evento a comparar.
     * @return Inteiro negativo, zero ou positivo conforme a ordem.
     */
    @Override
    public int compareTo(SimulationEvent other) {
        int timeComparison = Double.compare(this.timestamp, other.timestamp);
        if (timeComparison != 0) {
            return timeComparison;
        }
        // Tie-breaker: order by event type name to ensure reproducible runs
        return this.type.name().compareTo(other.type.name());
    }
    
    @Override
    public String toString() {
        return String.format("Event[t=%.3f, type=%s, location=%s]", 
            timestamp, type, location);
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof SimulationEvent)) return false;
        SimulationEvent other = (SimulationEvent) obj;
        return Double.compare(timestamp, other.timestamp) == 0 &&
               type == other.type &&
               (location == null ? other.location == null : location.equals(other.location));
    }
    
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + Double.hashCode(timestamp);
        result = 31 * result + type.hashCode();
        result = 31 * result + (location != null ? location.hashCode() : 0);
        return result;
    }
}