/**
   * Deletes an Address object from the application's SQLite database<br>
   * <br>
   * <b>NOTE:</b> This method uses the given Address's ID field to determine which record in the
   * database to delete
   *
   * @param a - The Address object to be deleted
   */
  public void deleteAddress(Address a) {
    long id = a.getId();

    // Query the database via the ContentProvider and delete the record with the matching ID
    int recordsDeleted =
        mContentResolver.delete(
            DatabaseContentProvider.CONTENT_URI_ADDRESSES,
            AddressesTable.COLUMN_ID + " = ? ",
            new String[] {String.valueOf(id)});
    if (recordsDeleted > 0) {
      Log.i(TAG, "Address " + a.getAddress() + " deleted from database");
    } else {
      Log.e(
          TAG,
          "Unable to find the address specified for deletion. The address specified was "
              + a.getAddress());
    }
  }
  /**
   * Updates the database record for a given Address object<br>
   * <br>
   * <b>NOTE:</b> This method uses the given Address's ID field to determine which record in the
   * database to update
   *
   * @param a - The Address object to be updated
   */
  public void updateAddress(Address a) {
    ContentValues values = new ContentValues();
    values.put(AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID, a.getCorrespondingPubkeyId());
    values.put(AddressesTable.COLUMN_LABEL, a.getLabel());
    values.put(AddressesTable.COLUMN_ADDRESS, a.getAddress());
    values.put(AddressesTable.COLUMN_PRIVATE_SIGNING_KEY, a.getPrivateSigningKey());
    values.put(AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY, a.getPrivateEncryptionKey());
    values.put(
        AddressesTable.COLUMN_RIPE_HASH, Base64.encodeToString(a.getRipeHash(), Base64.DEFAULT));
    values.put(AddressesTable.COLUMN_TAG, Base64.encodeToString(a.getTag(), Base64.DEFAULT));

    long id = a.getId();

    // Query the database via the ContentProvider and update the record with the matching ID
    mContentResolver.update(
        DatabaseContentProvider.CONTENT_URI_ADDRESSES,
        values,
        AddressesTable.COLUMN_ID + " = ? ",
        new String[] {String.valueOf(id)});

    Log.i(TAG, "Address ID " + id + " updated");
  }
  /**
   * 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;
  }