/**
   * If the message to be written is a {@link CoapResponse} this method decides whether the message
   * type is {@link MessageType.Name#ACK} (if there wasn't an empty acknowledgement sent yet) or
   * {@link MessageType.Name#CON} (if there was already an empty acknowledgement sent). In the
   * latter case it additionally cancels the sending of an empty acknowledgement (which was
   * scheduled by the <code>messageReceived</code> method when the request was received).
   *
   * @param ctx The {@link ChannelHandlerContext} connecting relating this class (which implements
   *     the {@link ChannelUpstreamHandler} interface) to the datagramChannel that received the
   *     message.
   * @param me the {@link MessageEvent} containing the actual message
   * @throws Exception if an error occurred
   */
  @Override
  public void writeRequested(ChannelHandlerContext ctx, MessageEvent me) throws Exception {
    if (isShutdown()) return;

    if (me.getMessage() instanceof CoapResponse) handleOutgoingCoapResponse(ctx, me);
    else if (me.getMessage() instanceof InternalApplicationShutdownMessage)
      handleApplicationShutdown(ctx, me);
    else ctx.sendDownstream(me);
  }
  private void handleIncomingConfirmableCoapResponse(ChannelHandlerContext ctx, MessageEvent me) {
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();
    CoapResponse coapResponse = (CoapResponse) me.getMessage();

    writeEmptyAcknowledgement(remoteEndpoint, coapResponse.getMessageID());

    ctx.sendUpstream(me);
  }
  /**
   * If the incoming message is a confirmable {@link CoapRequest} it schedules the sending of an
   * empty acknowledgement to the sender if there wasn't a piggy-backed response within a period of
   * 2 seconds.
   *
   * <p>If the incoming message is a confirmable {@link CoapResponse} it immediately sends a proper
   * acknowledgement if there was an open request waiting for a seperate response or
   * update-notification. It immediately sends an RST if there was no such response expected.
   *
   * @param ctx The {@link ChannelHandlerContext} relating this handler (which implements the {@link
   *     ChannelUpstreamHandler} interface) to the datagramChannel that received the message.
   * @param me the {@link MessageEvent} containing the actual message
   * @throws Exception if an error occured
   */
  @Override
  public void messageReceived(final ChannelHandlerContext ctx, MessageEvent me) throws Exception {
    log.debug("Upstream from {}: {}.", me.getRemoteAddress(), me.getMessage());

    if (isShutdown()) return;

    if (me.getMessage() instanceof CoapMessage) {

      CoapMessage coapMessage = (CoapMessage) me.getMessage();

      if (coapMessage.getMessageTypeName() == MessageType.Name.CON)
        handleIncomingConfirmableCoapMessage(ctx, me);
      else if (coapMessage.getMessageTypeName() == MessageType.Name.NON)
        handleIncomingNonConfirmableMessage(ctx, me);
      else ctx.sendUpstream(me);
    } else ctx.sendUpstream(me);
  }
  private void handleIncomingConfirmableCoapRequest(ChannelHandlerContext ctx, MessageEvent me) {
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();
    CoapMessage coapMessage = (CoapMessage) me.getMessage();

    IncomingReliableMessageExchange newMessageExchange =
        new IncomingReliableMessageExchange(remoteEndpoint, coapMessage.getMessageID());

    IncomingMessageExchange oldMessageExchange =
        ongoingMessageExchanges.get(remoteEndpoint, coapMessage.getMessageID());

    // Check if there is an ongoing
    if (oldMessageExchange != null) {

      if (oldMessageExchange instanceof IncomingReliableMessageExchange) {

        // if the old message exchange is reliable and the empty ACK was already sent send another
        // empty ACK
        if (((IncomingReliableMessageExchange) oldMessageExchange).isAcknowledgementSent())
          writeEmptyAcknowledgement(remoteEndpoint, coapMessage.getMessageID());

      }

      // if the old message was unreliable and the duplicate message is confirmable send empty ACK
      else writeEmptyAcknowledgement(remoteEndpoint, coapMessage.getMessageID());

      // As the message is already being processed there is nothing more to do
      return;
    }

    // try to add new reliable message exchange
    boolean added = false;
    synchronized (monitor) {
      Long time = System.currentTimeMillis() + MIN_EMPTY_ACK_DELAY_MILLIS;

      // Add message exchange to set of ongoing exchanges to detect duplicates
      if (!ongoingMessageExchanges.contains(remoteEndpoint, coapMessage.getMessageID())) {
        ongoingMessageExchanges.put(remoteEndpoint, coapMessage.getMessageID(), newMessageExchange);
        added = true;
      }

      // If the scheduling of the empty ACK does not work then it was already scheduled
      if (!emptyAcknowledgementSchedule.put(time, newMessageExchange)) {
        log.error("Could not schedule empty ACK for message: {}", coapMessage);
        ongoingMessageExchanges.remove(remoteEndpoint, coapMessage.getMessageID());
        added = false;
      }
    }

    // everything is fine, so further process message
    if (added) ctx.sendUpstream(me);
  }
  private void handleIncomingConfirmableCoapMessage(ChannelHandlerContext ctx, MessageEvent me) {

    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();
    CoapMessage coapMessage = (CoapMessage) me.getMessage();

    // Empty CON messages can be used as application layer PING (is CoAP endpoints alive?)
    if (coapMessage.getMessageCodeName() == MessageCode.Name.EMPTY)
      writeReset(remoteEndpoint, coapMessage.getMessageID());
    else if (MessageCode.isResponse(coapMessage.getMessageCode()))
      handleIncomingConfirmableCoapResponse(ctx, me);
    else if (MessageCode.isRequest(coapMessage.getMessageCode()))
      handleIncomingConfirmableCoapRequest(ctx, me);
    else
      log.error("Incoming CoAP message is neither empty nor request nor response: ", coapMessage);
  }
  private void handleOutgoingCoapResponse(ChannelHandlerContext ctx, MessageEvent me) {

    CoapResponse coapResponse = (CoapResponse) me.getMessage();
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();

    IncomingMessageExchange messageExchange;
    synchronized (monitor) {
      messageExchange = ongoingMessageExchanges.remove(remoteEndpoint, coapResponse.getMessageID());
    }

    if (messageExchange instanceof IncomingReliableMessageExchange) {

      // if the ongoing message exchange is reliable and the empty ACK was not yet sent make
      // response piggy-
      // backed and suppress scheduled empty ACK
      if (!((IncomingReliableMessageExchange) messageExchange).isAcknowledgementSent()) {
        coapResponse.setMessageType(MessageType.Name.ACK.getNumber());
        ((IncomingReliableMessageExchange) messageExchange).setAcknowledgementSent();
      }
    }

    ctx.sendDownstream(me);
  }
  private void handleIncomingNonConfirmableMessage(ChannelHandlerContext ctx, MessageEvent me) {
    InetSocketAddress remoteEndpoint = (InetSocketAddress) me.getRemoteAddress();
    CoapMessage coapMessage = (CoapMessage) me.getMessage();

    boolean isDuplicate = true;

    if (!ongoingMessageExchanges.contains(remoteEndpoint, coapMessage.getMessageID())) {
      IncomingMessageExchange messageExchange =
          new IncomingMessageExchange(remoteEndpoint, coapMessage.getMessageID());

      synchronized (monitor) {
        if (!ongoingMessageExchanges.contains(remoteEndpoint, coapMessage.getMessageID())) {
          ongoingMessageExchanges.put(remoteEndpoint, coapMessage.getMessageID(), messageExchange);

          isDuplicate = false;
        }
      }

      ctx.sendUpstream(me);
    }

    if (isDuplicate)
      log.info("Received duplicate (non-confirmable). IGNORE! (Message: {})", coapMessage);
  }