/**
   * 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;
  }
  /**
   * Calculates the acknowledgement Message for a given message. <br>
   * <br>
   * The process for this is as follows:<br>
   * <br>
   * 1) initialPayload = time || stream number || 32 bytes of random data<br>
   * <br>
   * 2) Do POW for the initialPayload<br>
   * <br>
   * 3) ackData = POWnonce || msgHeader || initialPayload<br>
   * <br>
   *
   * @param message - The original plain text Message object, provided so that its status can be
   *     updated during the process
   * @param ackData - A byte[] containing the 32 bytes of random data which is the acknowledgment
   *     data
   * @param toStreamNumber - An int representing the stream number of the destination address of the
   *     message to be sent
   * @param doPOW - A boolean indicating whether or not POW should be done for ack msgs generated
   *     during this process
   * @param timeToLive - The 'time to live' value (in seconds) to be used in processing this message
   * @return A byte[] containing the acknowledgement data for the message we wish to send
   */
  private byte[] generateFullAckMessage(
      Message message, byte[] ackData, int toStreamNumber, boolean doPOW, long timeToLive) {
    // Get the fuzzed expiration time
    long expirationTime = TimeUtils.getFuzzedExpirationTime(timeToLive);

    // Encode the expiration time, object type, object version, and stream number values into byte
    // form
    byte[] expirationTimeBytes = ByteUtils.longToBytes((expirationTime));
    byte[] objectTypeBytes = ByteUtils.intToBytes(OBJECT_TYPE_MSG);
    byte[] objectVersionBytes = VarintEncoder.encode(OBJECT_VERSION_MSG);
    byte[] streamNumberBytes = VarintEncoder.encode((long) toStreamNumber);

    // Combine the time, object type, object version, stream number, and ack data values into a
    // single byte[]
    byte[] initialPayload =
        ByteUtils.concatenateByteArrays(
            expirationTimeBytes, objectTypeBytes, objectVersionBytes, streamNumberBytes, ackData);

    // Create the payload for the ack msg
    byte[] payload = new byte[0];
    if (doPOW) {
      // Update the status of this message displayed in the UI
      String messageStatus = App.getContext().getString(R.string.message_status_doing_ack_pow);
      MessageStatusHandler.updateMessageStatus(message, messageStatus);

      // Do proof of work for the acknowledgement payload
      Log.i(
          TAG,
          "About to do POW calculations for the acknowledgment payload of a msg that we are sending");
      long powNonce =
          new POWProcessor()
              .doPOW(
                  initialPayload,
                  expirationTime,
                  POWProcessor.NETWORK_NONCE_TRIALS_PER_BYTE,
                  POWProcessor.NETWORK_EXTRA_BYTES);

      byte[] powNonceBytes = ByteUtils.longToBytes(powNonce);

      payload = ByteUtils.concatenateByteArrays(powNonceBytes, initialPayload);
    } else {
      payload = initialPayload;
    }

    byte[] headerData = new MessageProcessor().generateObjectHeader(payload);
    byte[] fullAckMsg = ByteUtils.concatenateByteArrays(headerData, payload);

    return fullAckMsg;
  }