/**
   * Takes an UnencryptedMsg object and extracts only the data needed to encrypt the message,
   * discarding data that is only used by Bitseal internally, such as the ID number.
   *
   * @param inputMsgData - The UnencryptedMsg object from which the data is to be extracted
   * @return A byte[] containing the message data needed for encryption
   */
  private byte[] constructMsgPayloadForEncryption(UnencryptedMsg unencMsg) {
    byte[] msgDataForEncryption = null;
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try {
      outputStream.write(VarintEncoder.encode(unencMsg.getSenderAddressVersion()));
      outputStream.write(VarintEncoder.encode(unencMsg.getSenderStreamNumber()));
      outputStream.write(ByteUtils.intToBytes(unencMsg.getBehaviourBitfield()));

      // If the public signing and public encryption keys have their leading 0x04 byte in place then
      // we need to remove them
      byte[] publicSigningKey = unencMsg.getPublicSigningKey();
      if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) {
        publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length);
      }
      outputStream.write(publicSigningKey);

      byte[] publicEncryptionKey = unencMsg.getPublicEncryptionKey();
      if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) {
        publicEncryptionKey =
            ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length);
      }
      outputStream.write(publicEncryptionKey);

      if (unencMsg.getSenderAddressVersion()
          >= 3) // The nonceTrialsPerByte and extraBytes fields are only included when the address
      // version is >= 3
      {
        outputStream.write(VarintEncoder.encode(unencMsg.getNonceTrialsPerByte()));
        outputStream.write(VarintEncoder.encode(unencMsg.getExtraBytes()));
      }

      outputStream.write(unencMsg.getDestinationRipe());
      outputStream.write(VarintEncoder.encode(unencMsg.getEncoding()));
      outputStream.write(VarintEncoder.encode(unencMsg.getMessageLength()));
      outputStream.write(unencMsg.getMessage());
      outputStream.write(VarintEncoder.encode(unencMsg.getAckLength()));
      outputStream.write(unencMsg.getAckMsg());
      outputStream.write(VarintEncoder.encode(unencMsg.getSignatureLength()));
      outputStream.write(unencMsg.getSignature());

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

    return msgDataForEncryption;
  }
  /**
   * Constructs an UnencryptedMsg object from a given Message object. Used when sending a message.
   * <br>
   * <br>
   * <b>NOTE!</b> Calling this method results in proof of work calculations being done for the
   * acknowledgement data of the message. This can take a long time and lots of CPU power!<br>
   * <br>
   * <b>NOTE!</b> Calling this method can result in requests to a Bitseal server to retrieve pubkey
   * data. These requests may take some time to complete!
   *
   * @param message - The Message object to convert into an UnencryptedMsg object
   * @param toPubkey - A Pubkey object containing the public keys of the address the message is
   *     being sent to
   * @param doPOW - A boolean indicating whether or not POW should be done for msgs generated during
   *     this process
   * @param timeToLive - The 'time to live' value (in seconds) to be used in processing this message
   * @return An UnencryptedMsg object based on the supplied Message object.
   */
  private UnencryptedMsg constructUnencryptedMsg(
      Message message, Pubkey toPubkey, boolean doPOW, long timeToLive) {
    String messageSubject = message.getSubject();
    String messageBody = message.getBody();

    // First let us check that the to address and from address Strings taken from the Message object
    // are in fact valid Bitmessage addresses
    String toAddressString = message.getToAddress();
    String fromAddressString = message.getFromAddress();
    AddressProcessor addProc = new AddressProcessor();

    if (addProc.validateAddress(toAddressString) != true) {
      throw new RuntimeException(
          "During the execution of constructUnencryptedMsg(), it was found that the 'to' address in the supplied Message was not a valid Bitmessage address");
    }
    if (addProc.validateAddress(fromAddressString) != true) {
      throw new RuntimeException(
          "During the execution of constructUnencryptedMsg(), it was found that the 'from' address in the supplied Message was not a valid Bitmessage address");
    }

    // Now that we have validated the to address and the from address, let us retrieve or create
    // their corresponding Address and Pubkey objects.
    Address fromAddress = null;
    AddressProvider addProv = AddressProvider.get(App.getContext());
    ArrayList<Address> retrievedAddresses =
        addProv.searchAddresses(AddressesTable.COLUMN_ADDRESS, fromAddressString);
    if (retrievedAddresses.size() != 1) {
      Log.e(
          TAG,
          "There should be exactly 1 record found in this search. Instead "
              + retrievedAddresses.size()
              + " records were found");
    } else {
      fromAddress = retrievedAddresses.get(0);
    }

    // Now we need to get the behaviour bitfield from the pubkey which corresponds to the from
    // address, so let us retrieve that pubkey.
    PubkeyProvider pubProv = PubkeyProvider.get(App.getContext());
    ArrayList<Pubkey> retrievedPubkeys =
        pubProv.searchPubkeys(
            PubkeysTable.COLUMN_CORRESPONDING_ADDRESS_ID, String.valueOf(fromAddress.getId()));
    Pubkey fromPubkey = null;
    if (retrievedPubkeys.size() == 1) {
      fromPubkey = retrievedPubkeys.get(0);
    } else if (retrievedPubkeys.size() > 1) // If there are duplicate pubkeys for this address
    {
      Log.e(
          TAG,
          "There should be exactly 1 record found in this search. Instead "
              + retrievedPubkeys.size()
              + " records were found");

      // Delete all but the most recent of the duplicate pubkeys
      long firstPubkeyTime = retrievedPubkeys.get(0).getExpirationTime();
      Pubkey pubkeyToKeep = retrievedPubkeys.get(0);
      for (Pubkey p : retrievedPubkeys) {
        if (p.getExpirationTime() > firstPubkeyTime) {
          pubkeyToKeep = p;
        }
      }
      for (Pubkey p : retrievedPubkeys) {
        if (p.equals(pubkeyToKeep) == false) {
          pubProv.deletePubkey(p);
        }
      }

      // Use the most recent of the duplicate pubkeys
      fromPubkey = pubkeyToKeep;
    }

    if (fromPubkey == null) {
      Log.e(
          TAG,
          "Could not find the Pubkey which corresponds to the from address, even though it should be one of our own. Something is wrong!");
      Log.d(TAG, "Regenerating the Pubkey for the from address");
      fromPubkey =
          new PubkeyGenerator()
              .generateAndSaveNewPubkey(
                  fromAddress); // If we can't find the pubkey we need then let us generate it again
    }

    // Now extract the public signing and public encryption keys from the "from" pubkey
    // If the public signing and encryption keys taken from the Pubkey object have an "\x04" byte at
    // their beginning, we need to remove it now.
    byte[] publicSigningKey = fromPubkey.getPublicSigningKey();
    byte[] publicEncryptionKey = fromPubkey.getPublicEncryptionKey();

    if (publicSigningKey[0] == (byte) 4 && publicSigningKey.length == 65) {
      publicSigningKey = ArrayCopier.copyOfRange(publicSigningKey, 1, publicSigningKey.length);
    }

    if (publicEncryptionKey[0] == (byte) 4 && publicEncryptionKey.length == 65) {
      publicEncryptionKey =
          ArrayCopier.copyOfRange(publicEncryptionKey, 1, publicEncryptionKey.length);
    }

    // Generate the ack data (32 random bytes)
    byte[] ackData = new byte[32];
    new SecureRandom().nextBytes(ackData);

    // Generate the full ack Message that will be included in this unencrypted msg.
    // NOTE: Calling generateFullAckMessage() results in Proof of Work calculations being done for
    // the
    //       acknowledgement Message. This can take a long time and lots of CPU power!
    byte[] fullAckMessage =
        generateFullAckMessage(message, ackData, fromPubkey.getStreamNumber(), doPOW, timeToLive);
    Log.d(TAG, "Full ack Message: " + ByteFormatter.byteArrayToHexString(fullAckMessage));

    // Create the single "message" text String which contains both the subject and the body of the
    // message
    // See https://bitmessage.org/wiki/Protocol_specification#Message_Encodings
    String messsageText = "Subject:" + messageSubject + "\n" + "Body:" + messageBody;

    // Now create the UnencryptedMsg object and populate its fields.
    UnencryptedMsg unencMsg = new UnencryptedMsg();

    unencMsg.setBelongsToMe(true);
    unencMsg.setExpirationTime(TimeUtils.getFuzzedExpirationTime(timeToLive));
    unencMsg.setObjectType(OBJECT_TYPE_MSG);
    unencMsg.setObjectVersion(OBJECT_VERSION_MSG);
    unencMsg.setStreamNumber(toPubkey.getStreamNumber());
    unencMsg.setSenderAddressVersion(fromPubkey.getObjectVersion());
    unencMsg.setSenderStreamNumber(fromPubkey.getStreamNumber());
    unencMsg.setBehaviourBitfield(fromPubkey.getBehaviourBitfield());
    unencMsg.setPublicSigningKey(publicSigningKey);
    unencMsg.setPublicEncryptionKey(publicEncryptionKey);
    unencMsg.setNonceTrialsPerByte(fromPubkey.getNonceTrialsPerByte());
    unencMsg.setExtraBytes(fromPubkey.getExtraBytes());
    unencMsg.setDestinationRipe(new KeyConverter().calculateRipeHashFromPubkey(toPubkey));
    unencMsg.setEncoding(MESSAGE_ENCODING_TYPE);
    unencMsg.setMessageLength(
        messsageText.getBytes()
            .length); // We have to use the byte length rather than the string length - some
    // characters take more bytes than others
    unencMsg.setMessage(
        messsageText
            .getBytes()); // PyBitmessage also uses UTF-8 as its character set, so this ought to be
    // adequate
    unencMsg.setAckLength(fullAckMessage.length);
    unencMsg.setAckMsg(fullAckMessage);

    // Save the acknowledgment data to the database so that when we receive the acknowledgment for
    // this message we will recognise it
    Payload ackPayload = new Payload();
    ackPayload.setBelongsToMe(true); // i.e. This is an acknowledgment created by me
    ackPayload.setPOWDone(true);
    ackPayload.setAck(true); // This payload is an acknowledgment
    ackPayload.setType(
        Payload.OBJECT_TYPE_MSG); // Currently we treat all acks from other people as msgs. Strictly
    // though they can be objects of any type, so this may change
    ackPayload.setPayload(ackData);
    PayloadProvider payProv = PayloadProvider.get(App.getContext());
    long ackPayloadId = payProv.addPayload(ackPayload);

    // Set the "ackPayloadId" field of the original Message object so that we know which Message
    // this ack data is for
    message.setAckPayloadId(ackPayloadId);
    MessageProvider msgProv = MessageProvider.get(App.getContext());
    msgProv.updateMessage(message);

    // Now create the signature for this message
    SigProcessor sigProc = new SigProcessor();
    byte[] signaturePayload = sigProc.createUnencryptedMsgSignaturePayload(unencMsg);
    byte[] signature = sigProc.signWithWIFKey(signaturePayload, fromAddress.getPrivateSigningKey());

    unencMsg.setSignature(signature);
    unencMsg.setSignatureLength(signature.length);

    return unencMsg;
  }