/**
   * 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;
  }
  /**
   * 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;
  }