/**
   * Takes a Msg and encodes it into a single byte[], in a way that is compatible with the way that
   * PyBitmessage does. This payload can then be sent to a server to be disseminated across the
   * network. The payload is stored as a Payload object.
   *
   * @param encMsg - A msg Object containing the message data used to create the payload.
   * @param powDone - A boolean value indicating whether or not POW has been done for this message
   * @param toPubkey - A Pubkey object containing the data for the Pubkey of the address that this
   *     message is being sent to
   * @return A Payload object containing the message payload
   */
  private Payload constructMsgPayloadForDissemination(
      BMObject encMsg, boolean powDone, Pubkey toPubkey) {
    // Create a new Payload object to hold the payload data
    Payload msgPayload = new Payload();
    msgPayload.setBelongsToMe(true);
    msgPayload.setPOWDone(powDone);
    msgPayload.setType(Payload.OBJECT_TYPE_MSG);

    // Encode the POW nonce, expiration time, object type, object version, and stream number values
    // into byte form
    byte[] powNonceBytes = ByteUtils.longToBytes(encMsg.getPOWNonce());
    byte[] expirationTimeBytes = ByteUtils.longToBytes(encMsg.getExpirationTime());
    byte[] objectTypeBytes = ByteUtils.intToBytes(OBJECT_TYPE_MSG);
    byte[] objectVersionBytes = VarintEncoder.encode(OBJECT_VERSION_MSG);
    byte[] streamNumberBytes = VarintEncoder.encode(encMsg.getStreamNumber());

    byte[] payload = null;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
      if (powDone) {
        outputStream.write(powNonceBytes);
      }
      outputStream.write(expirationTimeBytes);
      outputStream.write(objectTypeBytes);
      outputStream.write(objectVersionBytes);
      outputStream.write(streamNumberBytes);
      outputStream.write(encMsg.getPayload());

      payload = outputStream.toByteArray();
      outputStream.close();
    } catch (IOException e) {
      throw new RuntimeException(
          "IOException occurred in DataProcessor.constructMsgPayloadForDissemination()", e);
    }

    msgPayload.setPayload(payload);

    // Save the Payload object to the database
    PayloadProvider payProv = PayloadProvider.get(App.getContext());
    long msgPayloadId = payProv.addPayload(msgPayload);

    // Finally, set the msg payload ID to the one generated by the SQLite database
    msgPayload.setId(msgPayloadId);

    return msgPayload;
  }
  /**
   * Takes an UnencryptedMsg object and does all the work necessary to transform it into an
   * EncyrptedMsg object that is ready to be serialised and sent out to the Bitmessage network. The
   * two major parts of this process are encryption and proof of work. <br>
   * <br>
   * <b>NOTE!</b> Calling this method results in proof of work calculations being done for the
   * message. This can take a long time and lots of CPU power!<br>
   * <br>
   *
   * @param message - The original plain text Message object, provided so that its status can be
   *     updated during the process
   * @param unencMsg - The UnencryptedMsg object to be encrypted
   * @param toPubkey - The Pubkey object containing the public encryption key of the intended
   *     message recipient
   * @param doPOW - A boolean value indicating whether or not POW should be done for this message
   * @param timeToLive - The 'time to live' value (in seconds) to be used in creating this msg
   * @return A Msg object containing the encrypted message data
   */
  private BMObject constructMsg(
      Message message, UnencryptedMsg unencMsg, Pubkey toPubkey, boolean doPOW, long timeToLive) {
    // Reconstruct the ECPublicKey object from the byte[] found the the relevant PubKey
    ECPublicKey publicEncryptionKey =
        new KeyConverter().reconstructPublicKey(toPubkey.getPublicEncryptionKey());

    // Construct the payload to be encrypted
    byte[] msgDataForEncryption = constructMsgPayloadForEncryption(unencMsg);

    // Update the status of this message displayed in the UI
    String messageStatus = App.getContext().getString(R.string.message_status_encrypting_message);
    MessageStatusHandler.updateMessageStatus(message, messageStatus);

    // Encrypt the payload
    CryptProcessor cryptProc = new CryptProcessor();
    byte[] encryptedPayload = cryptProc.encrypt(msgDataForEncryption, publicEncryptionKey);

    // Create a new Msg object and populate its fields
    BMObject msg = new BMObject();
    msg.setBelongsToMe(
        true); // NOTE: This method assumes that any message I am encrypting 'belongs to me' (i.e.
    // The user of the application is the author of the message)
    msg.setExpirationTime(unencMsg.getExpirationTime());
    msg.setObjectType(unencMsg.getObjectType());
    msg.setObjectVersion(unencMsg.getObjectVersion());
    msg.setStreamNumber(toPubkey.getStreamNumber());
    msg.setPayload(encryptedPayload);

    if (doPOW) {
      MessageStatusHandler.updateMessageStatus(
          message, App.getContext().getString(R.string.message_status_doing_pow));

      // Do proof of work for the Msg object
      Log.i(TAG, "About to do POW calculations for a msg that we are sending");
      byte[] powPayload = constructMsgPayloadForPOW(msg);
      long powNonce =
          new POWProcessor()
              .doPOW(
                  powPayload,
                  unencMsg.getExpirationTime(),
                  toPubkey.getNonceTrialsPerByte(),
                  toPubkey.getExtraBytes());
      msg.setPOWNonce(powNonce);
    } else {
      msg.setPOWNonce(
          (long) 0); // If POW is not to be done for this message, set the powNonce as zero for now.
    }

    return msg;
  }
  /**
   * Takes a Msg and constructs the payload needed to do POW for it.
   *
   * @param msg - The msg Object to construct the POW payload for
   * @return The POW payload
   */
  private byte[] constructMsgPayloadForPOW(BMObject msg) {
    try {
      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

      outputStream.write(
          ByteUtils.longToBytes(
              msg.getExpirationTime())); // This conversion results in a byte[] of length 8, which
      // is what we want
      outputStream.write(ByteUtils.intToBytes(OBJECT_TYPE_MSG));
      outputStream.write(VarintEncoder.encode(OBJECT_VERSION_MSG));
      outputStream.write(VarintEncoder.encode(msg.getStreamNumber()));
      outputStream.write(msg.getPayload());

      return outputStream.toByteArray();
    } catch (IOException e) {
      throw new RuntimeException(
          "IOException occurred in OutgoingMessageProcessor.constructMsgPayloadForPOW()", e);
    }
  }