/**
   * If we receive an ACK or RST, we mark the outgoing request or response as acknowledged or
   * rejected respectively and cancel its retransmission.
   */
  @Override
  public void receiveEmptyMessage(Exchange exchange, EmptyMessage message) {
    exchange.setFailedTransmissionCount(0);
    // TODO: If this is an observe relation, the current response might not
    // be the one that is being acknowledged. The current response might
    // already be the next NON notification.

    if (message.getType() == Type.ACK) {
      if (exchange.getOrigin() == Origin.LOCAL) {
        exchange.getCurrentRequest().setAcknowledged(true);
      } else {
        exchange.getCurrentResponse().setAcknowledged(true);
      }
    } else if (message.getType() == Type.RST) {
      if (exchange.getOrigin() == Origin.LOCAL) {
        exchange.getCurrentRequest().setRejected(true);
      } else {
        exchange.getCurrentResponse().setRejected(true);
      }
    } else {
      LOGGER.warning("Empty messgae was not ACK nor RST: " + message);
    }

    LOGGER.finer("Cancel retransmission");
    exchange.setRetransmissionHandle(null);

    super.receiveEmptyMessage(exchange, message);
  }
  /**
   * When we receive a duplicate of a request, we stop it here and do not forward it to the upper
   * layer. If the server has already sent a response, we send it again. If the request has only
   * been acknowledged (but the ACK has gone lost or not reached the client yet), we resent the ACK.
   * If the request has neither been responded, acknowledged or rejected yet, the server has not yet
   * decided what to do with the request and we cannot do anything.
   */
  @Override
  public void receiveRequest(Exchange exchange, Request request) {

    if (request.isDuplicate()) {
      // Request is a duplicate, so resend ACK, RST or response
      if (exchange.getCurrentResponse() != null) {
        LOGGER.fine("Respond with the current response to the duplicate request");
        // Do not restart retransmission cycle
        super.sendResponse(exchange, exchange.getCurrentResponse());

      } else if (exchange.getCurrentRequest().isAcknowledged()) {
        LOGGER.fine(
            "The duplicate request was acknowledged but no response computed yet. Retransmit ACK");
        EmptyMessage ack = EmptyMessage.newACK(request);
        sendEmptyMessage(exchange, ack);

      } else if (exchange.getCurrentRequest().isRejected()) {
        LOGGER.fine("The duplicate request was rejected. Reject again");
        EmptyMessage rst = EmptyMessage.newRST(request);
        sendEmptyMessage(exchange, rst);

      } else {
        LOGGER.fine(
            "The server has not yet decided what to do with the request. We ignore the duplicate.");
        // The server has not yet decided, whether to acknowledge or
        // reject the request. We know for sure that the server has
        // received the request though and can drop this duplicate here.
      }

    } else {
      // Request is not a duplicate
      exchange.setCurrentRequest(request);
      super.receiveRequest(exchange, request);
    }
  }
  /**
   * Makes sure that the response type is correct. The response type for a NON can be NON or CON.
   * The response type for a CON should either be an ACK with a piggy-backed response or, if an
   * empty ACK has already be sent, a CON or NON with a separate response.
   */
  @Override
  public void sendResponse(final Exchange exchange, final Response response) {

    LOGGER.finer("Send response, failed transmissions: " + exchange.getFailedTransmissionCount());

    // If a response type is set, we do not mess around with it.
    // Only if none is set, we have to decide for one here.

    Type respType = response.getType();
    if (respType == null) {
      Type reqType = exchange.getCurrentRequest().getType();
      if (reqType == Type.CON) {
        if (exchange.getCurrentRequest().isAcknowledged()) {
          // send separate response
          response.setType(Type.CON);
        } else {
          exchange.getCurrentRequest().setAcknowledged(true);
          // send piggy-backed response
          response.setType(Type.ACK);
          response.setMID(exchange.getCurrentRequest().getMID());
        }
      } else {
        // send NON response
        response.setType(Type.NON);
      }

      LOGGER.finest(
          "Switched response message type from "
              + respType
              + " to "
              + response.getType()
              + " (request was "
              + reqType
              + ")");

    } else if (respType == Type.ACK || respType == Type.RST) {
      response.setMID(exchange.getCurrentRequest().getMID());
    }

    if (response.getType() == Type.CON) {
      LOGGER.finer("Scheduling retransmission for " + response);
      prepareRetransmission(
          exchange,
          new RetransmissionTask(exchange, response) {
            public void retransmit() {
              sendResponse(exchange, response);
            }
          });
    }
    super.sendResponse(exchange, response);
  }
  /**
   * When we receive a Confirmable response, we acknowledge it and it also counts as acknowledgment
   * for the request. If the response is a duplicate, we stop it here and do not forward it to the
   * upper layer.
   */
  @Override
  public void receiveResponse(Exchange exchange, Response response) {
    exchange.setFailedTransmissionCount(0);

    exchange.getCurrentRequest().setAcknowledged(true);
    LOGGER.finest("Cancel any retransmission");
    exchange.setRetransmissionHandle(null);

    if (response.getType() == Type.CON && !exchange.getRequest().isCanceled()) {
      LOGGER.finer("Response is confirmable, send ACK");
      EmptyMessage ack = EmptyMessage.newACK(response);
      sendEmptyMessage(exchange, ack);
    }

    if (response.isDuplicate()) {
      LOGGER.fine("Response is duplicate, ignore it");
    } else {
      super.receiveResponse(exchange, response);
    }
  }