/**
   * Given the given fragmentation threshold, determine whether the given bundle should be split
   * into several smaller bundles. If so, this returns true and generates a bunch of bundle received
   * events for the individual fragments.
   *
   * @param bundle Bundle to split into fragments
   * @param link Bundle to send on the given link
   * @param max_length Maximum length of the fragment
   * @return FragmentState that has the list of created fragments for the given bundle.
   */
  public FragmentState proactively_fragment(Bundle bundle, final Link link, int max_length) {
    int payload_len = bundle.payload().length();

    Bundle fragment;

    FragmentState state = new FragmentState(bundle);

    int todo = payload_len;
    int offset = 0;
    int count = 0;

    BlockInfoVec first_frag_blocks = new BlockInfoVec();
    BlockInfoVec all_frag_blocks = new BlockInfoVec();
    BlockInfoVec this_frag_blocks = first_frag_blocks;

    ListIterator<BlockInfo> entry = bundle.xmit_link_block_set().find_blocks(link).listIterator();

    while (entry.hasNext()) {
      BlockInfo block_info = entry.next();

      if ((block_info.type() == bundle_block_type_t.PRIMARY_BLOCK)
          || (block_info.type() == bundle_block_type_t.PAYLOAD_BLOCK)) {

        all_frag_blocks.add(block_info);
        first_frag_blocks.add(block_info);

      } else if ((block_info.flags() & block_flag_t.BLOCK_FLAG_REPLICATE.getCode()) > 0) {
        all_frag_blocks.add(block_info);
      } else {
        first_frag_blocks.add(block_info);
      }
    }

    do {
      fragment = create_fragment(bundle, link, this_frag_blocks, offset, max_length);
      assert (fragment != null) : TAG + ": proactively_fragment() fragment not valid";

      state.add_fragment(fragment);
      offset += fragment.payload().length();
      todo -= fragment.payload().length();
      this_frag_blocks = all_frag_blocks;
      ++count;

    } while (todo > 0);

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "proactively fragmenting " + "%s byte payload into %s %s byte fragments",
                payload_len, count, max_length));

    String[] hash_key = new String[1];
    get_hash_key(fragment, hash_key);
    fragment_table_.put(hash_key[0], state);

    return state;
  }
  /**
   * Prepare function prepare bundle to generate. It adds Primary Block Info at the start of
   * xmit_blocks and add endpoint eids at the start of dictionary.
   *
   * @param bundle Bundle to prepare
   * @param xmit_blocks Empty xmit_blocks of the blundle
   * @param source Source endpoint id
   * @param link Link of the bundle
   * @param list Owner type
   * @return Return success message on success
   */
  public int prepare(
      final Bundle bundle,
      BlockInfoVec xmit_blocks,
      final BlockInfo source,
      final Link link,
      list_owner_t list) {

    // There shouldn't already be anything in the xmit_blocks

    assert (xmit_blocks.size() == 0)
        : TAG + ": prepare() there shouldn't be anything already in xmit_blocks";

    // Add EIDs to start off the dictionary
    xmit_blocks.dict().add_eid(bundle.dest());
    xmit_blocks.dict().add_eid(bundle.source());
    xmit_blocks.dict().add_eid(bundle.replyto());
    xmit_blocks.dict().add_eid(bundle.custodian());

    // make sure to add the primary to the front
    xmit_blocks.add(0, new BlockInfo(this, source));

    return BP_SUCCESS;
  }
  /**
   * Create a bundle fragment from another bundle.
   *
   * @param bundle the source bundle from which we create the fragment. Note: the bundle may itself
   *     be a fragment
   * @param offset the offset relative to this bundle (not the original) for the for the new
   *     fragment. note that if this bundle is already a fragment, the offset into the original
   *     bundle will be this bundle's frag_offset + offset
   * @param length the length of the fragment we want
   * @return Newly created bundle
   */
  public Bundle create_fragment(Bundle bundle, BlockInfoVec blocks, int offset, int length) {

    Bundle fragment = new Bundle(location_t.MEMORY);
    bundle.copy_metadata(fragment);
    fragment.set_is_fragment(true);
    fragment.set_do_not_fragment(false);

    // initialize the fragment's orig_length and figure out the offset
    // into the payload
    if (!bundle.is_fragment()) {
      fragment.set_orig_length(bundle.payload().length());
      fragment.set_frag_offset(offset);
    } else {
      fragment.set_orig_length(bundle.orig_length());
      fragment.set_frag_offset(bundle.frag_offset() + offset);
    }

    // check for overallocated length
    if ((offset + length) > fragment.orig_length()) {
      Logger.getInstance()
          .error(
              TAG,
              String.format(
                  "fragment length overrun: "
                      + "orig_length %d frag_offset %d requested offset %d length %d",
                  fragment.orig_length(), fragment.frag_offset(), offset, length));

      // Panic;
      return null;
    }

    // initialize payload
    fragment.payload().write_data(bundle.payload(), offset, length, 0);

    // copy all blocks that follow the payload, and all those before
    // the payload that are marked with the "must be replicated in every
    // fragment" bit
    ListIterator<BlockInfo> iter = blocks.listIterator();

    // BlockInfoVec vec;

    boolean found_payload = false;

    while (iter.hasNext()) {
      BlockInfo entry = iter.next();

      int type = entry.type().getCode();

      if ((type == bundle_block_type_t.PRIMARY_BLOCK.getCode())
          || (bundle_block_type_t.PAYLOAD_BLOCK.getCode() > 0)
          || found_payload
          || ((entry.flags() & block_flag_t.BLOCK_FLAG_REPLICATE.getCode()) > 0)) {

        // we need to include this block; copy the BlockInfo into the
        // fragment
        fragment.recv_blocks().add(entry);

        if (type == bundle_block_type_t.PAYLOAD_BLOCK.getCode()) {
          found_payload = true;
        }
      }
    }
    return fragment;
  }
  /**
   * Create a fragment to be sent out on a particular link.
   *
   * @param bundle Source Bundle from which we create a fragment.
   * @param link Link on which we will send the fragment
   * @param blocks_to_copy Blocks to copy on this fragment.
   * @param offset The offset relative to this bundle for the for the new fragment
   * @param max_length Maximum length of the newly created fragment
   * @return Newly created fragment.
   */
  public Bundle create_fragment(
      Bundle bundle,
      final Link link,
      final BlockInfoVec blocks_to_copy,
      int offset,
      int max_length) {
    int block_length = 0;

    ListIterator<BlockInfo> entry = blocks_to_copy.listIterator();

    BlockInfo block_i;

    while (entry.hasNext()) {
      block_i = entry.next();
      if (block_i.type() == bundle_block_type_t.PRIMARY_BLOCK) {
        block_length += block_i.data_length();
      } else {
        block_length += block_i.data_offset(); // data offset is the
        // starting point of
      }
    }

    if (block_length > max_length) {
      Logger.getInstance()
          .error(
              TAG,
              String.format(
                  "unable to create a fragment of length %s; minimum length " + "required is %s",
                  max_length, block_length));
      return null;
    }

    Bundle fragment = new Bundle(location_t.DISK);

    // copy the metadata into the new fragment (which can be further
    // fragmented)
    bundle.copy_metadata(fragment);
    fragment.set_is_fragment(true);
    fragment.set_do_not_fragment(false);

    // initialize the fragment's orig_length and figure out the offset
    // into the payload
    if (!bundle.is_fragment()) {
      fragment.set_orig_length(bundle.payload().length());
      fragment.set_frag_offset(offset);
    } else {
      fragment.set_orig_length(bundle.orig_length());
      fragment.set_frag_offset(bundle.frag_offset() + offset);
    }

    // initialize payload
    int to_copy = Math.min(max_length - block_length, bundle.payload().length() - offset);
    fragment.payload().set_length(to_copy);
    fragment.payload().write_data(bundle.payload(), offset, to_copy, 0);
    BlockInfoVec xmit_blocks = fragment.xmit_link_block_set().create_blocks(link);

    entry = blocks_to_copy.listIterator(); // reset Iterator

    while (entry.hasNext()) {
      block_i = entry.next();

      xmit_blocks.add(block_i);
    }

    Logger.getInstance()
        .debug(
            TAG,
            String.format(
                "created %s byte fragment bundle with %s bytes of payload",
                to_copy + block_length, to_copy));

    return fragment;
  }
  /**
   * Generate primary block by encoding all the metadata of the primary block and copy to primary
   * block writeable buffer.
   *
   * @param bundle Bundle to generate
   * @param xmit_blocks xmit_blocks of the bundle
   * @param block Primary block of the bundle to generate and write to the writeable buffer
   */
  public void generate_primary(final Bundle bundle, BlockInfoVec xmit_blocks, BlockInfo block) {

    // point at the local dictionary
    Dictionary dict = xmit_blocks.dict();
    int primary_len = 0; // total length of the primary block
    PrimaryBlock primary = new PrimaryBlock();

    primary_len = get_primary_len(bundle, dict, primary);

    block.set_contents(new SerializableByteBuffer(primary_len));

    block.set_data_length((int) primary.block_length_value());
    block.set_data_offset((int) (primary_len - primary.block_length_value()));
    /*
     * Advance buf and decrement len as we go through the process.
     */
    IByteBuffer buf = block.writable_contents();
    int len = primary_len;

    Log.d(TAG, String.format("generating primary: length %s", primary_len));

    // Stick the version number in the first byte.
    buf.put((byte) BundleProtocol.CURRENT_VERSION);
    len -= 1;

    len -= write_sdnv(primary.processing_flags(), buf);
    len -= write_sdnv(primary.block_length(), buf);
    len -= write_sdnv(primary.dest_scheme_offset(), buf);
    len -= write_sdnv(primary.dest_ssp_offset(), buf);
    len -= write_sdnv(primary.source_scheme_offset(), buf);
    len -= write_sdnv(primary.source_ssp_offset(), buf);
    len -= write_sdnv(primary.replyto_scheme_offset(), buf);
    len -= write_sdnv(primary.replyto_ssp_offset(), buf);
    len -= write_sdnv(primary.custodian_scheme_offset(), buf);
    len -= write_sdnv(primary.custodian_ssp_offset(), buf);
    len -= write_sdnv(bundle.creation_ts().seconds(), buf);
    len -= write_sdnv(bundle.creation_ts().seqno(), buf);
    len -= write_sdnv(bundle.expiration(), buf);
    len -= write_sdnv(primary.dictionary_length(), buf);

    // Add the dictionary.
    Log.d(TAG, "Current Buf: " + buf.position());
    Log.d(TAG, "Dict length: " + dict.dict_length());
    Log.d(TAG, "Dict length: " + dict.dict_length());
    buf.put(dict.dict());
    // memcpy(buf, dict->dict(), dict->length());
    //        buf += dict->length();
    len -= dict.dict_length();
    Log.d(TAG, "Preparing len:" + len);
    /*
     * If the bundle is a fragment, stuff in SDNVs for the fragment
     * offset and original length.
     */

    if (bundle.is_fragment()) {
      len -= write_sdnv(bundle.frag_offset(), buf);
      Log.d(TAG, "Preparing len:" + len);

      len -= write_sdnv(bundle.orig_length(), buf);
      Log.d(TAG, "Preparing len:" + len);
    }
    /*
     * Asuming that get_primary_len is written correctly, len should
     * now be zero since we initialized it to primary_len at the
     * beginning of the function.
     */

    buf.position(0);
    assert (len == 0) : TAG + ": len not ==0";
    Log.e(TAG, "Current Len: " + len);
  }