/**
   * Returns a list of candidate contacts where the query is a prefix of the dialpad index of the
   * contact's name or phone number.
   *
   * @param query The prefix of a contact's dialpad index.
   * @return A list of top candidate contacts that will be suggested to user to match their input.
   */
  public ArrayList<ContactNumber> getLooseMatches(String query, SmartDialNameMatcher nameMatcher) {
    final boolean inUpdate = sInUpdate.get();
    if (inUpdate) {
      return Lists.newArrayList();
    }

    final SQLiteDatabase db = getReadableDatabase();

    /** Uses SQL query wildcard '%' to represent prefix matching. */
    final String looseQuery = query + "%";

    final ArrayList<ContactNumber> result = Lists.newArrayList();

    final StopWatch stopWatch = DEBUG ? StopWatch.start(":Name Prefix query") : null;

    final String currentTimeStamp = Long.toString(System.currentTimeMillis());

    /** Queries the database to find contacts that have an index matching the query prefix. */
    final Cursor cursor =
        db.rawQuery(
            "SELECT "
                + SmartDialDbColumns.DATA_ID
                + ", "
                + SmartDialDbColumns.DISPLAY_NAME_PRIMARY
                + ", "
                + SmartDialDbColumns.PHOTO_ID
                + ", "
                + SmartDialDbColumns.NUMBER
                + ", "
                + SmartDialDbColumns.CONTACT_ID
                + ", "
                + SmartDialDbColumns.LOOKUP_KEY
                + " FROM "
                + Tables.SMARTDIAL_TABLE
                + " WHERE "
                + SmartDialDbColumns.CONTACT_ID
                + " IN "
                + " (SELECT "
                + PrefixColumns.CONTACT_ID
                + " FROM "
                + Tables.PREFIX_TABLE
                + " WHERE "
                + Tables.PREFIX_TABLE
                + "."
                + PrefixColumns.PREFIX
                + " LIKE '"
                + looseQuery
                + "')"
                + " ORDER BY "
                + SmartDialSortingOrder.SORT_ORDER,
            new String[] {currentTimeStamp});

    if (DEBUG) {
      stopWatch.lap("Prefix query completed");
    }

    /** Gets the column ID from the cursor. */
    final int columnDataId = 0;
    final int columnDisplayNamePrimary = 1;
    final int columnPhotoId = 2;
    final int columnNumber = 3;
    final int columnId = 4;
    final int columnLookupKey = 5;
    if (DEBUG) {
      stopWatch.lap("Found column IDs");
    }

    final Set<ContactMatch> duplicates = new HashSet<ContactMatch>();
    int counter = 0;
    try {
      if (DEBUG) {
        stopWatch.lap("Moved cursor to start");
      }
      /** Iterates the cursor to find top contact suggestions without duplication. */
      while ((cursor.moveToNext()) && (counter < MAX_ENTRIES)) {
        final long dataID = cursor.getLong(columnDataId);
        final String displayName = cursor.getString(columnDisplayNamePrimary);
        final String phoneNumber = cursor.getString(columnNumber);
        final long id = cursor.getLong(columnId);
        final long photoId = cursor.getLong(columnPhotoId);
        final String lookupKey = cursor.getString(columnLookupKey);

        /**
         * If a contact already exists and another phone number of the contact is being processed,
         * skip the second instance.
         */
        final ContactMatch contactMatch = new ContactMatch(lookupKey, id);
        if (duplicates.contains(contactMatch)) {
          continue;
        }

        /**
         * If the contact has either the name or number that matches the query, add to the result.
         */
        final boolean nameMatches = nameMatcher.matches(displayName);
        final boolean numberMatches = (nameMatcher.matchesNumber(phoneNumber, query) != null);
        if (nameMatches || numberMatches) {
          /** If a contact has not been added, add it to the result and the hash set. */
          duplicates.add(contactMatch);
          result.add(new ContactNumber(id, dataID, displayName, phoneNumber, lookupKey, photoId));
          counter++;
          if (DEBUG) {
            stopWatch.lap("Added one result");
          }
        }
      }

      if (DEBUG) {
        stopWatch.stopAndLog(TAG + "Finished loading cursor", 0);
      }
    } finally {
      cursor.close();
    }
    return result;
  }