/** "Hook to ask the router if the bundle can be deleted." [DTN2] */
  @Override
  public boolean can_delete_bundle(final Bundle bundle) {

    Log.d(
        TAG,
        String.format("TableBasedRouter::can_delete_bundle: checking if we can delete %s", bundle));

    // "check if we haven't yet done anything with this bundle" [DTN2]
    if (bundle
            .fwdlog()
            .get_count(
                ForwardingInfo.state_t.TRANSMITTED.getCode()
                    | ForwardingInfo.state_t.DELIVERED.getCode(),
                ForwardingInfo.ANY_ACTION)
        == 0) {
      Log.d(
          TAG,
          String.format(
              "TableBasedRouter::can_delete_bundle(%d): " + "not yet transmitted or delivered",
              bundle.bundleid()));
      return false;
    }

    // check if we have local custody
    if (bundle.local_custody()) {
      Log.d(
          TAG,
          String.format(
              "TableBasedRouter::can_delete_bundle(%d): " + "not deleting because we have custody",
              bundle.bundleid()));
      return false;
    }

    return true;
  }
  @Override
  protected void handle_bundle_received(BundleReceivedEvent event) {
    boolean should_route = true;

    Bundle bundle = event.bundle();
    Log.d(TAG, String.format("handle bundle received: bundle %d", bundle.bundleid()));

    EndpointID remote_eid = EndpointID.NULL_EID();

    if (event.link() != null) {
      remote_eid = event.link().remote_eid();
    }

    if (!reception_cache_.add_entry(bundle, remote_eid)) {
      Log.i(TAG, String.format("ignoring duplicate bundle: bundle %d", bundle.bundleid()));
      BundleDaemon.getInstance()
          .post_at_head(
              new BundleDeleteRequest(
                  bundle, BundleProtocol.status_report_reason_t.REASON_NO_ADDTL_INFO));
      return;
    }

    if (should_route) {
      route_bundle(bundle);
    } else {
      BundleDaemon.getInstance()
          .post_at_head(
              new BundleDeleteRequest(
                  bundle, BundleProtocol.status_report_reason_t.REASON_NO_ADDTL_INFO));
    }
  }
    /**
     * "Add a new bundle/info pair to the deferred list" [DTN2]
     *
     * @param bundle
     * @param info
     * @return
     */
    boolean add(final Bundle bundle, final ForwardingInfo info) {
      if (list_.contains(bundle)) {
        Log.e(TAG, String.format("bundle %d already in deferred list!", bundle.bundleid()));
        return false;
      }

      Log.d(
          TAG,
          String.format(
              "adding bundle %d to deferred (list length %d)", bundle.bundleid(), count_));

      count_++;
      list_.push_back(bundle);

      info_.put(bundle, info);

      return true;
    }
  @Override
  protected void handle_bundle_cancelled(BundleSendCancelledEvent event) {
    Bundle bundle = event.bundle();
    Log.d(TAG, String.format("handle bundle cancelled: bundle %d", bundle.bundleid()));

    // "if the bundle has expired, we don't want to reroute it." [DTN2]
    if (!bundle.expired()) {
      route_bundle(bundle);
    }
  }
  @Override
  protected void handle_bundle_transmitted(BundleTransmittedEvent event) {
    Bundle bundle = event.bundle();
    Log.d(TAG, String.format("handle bundle transmitted: bundle %d", bundle.bundleid()));

    // "if the bundle has a deferred single-copy transmission for
    // forwarding on any links, then remove the forwarding log entries" [DTN2]
    remove_from_deferred(bundle, ForwardingInfo.action_t.FORWARD_ACTION.getCode());

    // "check if the transmission means that we can send another bundle
    // on the link" [DTN2]
    Link link = event.contact().link();
    check_next_hop(link);
  }
    /**
     * "Remove the bundle and its associated forwarding info from the list" [DTN2]
     *
     * @param bundle
     * @return
     */
    boolean del(final Bundle bundle) {
      if (!list_.erase(bundle, false)) {
        return false;
      }

      assert (count_ > 0) : "DeferredList::del count <= 0";
      count_--;

      Log.d(
          TAG,
          String.format("removed bundle %d from deferred (length %d)", bundle.bundleid(), count_));

      ForwardingInfo removed_result = info_.remove(bundle);
      assert removed_result != null : "DeferredList::del failed to remove info map";

      return true;
    }
  /**
   * "Check whether the Bundle should be forwarded to the give route or not" [DTN2]
   *
   * @param bundle the Bundle to check
   * @param route the route to check
   */
  protected boolean should_fwd(final Bundle bundle, RouteEntry route) {

    if (route == null) return false;

    EndpointID prevhop = reception_cache_.lookup(bundle);
    if (prevhop != null) {
      if (prevhop.equals(route.link().remote_eid()) && !prevhop.equals(EndpointID.NULL_EID())) {
        Log.d(
            TAG,
            String.format(
                "should_fwd bundle %d: " + "skip %s since bundle arrived from the same node",
                bundle.bundleid(), route.link().name()));
        return false;
      }
    }

    return super.should_fwd(bundle, route.link(), route.action());
  }
  /** "Hook to tell the router that the bundle should be deleted." [DTN2] */
  @Override
  public void delete_bundle(final Bundle bundle) {

    Log.d(TAG, String.format("delete bundleid %d", bundle.bundleid()));
    remove_from_deferred(bundle, ForwardingInfo.ANY_ACTION);
  }
  /**
   * "Called when the next hop link is available for transmission (i.e. either when it first arrives
   * and the contact is brought up or when a bundle is completed and it's no longer busy).
   *
   * <p>Loops through the bundle list and calls fwd_to_matching on all bundles." [DTN2]
   */
  protected void check_next_hop(Link next_hop) {

    // "if the link isn't open, there's nothing to do now" [DTN2]
    if (!next_hop.isopen()) {
      Log.d(
          TAG,
          String.format(
              "check_next_hop %s -> %s: link not open...", next_hop.name(), next_hop.nexthop()));
      return;
    }

    // "if the link queue doesn't have space (based on the low water
    // mark) don't do anything" [DTN2]
    if (!next_hop.queue_has_space()) {
      Log.d(
          TAG,
          String.format(
              "check_next_hop %s -> %s: no space in queue...",
              next_hop.name(), next_hop.nexthop()));
      return;
    }

    Log.d(
        TAG,
        String.format(
            "check_next_hop %s -> %s: checking deferred bundle list...",
            next_hop.name(), next_hop.nexthop()));

    // "because the loop below will remove the current bundle from
    // the deferred list, invalidating any iterators pointing to its
    // position, make sure to advance the iterator before processing
    // the current bundle" [DTN2]
    DeferredList deferred = deferred_list(next_hop);

    deferred.list().get_lock().lock();
    try {

      Iterator<Bundle> iter = deferred.list().begin();
      while (iter.hasNext()) {
        if (next_hop.queue_is_full()) {
          Log.d(
              TAG,
              String.format(
                  "check_next_hop %s: link queue is full, stopping loop", next_hop.name()));
          break;
        }

        Bundle bundle = iter.next();

        ForwardingInfo info = deferred.find(bundle);
        assert info != null
            : "TableBasedRouter: check_next_hop, ForwardingInfo regarding Bundle is null";

        // "if should_fwd returns false, then the bundle was either
        // already transmitted or is in flight on another node. since
        // it's possible that one of the other transmissions will
        // fail, we leave it on the deferred list for now, relying on
        // the transmitted handlers to clean up the state" [DTN2]
        if (!this.should_fwd(bundle, next_hop, info.action())) {
          Log.d(TAG, String.format("check_next_hop: not forwarding to link %s", next_hop.name()));
          continue;
        }

        // "if the link is available and not open, open it" [DTN2]
        if (next_hop.isNotUnavailable() && (!next_hop.isopen()) && (!next_hop.isopening())) {
          Log.d(
              TAG,
              String.format(
                  "check_next_hop: " + "opening %s because a message is intended for it",
                  next_hop.name()));
          actions_.open_link(next_hop);
        }

        // "remove the bundle from the deferred list" [DTN2]
        Log.d(
            TAG,
            String.format(
                "check_next_hop: sending bundle %d to %s", bundle.bundleid(), next_hop.name()));

        iter.remove();
        actions_.queue_bundle(bundle, next_hop, info.action(), info.custody_spec());
      }
    } catch (BundleListLockNotHoldByCurrentThread e) {
      Log.e(TAG, "Table Based Router " + e.toString());
    } finally {
      deferred.list().get_lock().unlock();
    }
  }
  /**
   * "Check the route table entries that match the given bundle and have not already been found in
   * the bundle history. If a match is found, call fwd_to_nexthop on it." [DTN2]
   *
   * @param bundle the bundle to forward
   * @return "the number of links on which the bundle was queued (i.e. the number of matching route
   *     entries.)" [DTN2]
   */
  protected int route_bundle(Bundle bundle) {

    RouteEntryVec matches = new RouteEntryVec();

    Log.d(TAG, String.format("route_bundle: checking bundle %d", bundle.bundleid()));

    // "check to see if forwarding is suppressed to all nodes" [DTN2]
    if (bundle
            .fwdlog()
            .get_count(
                EndpointIDPattern.WILDCARD_EID(),
                ForwardingInfo.state_t.SUPPRESSED.getCode(),
                ForwardingInfo.ANY_ACTION)
        > 0) {
      Log.i(
          TAG,
          String.format(
              "route_bundle: " + "ignoring bundle %d since forwarding is suppressed",
              bundle.bundleid()));
      return 0;
    }

    Link null_link = null;
    route_table_.get_matching(bundle.dest(), null_link, matches);

    // "sort the matching routes by priority, allowing subclasses to
    // override the way in which the sorting occurs" [DTN2]
    sort_routes(matches);

    Log.d(
        TAG,
        String.format(
            "route_bundle bundle id %d: checking %d route entry matches",
            bundle.bundleid(), matches.size()));

    int count = 0;
    Iterator<RouteEntry> itr = matches.iterator();
    while (itr.hasNext()) {
      RouteEntry route = itr.next();
      Log.d(
          TAG,
          String.format(
              "checking route entry %s link %s (%s)",
              route.toString(), route.link().name(), route.link()));

      if (!should_fwd(bundle, route)) {
        continue;
      }

      if (deferred_list(route.link()).list().contains(bundle)) {
        Log.d(
            TAG,
            String.format(
                "route_bundle bundle %d: " + "ignoring link %s since already deferred",
                bundle.bundleid(), route.link().name()));
        continue;
      }

      // "because there may be bundles that already have deferred
      // transmission on the link, we first call check_next_hop to
      // get them into the queue before trying to route the new
      // arrival, otherwise it might leapfrog the other deferred
      // bundles" [DTN2]
      check_next_hop(route.link());

      if (!fwd_to_nexthop(bundle, route)) {
        continue;
      }

      ++count;
    }

    Log.d(
        TAG,
        String.format(
            "route_bundle bundle id %d: forwarded on %d links", bundle.bundleid(), count));
    return count;
  }
  /** "Try to forward a bundle to a next hop route." [DTN2] */
  protected boolean fwd_to_nexthop(Bundle bundle, RouteEntry route) {

    Link link = route.link();

    // "if the link is available and not open, open it" [DTN2]
    if (link.isNotUnavailable() && (!link.isopen()) && (!link.isopening())) {
      Log.d(TAG, String.format("opening %s because a message is intended for it", link.name()));
      actions_.open_link(link);
    }

    // "if the link is open and has space in the queue, then queue the
    // bundle for transmission there" [DTN2]
    if (link.isopen() && !link.queue_is_full()) {
      Log.d(TAG, String.format("queuing %d on %s", bundle.bundleid(), link.name()));
      actions_.queue_bundle(bundle, link, route.action(), route.custody_spec());
      return true;
    }

    // "otherwise we can't send the bundle now, so put it on the link's
    // deferred list and log reason why we can't forward it" [DTN2]
    DeferredList deferred = deferred_list(link);
    if (!bundle.is_queued_on(deferred.list())) {
      ForwardingInfo info =
          new ForwardingInfo(
              ForwardingInfo.state_t.NONE,
              route.action(),
              link.name_str(),
              0xffffffff,
              link.remote_eid(),
              route.custody_spec());
      deferred.add(bundle, info);
    } else {
      Log.w(
          TAG,
          String.format(
              "bundle %d already exists on deferred list of link %s",
              bundle.bundleid(), link.name()));
    }

    if (!link.isNotUnavailable()) {
      Log.d(
          TAG,
          String.format(
              "can't forward bundle %d to %s because link not available",
              bundle.bundleid(), link.name()));
    } else if (!link.isopen()) {
      Log.d(
          TAG,
          String.format(
              TAG,
              "can't forward bundle %d to %s because link not open",
              bundle.bundleid(),
              link.name()));
    } else if (link.queue_is_full()) {
      Log.d(
          TAG,
          String.format(
              TAG,
              "can't forward bundle %d to %s because link queue is full",
              bundle.bundleid(),
              link.name()));
    } else {
      Log.d(TAG, String.format(TAG, "can't forward %d to %s", bundle.bundleid(), link.name()));
    }

    return false;
  }
  /**
   * Internal function get the total length of primary block to write on the buffer
   *
   * @param bundle Bundle to generate
   * @param dict Dictionary to get the offsets of the endpoint eids
   * @param primary PrimaryBlock data strucre object
   * @return Total numbers of Bytes required to write primary block
   */
  protected static int get_primary_len(final Bundle bundle, Dictionary dict, PrimaryBlock primary) {
    int primary_len = 0;
    int block_len = 0;
    primary.set_dictionary_length(0);
    primary.set_block_length(0);

    /*
     * We need to figure out the total length of the primary block,
     * except for the SDNVs used to encode flags and the length itself and
     * the one byte version field.
     *
     * First, we determine the size of the dictionary by first
     * figuring out all the unique strings, and in the process,
     * remembering their offsets and summing up their lengths
     * (including the null terminator for each).
     */

    dict.get_offsets(bundle.dest(), primary.dest_scheme_offset(), primary.dest_ssp_offset());

    block_len += SDNV.encoding_len(primary.dest_scheme_offset());
    block_len += SDNV.encoding_len(primary.dest_ssp_offset());

    dict.get_offsets(bundle.source(), primary.source_scheme_offset(), primary.source_ssp_offset());

    block_len += SDNV.encoding_len(primary.source_scheme_offset());
    block_len += SDNV.encoding_len(primary.source_ssp_offset());

    dict.get_offsets(
        bundle.replyto(), primary.replyto_scheme_offset(), primary.replyto_ssp_offset());

    block_len += SDNV.encoding_len(primary.replyto_scheme_offset());
    block_len += SDNV.encoding_len(primary.replyto_ssp_offset());

    dict.get_offsets(
        bundle.custodian(), primary.custodian_scheme_offset(), primary.custodian_ssp_offset());

    block_len += SDNV.encoding_len(primary.custodian_scheme_offset());
    block_len += SDNV.encoding_len(primary.custodian_ssp_offset());

    primary.set_dictionary_length(dict.dict_length());

    block_len += SDNV.encoding_len(bundle.creation_ts().seconds());
    block_len += SDNV.encoding_len(bundle.creation_ts().seqno());
    block_len += SDNV.encoding_len(bundle.expiration());

    block_len += SDNV.encoding_len(primary.dictionary_length_value());
    block_len += primary.dictionary_length_value();

    /*
     * If the bundle is a fragment, we need to include space for the
     * fragment offset and the original payload length.
     *
     * Note: Any changes to this protocol must be reflected into the
     * FragmentManager since it depends on this length when
     * calculating fragment sizes.
     */
    if (bundle.is_fragment()) {
      block_len += SDNV.encoding_len(bundle.frag_offset());
      block_len += SDNV.encoding_len(bundle.orig_length());
    }

    // Format the processing flags.
    primary.set_processing_flags(format_bundle_flags(bundle));

    primary.set_processing_flags(format_bundle_flags(bundle));
    primary.set_processing_flags(primary.processing_flags_value() | format_cos_flags(bundle));
    primary.set_processing_flags(primary.processing_flags_value() | format_srr_flags(bundle));

    /*
     * Finally, add up the initial preamble and the variable
     * length part.
     */

    primary.set_block_length(block_len);

    primary_len =
        (int)
            (1
                + SDNV.encoding_len(primary.processing_flags)
                + SDNV.encoding_len(primary.block_length())
                + primary.block_length_value());

    Log.d(TAG, "get_primary_len: for bundleid = " + bundle.bundleid() + ": " + primary_len);
    // Fill in the remaining values of 'primary' just for the sake of returning
    // a complete data structure.
    primary.set_version(BundleProtocol.CURRENT_VERSION);
    primary.set_creation_time(bundle.creation_ts().seconds());
    primary.set_creation_sequence(bundle.creation_ts().seqno());
    primary.set_lifetime(bundle.expiration());
    return primary_len;
  }