/// <summary>
        /// Retrieve the publications for a person and write them to the database
        /// </summary>
        /// <param name="ncbi">NCBI web query object</param>
        /// <param name="pubTypes">PublicationTypes object</param>
        /// <param name="person">Person to retrieve publications for</param>
        /// <param name="StatusCallback">Callback function to return status</param>
        /// <param name="MessageCallback">Callback function to send messages</param>
        /// <param name="AverageMilliseconds">Average time (in milliseconds) of each publication write</param>
        /// <returns>Number of publications written</returns>
        public int GetPublications(NCBI ncbi, PublicationTypes pubTypes, Person person,
                                   GetPublicationsStatus StatusCallback, GetPublicationsMessage MessageCallback,
                                   CheckForInterrupt InterruptCallback, out double AverageMilliseconds)
        {
            ArrayList Parameters;

            DateTime StartTime;
            DateTime EndTime;
            double   TotalMilliseconds = 0;

            AverageMilliseconds = 0;
            int numberFound   = 0;
            int numberWritten = 0;

            // Double-check that the person is really unharvested. If we try to
            // write publications for a person who already has publications, it will
            // cause an error -- and that could happen if this person was already
            // written from a duplicate person earlier.
            Parameters = new ArrayList();
            Parameters.Add(Database.Parameter(person.Setnb));
            int HarvestedCount = DB.GetIntValue("SELECT Count(*) FROM People WHERE Setnb = ? AND Harvested = 1", Parameters);

            if (HarvestedCount > 0)
            {
                MessageCallback("Already harvested publications for " + person.Last + " (" + person.Setnb + ")", false);
                Parameters = new ArrayList();
                Parameters.Add(Database.Parameter(person.Setnb));
                return(DB.GetIntValue("SELECT Count(*) FROM PeoplePublications WHERE Setnb = ?", Parameters));
            }


            MessageCallback("Retrieving data from NCBI", true);


            // Find any other people with the same names and search criteria.
            // Any publications found for this person should also be found
            // for them, so when we write the rows to PeoplePublications later
            // we'll also write them for the other people as well.

            // Look in the database for any other people with the same
            // values for name1, name2, name3, name4, name5, name6, and MedlineSearch.
            // Write their PeoplePublications as well.
            string NamesClause = "";

            Parameters = new ArrayList();
            for (int i = 0; i < 6; i++)
            {
                if (i < person.Names.Length)
                {
                    Parameters.Add(Database.Parameter(person.Names[i]));
                    NamesClause += " Name" + ((int)(i + 1)).ToString() + " = ? AND ";
                }
                else
                {
                    NamesClause += " Name" + ((int)(i + 1)).ToString() + " IS NULL AND ";
                }
            }
            Parameters.Add(Database.Parameter(person.MedlineSearch));
            Parameters.Add(Database.Parameter(person.Setnb));
            DataTable Results = DB.ExecuteQuery("SELECT " + Database.PEOPLE_COLUMNS +
                                                @"FROM People
                                                  WHERE Harvested = 0 AND "
                                                + NamesClause +
                                                @" MedlineSearch = ?
                                                    AND Setnb <> ?", Parameters
                                                );
            ArrayList DuplicatePeople = new ArrayList();

            foreach (DataRow Row in Results.Rows)
            {
                Person dupe = new Person(Row, Results.Columns);
                DuplicatePeople.Add(dupe);
                MessageCallback("Also writing publications for " + dupe.Last + " (" + dupe.Setnb + ") with same names and search criteria", false);
            }



            // Search NCBI -- if an error is thrown, write that error to the database
            string results;

            try
            {
                results = ncbi.Search(person.MedlineSearch);
                if (results.Substring(0, 100).Contains("Error occurred"))
                {
                    // NCBI returns an HTML error page in the results
                    //
                    // <html>
                    // <body>
                    // <br/><h2>Error occurred: Unable to obtain query #1</h2><br/>
                    // ...
                    //
                    // If NCBI returns an empty result set with no publications, it will give the error:
                    // Error occurred: Empty result - nothing todo
                    //
                    // That error should generate a warning and mark the person as harvested in the database.
                    // Any other error should be written to the database as an error.
                    string Error = results.Substring(results.IndexOf("Error occurred"));
                    if (results.Contains("<"))
                    {
                        Error = Error.Substring(0, Error.IndexOf("<"));
                    }
                    string Message;
                    if (Error.ToLower().Contains("empty result"))
                    {
                        Message = "Warning for "
                                  + person.Last + " (" + person.Setnb + "): no publications found (NCBI returned empty results)";
                        person.Harvested = true;
                        person.WriteToDB(DB);
                    }
                    else
                    {
                        Message = "Error reading publications for "
                                  + person.Last + " (" + person.Setnb + "): NCBI returned '" + Error + "'";
                        person.WriteErrorToDB(DB, Message);
                    }
                    MessageCallback(Message, false);
                    return(0);
                }
            }
            catch (Exception ex)
            {
                string Message = "Error reading publications for "
                                 + person.Last + " (" + person.Setnb + "): " + ex.Message;
                person.WriteErrorToDB(DB, Message);
                MessageCallback(Message, false);
                return(0);
            }

            Publications mpr = new Publications(results, pubTypes);

            if (mpr.PublicationList != null)
            {
                foreach (Publication publication in mpr.PublicationList)
                {
                    numberFound++;

                    // Exit immediately if the user interrupted the harvest
                    if (InterruptCallback())
                    {
                        return(numberWritten);
                    }

                    try
                    {
                        // Calculate the average time, to return in the callback status function
                        StartTime = DateTime.Now;

                        // Add the publication to PeoplePublications
                        // First find the author position and calculate the position type
                        int AuthorPosition = 0;
                        for (int i = 1; (publication.Authors != null) && (AuthorPosition == 0) && (i <= publication.Authors.Length); i++)
                        {
                            foreach (string name in person.Names)
                            {
                                if (StringComparer.CurrentCultureIgnoreCase.Equals(
                                        publication.Authors[i - 1], name //.ToUpper()
                                        ))
                                {
                                    AuthorPosition = i;
                                }
                                else if (name == "*")
                                {
                                    AuthorPosition = -1;
                                }
                            }
                        }

                        // If the PMID is 0, we don't have a way to process the publication
                        // and it was probably a Medline search result error.
                        if (publication.PMID == int.MinValue)
                        {
                            string errorMessage = "Found an invalid publication";
                            if (!string.IsNullOrEmpty(publication.Title))
                            {
                                errorMessage += " (Title = '" + publication.Title + "')";
                            }
                            person.WriteErrorToDB(DB, errorMessage);
                            MessageCallback(errorMessage, false);
                        }
                        else if (publication.PMID == 0)
                        {
                            string errorMessage = "WARNING: Found a publication with PMID = 0, not marking this as an error";
                            if (!string.IsNullOrEmpty(publication.Title))
                            {
                                errorMessage += " (Title = '" + publication.Title + "')";
                            }
                            MessageCallback(errorMessage, false);
                        }

                        // If for some reason the author doesn't exist in the publication, send a message back
                        else if (AuthorPosition == 0)
                        {
                            MessageCallback("Publication " + publication.PMID + " does not contain author " + person.Setnb, false);
                        }
                        else
                        {
                            // Write the publication to the database
                            if (Publications.WriteToDB(publication, DB, pubTypes, Languages))
                            {
                                // Exit immediately if the user interrupted the harvest
                                if (InterruptCallback())
                                {
                                    return(numberWritten);
                                }

                                // Only increment the publication count if the publication
                                // is actually written or already in the database
                                numberWritten++;

                                // Only add the row to PeoplePublications if the publication
                                // was written, or was already in the database. (For example,
                                // if the publication is not in English, it won't be written.)

                                Publications.WritePeoplePublicationsToDB(DB, person, publication);

                                // Write the publication for each of the other people
                                foreach (Person dupe in DuplicatePeople)
                                {
                                    Publications.WritePeoplePublicationsToDB(DB, dupe, publication);
                                }

                                // Calculate the average time per publication in milliseconds
                                EndTime = DateTime.Now;
                                TimeSpan Difference = EndTime - StartTime;
                                TotalMilliseconds  += Difference.TotalMilliseconds;
                                AverageMilliseconds = TotalMilliseconds / numberWritten;
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        person.WriteErrorToDB(DB, ex.Message);
                        MessageCallback("Error writing publication " + publication.PMID.ToString() + ": " + ex.Message, false);
                    }
                    StatusCallback(numberFound, mpr.PublicationList.Length, (int)AverageMilliseconds);
                }
            }

            // Make sure each of the people with the same names and search query
            // are marked as harvested and have their errors cleared
            foreach (Person dupe in DuplicatePeople)
            {
                Parameters = new ArrayList();
                Parameters.Add(Database.Parameter(dupe.Setnb));
                DB.ExecuteNonQuery(
                    @"UPDATE People
                         SET Harvested = 1, Error = NULL, ErrorMessage = NULL
                       WHERE Setnb = ?", Parameters);
            }

            // Once the publications are all read, updated People.Harvested, as part of
            // the fault-tolerance scheme -- PeoplePublications rows are only "final" when
            // this value is updated for the person. Any others can be cleared using
            // ClearDataAfterInterruption().
            Parameters = new ArrayList();
            Parameters.Add(Database.Parameter(person.Setnb));
            DB.ExecuteNonQuery(@"UPDATE People
                                    SET Harvested = 1
                                  WHERE Setnb = ?", Parameters);

            return(numberWritten);
        }