/**
   * 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());
    }
  }
  /**
   * Returns an ArrayList containing all the Addresses stored in the application's database
   *
   * @return An ArrayList containing one Address object for each record in the Addresses table.
   */
  public ArrayList<Address> getAllAddresses() {
    ArrayList<Address> addresses = new ArrayList<Address>();

    // Specify which columns from the table we are interested in
    String[] projection = {
      AddressesTable.COLUMN_ID,
      AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID,
      AddressesTable.COLUMN_LABEL,
      AddressesTable.COLUMN_ADDRESS,
      AddressesTable.COLUMN_PRIVATE_SIGNING_KEY,
      AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY,
      AddressesTable.COLUMN_RIPE_HASH,
      AddressesTable.COLUMN_TAG
    };

    // Query the database via the ContentProvider
    Cursor cursor =
        mContentResolver.query(
            DatabaseContentProvider.CONTENT_URI_ADDRESSES, projection, null, null, null);

    if (cursor.moveToFirst()) {
      do {
        long id = cursor.getLong(0);
        long correspondingPubkeyId = cursor.getLong(1);
        String label = cursor.getString(2);
        String address = cursor.getString(3);
        String privateSigningKey = cursor.getString(4);
        String privateEncryptionKey = cursor.getString(5);
        byte[] ripeHash = Base64.decode(cursor.getString(6), Base64.DEFAULT);
        byte[] tag = Base64.decode(cursor.getString(7), Base64.DEFAULT);

        Address a = new Address();
        a.setId(id);
        a.setCorrespondingPubkeyId(correspondingPubkeyId);
        a.setLabel(label);
        a.setAddress(address);
        a.setPrivateSigningKey(privateSigningKey);
        a.setPrivateEncryptionKey(privateEncryptionKey);
        a.setRipeHash(ripeHash);
        a.setTag(tag);

        addresses.add(a);
      } while (cursor.moveToNext());
    }

    cursor.close();
    return addresses;
  }
  /**
   * Takes an Address object and adds it to the app's SQLite database as a new record, returning the
   * ID of the newly created record.
   *
   * @param a - The Address object to be added
   * @return id - A long value representing the ID of the newly created record
   */
  public long addAddress(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));

    Uri insertionUri =
        mContentResolver.insert(DatabaseContentProvider.CONTENT_URI_ADDRESSES, values);
    Log.i(TAG, "Address with address " + a.getAddress() + " saved to database");

    // Parse the ID of the newly created record from the insertion Uri
    String uriString = insertionUri.toString();
    String idString = uriString.substring(uriString.indexOf("/") + 1);
    long id = Long.parseLong(idString);
    return id;
  }
  /**
   * 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;
  }
  /**
   * Finds all Addresses in the application's database that match the given field
   *
   * @param columnName - A String specifying the name of the column in the database that should be
   *     used to find matching records. See the AddressesTable class to find the relevant column
   *     name.
   * @param searchString - A String specifying the value to search for. There are 4 use cases for
   *     this:<br>
   *     1) The value to search for is a String (e.g. A label from the UI). In this case the value
   *     can be passed in directly.<br>
   *     2) The value to search for is an int or long. In this case you should use String.valueOf(x)
   *     and pass in the resulting String.<br>
   *     3) The value to search for is a boolean. In this case you should pass in the String "0" for
   *     false or the String "1" for true. <br>
   *     4) The value to search for is a byte[]. In this case you should encode the byte[] into a
   *     Base64 encoded String using the class android.util.Base64 and pass in the resulting String.
   *     <br>
   *     <br>
   *     <b>NOTE:</b> The above String conversion is very clumsy, but seems to be necessary. See
   *     https://stackoverflow.com/questions/20911760/android-how-to-query-sqlitedatabase-with-non-string-selection-args
   * @return An ArrayList containing Address objects populated with the data from the database
   *     search
   */
  public ArrayList<Address> searchAddresses(String columnName, String searchString) {
    ArrayList<Address> matchingRecords = new ArrayList<Address>();

    // Specify which columns from the table we are interested in
    String[] projection = {
      AddressesTable.COLUMN_ID,
      AddressesTable.COLUMN_CORRESPONDING_PUBKEY_ID,
      AddressesTable.COLUMN_LABEL,
      AddressesTable.COLUMN_ADDRESS,
      AddressesTable.COLUMN_PRIVATE_SIGNING_KEY,
      AddressesTable.COLUMN_PRIVATE_ENCRYPTION_KEY,
      AddressesTable.COLUMN_RIPE_HASH,
      AddressesTable.COLUMN_TAG
    };

    // Query the database via the ContentProvider
    Cursor cursor =
        mContentResolver.query(
            DatabaseContentProvider.CONTENT_URI_ADDRESSES,
            projection,
            AddressesTable.TABLE_ADDRESSES + "." + columnName + " = ? ",
            new String[] {searchString},
            null);

    if (cursor.moveToFirst()) {
      do {
        long id = cursor.getLong(0);
        long correspondingPubkeyId = cursor.getLong(1);
        String label = cursor.getString(2);
        String address = cursor.getString(3);
        String privateSigningKey = cursor.getString(4);
        String privateEncryptionKey = cursor.getString(5);
        byte[] ripeHash = Base64.decode(cursor.getString(6), Base64.DEFAULT);
        byte[] tag = Base64.decode(cursor.getString(7), Base64.DEFAULT);

        Address a = new Address();
        a.setId(id);
        a.setCorrespondingPubkeyId(correspondingPubkeyId);
        a.setLabel(label);
        a.setAddress(address);
        a.setPrivateSigningKey(privateSigningKey);
        a.setPrivateEncryptionKey(privateEncryptionKey);
        a.setRipeHash(ripeHash);
        a.setTag(tag);

        matchingRecords.add(a);
      } while (cursor.moveToNext());
    } else {
      Log.i(
          TAG,
          "Unable to find any Addresses with the value "
              + searchString
              + " in the "
              + columnName
              + " column");
      cursor.close();
      return matchingRecords;
    }

    cursor.close();
    return matchingRecords;
  }