/**
   * Start a search
   *
   * @param author Author to search for
   * @param title Title to search for
   * @param isbn ISBN to search for
   */
  public void search(
      String author, String title, String isbn, boolean fetchThumbnail, int searchFlags) {
    if ((searchFlags & SEARCH_ALL) == 0)
      throw new RuntimeException("Must specify at least one source to use");

    if (mRunningTasks.size() > 0) {
      throw new RuntimeException("Attempting to start new search while previous search running");
    }

    // Save the flags
    mSearchFlags = searchFlags;
    if (!Utils.USE_LT) {
      mSearchFlags &= ~SEARCH_LIBRARY_THING;
    }

    // Save the input and initialize
    mBookData = new Bundle();
    mSearchResults = new Hashtable<Integer, Bundle>();

    mWaitingForIsbn = false;
    mCancelledFlg = false;

    mAuthor = author;
    mTitle = title;
    mIsbn = isbn;
    mHasIsbn = mIsbn != null && mIsbn.trim().length() > 0 && IsbnUtils.isValid(mIsbn);

    mFetchThumbnail = fetchThumbnail;

    // XXXX: Not entirely sure why this code was targetted at the UI thread.
    doSearch();
    // if (mTaskManager.runningInUiThread()) {
    //	doSearch();
    // } else {
    //	mTaskManager.postToUiThread(new Runnable() {
    //		@Override
    //		public void run() {
    //			doSearch();
    //		}});
    // }
  }
  private void doSearch() {
    // List for task ends
    TaskManager.getMessageSwitch().addListener(mTaskManager.getSenderId(), this, false);
    // System.out.println("Listening");

    // We really want to ensure we get the same book from each, so if isbn is not present, do
    // these in series.

    boolean tasksStarted = false;
    mSearchingAsin = false;
    try {
      if (mIsbn != null && mIsbn.length() > 0) {
        if (IsbnUtils.isValid(mIsbn)) {
          // We have an ISBN, just do the search
          mWaitingForIsbn = false;
          tasksStarted = this.startSearches(mSearchFlags);
        } else {
          // Assume it's an ASIN, and just search Amazon
          mSearchingAsin = true;
          mWaitingForIsbn = false;
          // mSearchFlags = SEARCH_AMAZON;
          tasksStarted = startOneSearch(SEARCH_AMAZON);
          // tasksStarted = this.startSearches(mSearchFlags);
        }
      } else {
        // Run one at a time, startNext() defined the order.
        mWaitingForIsbn = true;
        tasksStarted = startNext();
      }
    } finally {
      if (!tasksStarted) {
        sendResults();
        TaskManager.getMessageSwitch().removeListener(mTaskManager.getSenderId(), this);
        // System.out.println("Not listening(2)");
      }
    }
  }
  /** Combine all the data and create a book or display an error. */
  private void sendResults() {
    // This list will be the actual order of the result we apply, based on the
    // actual results and the default order.
    ArrayList<Integer> results = new ArrayList<Integer>();

    if (mHasIsbn) {
      // If ISBN was passed, ignore entries with the wrong ISBN, and put entries with no ISBN at the
      // end
      ArrayList<Integer> uncertain = new ArrayList<Integer>();
      for (int i : mDefaultReliabilityOrder) {
        if (mSearchResults.containsKey(i)) {
          Bundle bookData = mSearchResults.get(i);
          if (bookData.containsKey(CatalogueDBAdapter.KEY_ISBN)) {
            String isbn = bookData.getString(CatalogueDBAdapter.KEY_ISBN);
            if (IsbnUtils.matches(mIsbn, isbn)) {
              results.add(i);
            }
          } else {
            uncertain.add(i);
          }
        }
      }
      for (Integer i : uncertain) {
        results.add(i);
      }
      // Add the passed ISBN first; avoid overwriting
      mBookData.putString(CatalogueDBAdapter.KEY_ISBN, mIsbn);
    } else {
      // If ISBN was not passed, then just used the default order
      for (int i : mDefaultReliabilityOrder) results.add(i);
    }

    // Merge the data we have. We do this in a fixed order rather than as the threads finish.
    for (int i : results) accumulateData(i);

    // If there are thumbnails present, pick the biggest, delete others and rename.
    Utils.cleanupThumbnails(mBookData);

    // Try to use/construct authors
    String authors = null;
    try {
      authors = mBookData.getString(CatalogueDBAdapter.KEY_AUTHOR_DETAILS);
    } catch (Exception e) {
    }

    if (authors == null || authors.equals("")) {
      authors = mAuthor;
    }

    if (authors != null && !authors.equals("")) {
      // Decode the collected author names and convert to an ArrayList
      ArrayList<Author> aa = Utils.getAuthorUtils().decodeList(authors, '|', false);
      mBookData.putSerializable(CatalogueDBAdapter.KEY_AUTHOR_ARRAY, aa);
    }

    // Try to use/construct title
    String title = null;
    try {
      title = mBookData.getString(CatalogueDBAdapter.KEY_TITLE);
    } catch (Exception e) {
    }

    if (title == null || title.equals("")) title = mTitle;

    if (title != null && !title.equals("")) {
      mBookData.putString(CatalogueDBAdapter.KEY_TITLE, title);
    }

    // Try to use/construct isbn
    String isbn = null;
    try {
      isbn = mBookData.getString(CatalogueDBAdapter.KEY_ISBN);
    } catch (Exception e) {
    }

    if (isbn == null || isbn.equals("")) isbn = mIsbn;

    if (isbn != null && !isbn.equals("")) {
      mBookData.putString(CatalogueDBAdapter.KEY_ISBN, isbn);
    }

    // Try to use/construct series
    String series = null;
    try {
      series = mBookData.getString(CatalogueDBAdapter.KEY_SERIES_DETAILS);
    } catch (Exception e) {
    }

    if (series != null && !series.equals("")) {
      // Decode the collected series names and convert to an ArrayList
      try {
        ArrayList<Series> sa = Utils.getSeriesUtils().decodeList(series, '|', false);
        mBookData.putSerializable(CatalogueDBAdapter.KEY_SERIES_ARRAY, sa);
      } catch (Exception e) {
        Logger.logError(e);
      }
    } else {
      // add series to stop crashing
      mBookData.putSerializable(CatalogueDBAdapter.KEY_SERIES_ARRAY, new ArrayList<Series>());
    }

    //
    // TODO: this needs to be locale-specific. Currently we probably get good-enough data without
    // forcing a cleanup.
    //
    // Removed 20-Jan-2016 PJW; see Issue 717.
    //
    // Cleanup other fields
    // Utils.doProperCase(mBookData, CatalogueDBAdapter.KEY_TITLE);
    // Utils.doProperCase(mBookData, CatalogueDBAdapter.KEY_PUBLISHER);
    // Utils.doProperCase(mBookData, CatalogueDBAdapter.KEY_DATE_PUBLISHED);
    // Utils.doProperCase(mBookData, CatalogueDBAdapter.KEY_SERIES_NAME);

    // If book is not found or missing required data, warn the user
    if (authors == null || authors.length() == 0 || title == null || title.length() == 0) {
      mTaskManager.doToast(BookCatalogueApp.getResourceString(R.string.book_not_found));
    }
    // Pass the data back
    sendSearchFinished();
  }