/** "Return the total length of the formatted bundle block data." [DTN2] */
  public static int total_length(final BlockInfoVec blocks) {

    int ret = 0;
    Iterator<BlockInfo> itr = blocks.iterator();
    while (itr.hasNext()) {
      BlockInfo block = itr.next();
      ret += block.full_length();
    }
    return ret;
  }
  /**
   * "Generate contents for the given BlockInfoVec on the given Link." [DTN2]
   *
   * @return "the total length of the formatted blocks for this bundle." [DTN2]
   */
  public static int generate_blocks(Bundle bundle, BlockInfoVec blocks, final Link link) {
    // "now assert there's at least 2 blocks (primary + payload) and
    // that the primary is first" [DTN2]
    assert (blocks.size() >= 2);
    assert (blocks.front().type() == bundle_block_type_t.PRIMARY_BLOCK);

    // "now we make a pass through the list and call generate on
    // each block processor" [DTN2]

    for (int i = 0; i < blocks.size(); i++) {
      boolean last = i == blocks.size() - 1;

      BlockInfo iter = blocks.get(i);

      iter.owner().generate(bundle, blocks, iter, link, last);

      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "generated block (owner %s type %s) "
                      + "data_offset %d data_length %d , contents_length %d",
                  iter.owner().block_type(),
                  iter.type(),
                  iter.data_offset(),
                  iter.data_length(),
                  iter.contents().position()));

      if (last) {
        assert ((iter.flags() & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode()) != 0);
      } else {
        assert ((iter.flags() & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode()) == 0);
      }
    }

    // "Now that all the EID references are added to the dictionary,
    // generate the primary block." [DTN2]
    PrimaryBlockProcessor pbp =
        (PrimaryBlockProcessor) find_processor(BundleProtocol.bundle_block_type_t.PRIMARY_BLOCK);
    assert (blocks.front().owner() == pbp);
    pbp.generate_primary(bundle, blocks, blocks.front());

    // "make a final pass through, calling finalize() and extracting
    // the block length" [DTN2]
    int total_len = 0;
    for (int i = blocks.size() - 1; i >= 0; i--) {
      BlockInfo iter = blocks.get(i);
      iter.owner().finalize(bundle, blocks, iter, link);
      total_len += iter.full_length();
    }

    return total_len;
  }
  /**
   * "Temporary helper function to find the offset of the first byte of the payload in a
   * BlockInfoVec." [DTN2]
   */
  public static int payload_offset(final BlockInfoVec blocks) {

    int ret = 0;
    Iterator<BlockInfo> itr = blocks.iterator();
    while (itr.hasNext()) {
      BlockInfo block = itr.next();

      if (block.type() == BundleProtocol.bundle_block_type_t.PAYLOAD_BLOCK) {
        ret += block.data_offset();
        return ret;
      }

      ret += block.full_length();
    }

    return ret;
  }
  /**
   * "Copies out a chunk of formatted bundle data at a specified offset from the provided
   * BlockInfoVec." [DTN2]
   *
   * @return "the length of the chunk produced (up to the supplied length) and sets last to true if
   *     the bundle is complete." [DTN2]
   */
  public static int produce(
      final Bundle bundle,
      final BlockInfoVec blocks,
      IByteBuffer data,
      int offset,
      int len,
      boolean[] last) {

    int old_position = data.position();
    int origlen = len;
    last[0] = false;

    if (len == 0) return 0;

    assert (!blocks.isEmpty());

    Iterator<BlockInfo> iter = blocks.iterator();

    BlockInfo current_block = iter.next();
    // "advance past any blocks that are skipped by the given offset."[DTN2]
    while (offset >= current_block.full_length()) {

      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "BundleProtocol::produce skipping block type %s "
                      + "since offset %d >= block length %d",
                  current_block.type().toString(), offset, current_block.full_length()));

      offset -= current_block.full_length();
      current_block = iter.next();
    }
    // "the offset value now should be within the current block" [DTN2]

    while (true) {
      // The first time remainder will be minus from leftover offset above
      // but later on it will be the full content of the block
      int remainder = current_block.full_length() - offset;
      int tocopy = Math.min(len, remainder);
      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "BundleProtocol::produce copying %d/%d bytes from "
                      + "block type %s at offset %d",
                  tocopy, remainder, current_block.type().toString(), offset));

      current_block.owner().produce(bundle, current_block, data, offset, tocopy);

      len -= tocopy;

      // move the position of IByteBuffer
      data.position(data.position() + tocopy);

      // "if we've copied out the full amount the user asked for,
      // we're done. note that we need to check the corner case
      // where we completed the block exactly to properly set the
      // last bit" [DTN2]
      if (len == 0) {
        if ((tocopy == remainder)
            && ((current_block.flags()
                    & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode())
                > 0)) {

          last[0] = true;
        }

        break;
      }

      // "we completed the current block, so we're done if this
      // is the last block, even if there's space in the user buffer"
      // [DTN2]
      assert (tocopy == remainder);
      if ((current_block.flags() & BundleProtocol.block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode())
          > 0) {

        last[0] = true;
        break;
      }

      // advance to next block
      current_block = iter.next();
      offset = 0;
    }

    BPF.getInstance()
        .getBPFLogger()
        .debug(
            TAG,
            String.format(
                "BundleProtocol::produce complete: "
                    + "produced %d bytes, bundle id %d, status is  %s",
                origlen - len, bundle.bundleid(), last[0] ? "complete" : "not complete"));

    data.position(old_position);
    return origlen - len;
  }