/** Retira o próximo evento da fila de prioridades, e o trata. */
  public void handleEvents() {
    // Não é esperado que esta lista esteja vazia...
    if (getEvents().isEmpty()) throw new RuntimeException();
    // Evento é retornado e deletado da fila.
    Event event = getEvents().poll();

    currentTime = event.getTime();
    switch (event.getType()) {
      case CongestionPacketIntoRouter:
        handleCongestionPacketIntoRouterEvent(event);
        break;
      case TxPacketIntoRouter:
        handleTxPacketIntoRouter(event);
        break;
      case Timeout:
        handleTimeout(event);
        break;
      case RouterSuccessfullySentPacket:
        handleRouterSuccessfullySentPacket(event);
        break;
      case TxPacketHeadsToRouter:
        handleTxPacketHeadsToRouter(event);
        break;
      case SackArrives:
        handleSackArrives(event);
        break;
      default:
        throw new RuntimeException(); // Não deve existir outro evento...
    }
  }
 private void handleTxPacketIntoRouter(Event event) {
   // Sempre que o roteador fica vazio, um novo envio é agendado.
   if (getSystem().getRouter().getBuffer().quantityOfRemainingPackets().intValue() == 0) {
     Event sendPack = new Event(EventType.RouterSuccessfullySentPacket);
     double packTransmission = packTransmission(SimulationProperties.getMSS());
     sendPack.setTime(currentTime + packTransmission);
     getEvents().add(sendPack);
   }
   getSystem().getRouter().receivePacket(event.getPack(), currentTime);
 }
  /**
   * Agenda eventos de envio e de chegada de pacote no roteador.
   *
   * @param tx Servidor de saída do pacote.
   */
  private void newTxPacketHeadsToRouter(Tx tx) {
    Pack nextTcpPack = tx.sendPacket(currentTime);
    tx.setTransmiting(true);
    double propagation = setPropagationDelay(tx);

    Event endOfTransmition = new Event(EventType.TxPacketHeadsToRouter);
    endOfTransmition.setTime(currentTime + transmissionTimeMs);
    endOfTransmition.setTxIndex(tx.getIndex());
    getEvents().add(endOfTransmition);

    Event routerReceivesTcp = new Event(EventType.TxPacketIntoRouter);
    routerReceivesTcp.setTime(currentTime + transmissionTimeMs + propagation);
    routerReceivesTcp.setPack(nextTcpPack);
    getEvents().add(routerReceivesTcp);

    Event timeout = new Event(EventType.Timeout);
    timeout.setTime(currentTime + tx.getRTO());
    timeout.setTxIndex(tx.getIndex());
    nextTcpPack.setTimeout(timeout);
    getEvents().add(timeout);

    if (tx.getNotDeliveredPacks().contains(nextTcpPack)) {
      int index = tx.getNotDeliveredPacks().indexOf(nextTcpPack);
      Pack p = tx.getNotDeliveredPacks().get(index);
      getEvents().remove(p.getTimeout());
      tx.getNotDeliveredPacks().remove(index);
    }

    tx.getNotDeliveredPacks().add(nextTcpPack);
  }
  private void handleTimeout(Event event) {
    Tx tx = getSystem().getTxs().get(event.getTxIndex());

    if (!tx.isTransmiting()) {
      tx.handleTimeOutEvent();
      newTxPacketHeadsToRouter(tx);
    } else tx.handleTimeOutEvent();
  }
  private void handleCongestionPacketIntoRouterEvent(Event event) {
    double scheduleTime = random.generateExponential(1.0 / congestionPacketFrequency);
    Event scheduleNextCongestionPack = new Event(EventType.CongestionPacketIntoRouter);
    scheduleNextCongestionPack.setTime(currentTime + scheduleTime);
    getEvents().add(scheduleNextCongestionPack);

    if (getSystem().getRouter().getBuffer().quantityOfRemainingPackets().equals(0)) {
      Event sendPack = new Event(EventType.RouterSuccessfullySentPacket);
      double packTransmission = packTransmission(SimulationProperties.getMSS());
      sendPack.setTime(currentTime + packTransmission);
      getEvents().add(sendPack);
    }

    long numberOfCongestionPacks =
        Math.round(random.generateGeometric(1.0 / congestionPacketFrequency));
    for (long i = 0; i < numberOfCongestionPacks; i++)
      getSystem().getRouter().receivePacket(new Pack(PackType.Congestion, 0), currentTime);
  }
 private void handleRouterSuccessfullySentPacket(Event event) {
   SACK sack = getSystem().getRouter().sendPackToRx(currentTime);
   if (sack != null) { // Tráfego de fundo se perde
     int ackPropagation =
         getSystem().getTxs().get(sack.getDestination()).getGroup().equals(Group.Group1)
             ? SimulationProperties.getAckG1PropagationTime()
             : SimulationProperties.getAckG2PropagationTime();
     Event sendSack = new Event(EventType.SackArrives);
     sendSack.setTime(currentTime + ackPropagation);
     sendSack.setSack(sack);
     getEvents().add(sendSack);
   }
   // Mais um pacote sai do roteador para um Rx.
   if (getSystem().getRouter().getBuffer().quantityOfRemainingPackets().intValue() >= 1) {
     Event sendPack = new Event(EventType.RouterSuccessfullySentPacket);
     double packTransmission = packTransmission(SimulationProperties.getMSS());
     sendPack.setTime(currentTime + packTransmission);
     getEvents().add(sendPack);
   }
 }
  private void handleSackArrives(Event event) {
    SACK sack = event.getSack();
    Tx tx = getSystem().getTxs().get(sack.getDestination());
    tx.receiveSack(sack, currentTime);

    if (!tx.isTransmiting())
      if (tx.getNextPacketToSend() < tx.getOldestNotReceivedPacket() + tx.getCongestionWindow())
        newTxPacketHeadsToRouter(tx);

    removeTimeouts(sack, tx);
  }
 private void handleTxPacketHeadsToRouter(Event event) {
   Tx tx = getSystem().getTxs().get(event.getTxIndex());
   if (tx.getNextPacketToSend() < tx.getOldestNotReceivedPacket() + tx.getCongestionWindow())
     newTxPacketHeadsToRouter(tx);
   else tx.setTransmiting(false);
 }
  /**
   * A fila de eventos precisa ser setada com eventos iniciais. Este método seta envios de pacotes
   * de congestionamento (tráfego de fundo) e também envios de pacotes vindo dos Txs.
   */
  private void setFirstEvents() {
    resetSimulation();

    // Prepara o tráfego de fundo.
    if (SimulationProperties.isWithCongestion()) {
      Event firstCongestionPack =
          new Event(
              EventType
                  .CongestionPacketIntoRouter); // Evento "Pacote de Tráfego de Fundo chegar ao
                                                // roteador"
      firstCongestionPack.setTime(random.generateExponential(1.0 / congestionPacketFrequency));
      getEvents().add(firstCongestionPack);
    }

    // Prepara os servidores para transmissão de Tx-Rx.
    for (Tx tx : getSystem().getTxs()) {
      double propagationDelay = setPropagationDelay(tx);
      // Fator aleatório além da propagação e da transmissão.
      // É zero por padrão, mas este intervalo muda nos cenários 2 e 3.
      double randTime = random.generateDouble() * SimulationProperties.getAssyncInterval();

      Event packSent = new Event(EventType.TxPacketHeadsToRouter); // Evento "Pacote sair do Tx"
      packSent.setTime(randTime + transmissionTime);
      packSent.setTxIndex(tx.getIndex());
      getEvents().add(packSent);

      Pack pack = tx.sendPacket(randTime);
      Event firstTcpPack =
          new Event(EventType.TxPacketIntoRouter); // Evento "Pacote TCP chegar ao roteador"
      firstTcpPack.setTime(randTime + transmissionTime + propagationDelay);
      firstTcpPack.setPack(pack);
      getEvents().add(firstTcpPack);

      Event possibleTimeout = new Event(EventType.Timeout); // Evento "Possível Timeout do pacote"
      possibleTimeout.setTime(randTime + tx.getRTO());
      possibleTimeout.setTxIndex(tx.getIndex());
      pack.setTimeout(possibleTimeout);
      getEvents().add(possibleTimeout);
    }
  }