/**
   * "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;
  }
  /**
   * "Loop through the bundle's received block list to validate each entry." [DTN2]
   *
   * @return "true if the bundle is valid, false if it should be deleted." [DTN2]
   */
  public static boolean validate(
      Bundle bundle,
      status_report_reason_t[] reception_reason,
      status_report_reason_t[] deletion_reason) {
    int primary_blocks = 0, payload_blocks = 0;
    BlockInfoVec recv_blocks = bundle.recv_blocks();

    // "a bundle must include at least two blocks (primary and payload)"
    // [DTN2]
    if (recv_blocks.size() < 2) {
      BPF.getInstance().getBPFLogger().error(TAG, "bundle fails to contain at least two blocks");
      deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
      return false;
    }

    // "the first block of a bundle must be a primary block" [DTN2]
    if (recv_blocks.front().type() != BundleProtocol.bundle_block_type_t.PRIMARY_BLOCK) {
      BPF.getInstance().getBPFLogger().error(TAG, "bundle fails to contain a primary block");
      deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
      return false;
    }

    // "validate each individual block" [DTN2]

    int last_block_index = recv_blocks.size() - 1;
    for (int i = 0; i < recv_blocks.size(); i++) {
      BlockInfo current_block = recv_blocks.get(i);

      // "a block may not have enough data for the preamble" [DTN2]
      if (current_block.data_offset() == 0) {
        // "either the block is not the last one and something went
        // badly wrong, or it is the last block present" [DTN2]
        if (i != last_block_index) {
          BPF.getInstance().getBPFLogger().error(TAG, "bundle block too short for the preamble");
          deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
          return false;
        }
        // "this is the last block, so drop it" [DTN2]
        BPF.getInstance().getBPFLogger().debug(TAG, "forgetting preamble-starved last block");
        recv_blocks.remove(current_block);
        if (recv_blocks.size() < 2) {
          BPF.getInstance()
              .getBPFLogger()
              .error(TAG, "bundle fails to contain at least two blocks");
          deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
          return false;
        }
        // "continue with the tests; results may have changed now that
        // a different block is last" [DTN2]
        return false;
      } else {
        if (current_block.type() == BundleProtocol.bundle_block_type_t.PRIMARY_BLOCK) {
          primary_blocks++;
        }

        if (current_block.type() == BundleProtocol.bundle_block_type_t.PAYLOAD_BLOCK) {
          payload_blocks++;
        }
      }

      if (!current_block
          .owner()
          .validate(bundle, recv_blocks, current_block, reception_reason, deletion_reason)) {
        return false;
      }

      // "a bundle's last block must be flagged as such,
      // and all other blocks should not be flagged" [DTN2]
      if (i == last_block_index) {
        if (!current_block.last_block()) {
          if (!bundle.fragmented_incoming()) {
            BPF.getInstance().getBPFLogger().error(TAG, "bundle's last block not flagged");
            deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
            return false;
          } else {
            BPF.getInstance()
                .getBPFLogger()
                .debug(TAG, "bundle's last block not flagged, but " + "it is a reactive fragment");
          }
        }
      } else {
        if (current_block.last_block()) {
          BPF.getInstance().getBPFLogger().error(TAG, "bundle block incorrectly flagged as last");
          deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
          return false;
        }
      }
    }

    // "a bundle must contain one, and only one, primary block" [DTN2]
    if (primary_blocks != 1) {
      BPF.getInstance()
          .getBPFLogger()
          .error(TAG, String.format("bundle contains %d primary blocks", primary_blocks));
      deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
      return false;
    }

    // "a bundle must not contain more than one payload block" [DTN2]
    if (payload_blocks > 1) {
      BPF.getInstance()
          .getBPFLogger()
          .error(TAG, String.format("bundle contains %d payload blocks", payload_blocks));
      deletion_reason[0] = BundleProtocol.status_report_reason_t.REASON_BLOCK_UNINTELLIGIBLE;
      return false;
    }

    return true;
  }
  /**
   * "Parse the supplied chunk of arriving data and append it to the rcvd_blocks_ list in the given
   * bundle, finding the appropriate BlockProcessor element and calling its receive() handler.
   *
   * <p>When called repeatedly for arriving chunks of data, this properly fills in the entire
   * bundle, including the in_blocks_ record of the arriving blocks and the payload (which is stored
   * externally)." [DTN2]
   *
   * @return "the length of data consumed or -1 on protocol error, plus sets last to true if the
   *     bundle is complete." [DTN2]
   */
  public static int consume(Bundle bundle, IByteBuffer data, int len, boolean[] last) {

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

    BlockInfoVec recv_blocks = bundle.recv_blocks();

    // "special case for first time we get called, since we need to
    // create a BlockInfo struct for the primary block without knowing
    // the typecode or the length" [DTN2]
    if (recv_blocks.isEmpty()) {
      BPF.getInstance()
          .getBPFLogger()
          .debug(TAG, "consume: got first block... " + "creating primary block info");
      recv_blocks.append_block(find_processor(bundle_block_type_t.PRIMARY_BLOCK), null);
    }

    // "loop as long as there is data left to process" [DTN2]
    while (len != 0) {
      BPF.getInstance()
          .getBPFLogger()
          .debug(TAG, String.format("consume: %d bytes left to process", len));
      BlockInfo info = recv_blocks.back();

      // "if the last received block is complete, create a new one
      // and push it onto the vector. at this stage we consume all
      // blocks, even if there's no BlockProcessor that understands
      // how to parse it" [DTN2]
      if (info.complete()) {
        data.mark();
        byte bundle_block_type_byte = data.get();
        bundle_block_type_t type = bundle_block_type_t.get(bundle_block_type_byte);
        data.reset();
        info = recv_blocks.append_block(find_processor(type), null);
        BPF.getInstance()
            .getBPFLogger()
            .debug(
                TAG,
                String.format(
                    "consume: previous block complete, " + "created new BlockInfo type %s",
                    info.owner().block_type()));
      }

      // "now we know that the block isn't complete, so we tell it to
      // consume a chunk of data" [DTN2]
      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "consume: block processor %s type %s incomplete, "
                      + "calling consume (%d bytes already buffered)",
                  info.owner().block_type(), info.type(), info.contents().position()));

      int cc = info.owner().consume(bundle, info, data, len);
      if (cc < 0) {
        BPF.getInstance()
            .getBPFLogger()
            .error(TAG, String.format("consume: protocol error handling block %s", info.type()));
        return -1;
      }

      // "decrement the amount that was just handled from the overall
      // total. verify that the block was either completed or
      // consumed all the data that was passed in." [DTN2]
      len -= cc;
      data.position(data.position() + cc);

      BPF.getInstance()
          .getBPFLogger()
          .debug(
              TAG,
              String.format(
                  "consume: consumed %d bytes of block type %s (%s)",
                  cc, info.type(), info.complete() ? "complete" : "not complete"));

      if (info.complete()) {
        // check if we're done with the bundle
        if ((info.flags() & block_flag_t.BLOCK_FLAG_LAST_BLOCK.getCode()) > 0) {
          last[0] = true;
          break;
        }

      } else {
        assert (len == 0);
      }
    }

    BPF.getInstance()
        .getBPFLogger()
        .debug(
            TAG,
            String.format(
                "bundle id %d consume completed, %d/%d bytes consumed %s",
                bundle.bundleid(), origlen - len, origlen, last[0] ? "(completed bundle)" : ""));

    data.position(old_position);
    return origlen - len;
  }
  /**
   * "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;
  }