// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/spellchecker.h"

#include "app/l10n_util.h"
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/file_util.h"
#include "base/histogram.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/stats_counters.h"
#include "base/string_util.h"
#include "chrome/browser/net/url_fetcher.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/spellchecker_common.h"
#include "chrome/browser/spellchecker_platform_engine.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_counters.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"
#include "net/url_request/url_request.h"
#include "third_party/hunspell/src/hunspell/hunspell.hxx"

using base::TimeTicks;

namespace {

static const struct {
  // The language.
  const char* language;

  // The corresponding language and region, used by the dictionaries.
  const char* language_region;
} g_supported_spellchecker_languages[] = {
  {"en-US", "en-US"},
  {"en-GB", "en-GB"},
  {"en-AU", "en-AU"},
  {"fr", "fr-FR"},
  {"it", "it-IT"},
  {"de", "de-DE"},
  {"es", "es-ES"},
  {"nl", "nl-NL"},
  {"pt-BR", "pt-BR"},
  {"ru", "ru-RU"},
  {"pl", "pl-PL"},
  // {"th", "th-TH"}, // Not to be included in Spellchecker as per B=1277824
  {"sv", "sv-SE"},
  {"da", "da-DK"},
  {"pt-PT", "pt-PT"},
  {"ro", "ro-RO"},
  // {"hu", "hu-HU"}, // Not to be included in Spellchecker as per B=1277824
  {"he", "he-IL"},
  {"id", "id-ID"},
  {"cs", "cs-CZ"},
  {"el", "el-GR"},
  {"nb", "nb-NO"},
  {"vi", "vi-VN"},
  // {"bg", "bg-BG"}, // Not to be included in Spellchecker as per B=1277824
  {"hr", "hr-HR"},
  {"lt", "lt-LT"},
  {"sk", "sk-SK"},
  {"sl", "sl-SI"},
  {"ca", "ca-ES"},
  {"lv", "lv-LV"},
  // {"uk", "uk-UA"}, // Not to be included in Spellchecker as per B=1277824
  {"hi", "hi-IN"},
  {"et", "et-EE"},
  {"tr", "tr-TR"},
};

// Get the fallback folder (currently chrome::DIR_USER_DATA) where the
// dictionary is downloaded in case of system-wide installations.
FilePath GetFallbackDictionaryDownloadDirectory() {
  FilePath dict_dir_userdata;
  PathService::Get(chrome::DIR_USER_DATA, &dict_dir_userdata);
  dict_dir_userdata = dict_dir_userdata.AppendASCII("Dictionaries");
  return dict_dir_userdata;
}

bool SaveBufferToFile(const std::string& data,
                      FilePath file_to_write) {
  int num_bytes = data.length();
  return file_util::WriteFile(file_to_write, data.data(), num_bytes) ==
      num_bytes;
}

}  // namespace

// Design: The spellchecker initializes hunspell_ in the Initialize() method.
// This is done using the dictionary file on disk, e.g. "en-US_1_1.bdic".
// Initialization of hunspell_ is held off during this process. If the
// dictionary is not available, we first attempt to download and save it. After
// the dictionary is downloaded and saved to disk (or the attempt to do so
// fails)), corresponding flags are set in spellchecker - in the IO thread.
// After the flags are cleared, a (final) attempt is  made to initialize
// hunspell_. If it fails even then (dictionary could not download), no more
// attempts are made to initialize it.
class SaveDictionaryTask : public Task {
 public:
  SaveDictionaryTask(Task* on_dictionary_save_complete_callback_task,
                     const FilePath& first_attempt_file_name,
                     const FilePath& fallback_file_name,
                     const std::string& data)
      : on_dictionary_save_complete_callback_task_(
            on_dictionary_save_complete_callback_task),
        first_attempt_file_name_(first_attempt_file_name),
        fallback_file_name_(fallback_file_name),
        data_(data) {
  }

 private:
  void Run();

  bool SaveBufferToFile(const std::string& data,
                        FilePath file_to_write) {
    int num_bytes = data.length();
    return file_util::WriteFile(file_to_write, data.data(), num_bytes) ==
        num_bytes;
  }

  // factory object to invokelater back to spellchecker in io thread on
  // download completion to change appropriate flags.
  Task* on_dictionary_save_complete_callback_task_;

  // The file which will be stored in the first attempt.
  FilePath first_attempt_file_name_;

  // The file which will be stored as a fallback.
  FilePath fallback_file_name_;

  // The buffer which has to be stored to disk.
  std::string data_;

  // This invokes back to io loop when downloading is over.
  DISALLOW_COPY_AND_ASSIGN(SaveDictionaryTask);
};

void SaveDictionaryTask::Run() {
  if (!SaveBufferToFile(data_, first_attempt_file_name_)) {
    // Try saving it to |fallback_file_name_|, which almost surely has
    // write permission. If even this fails, there is nothing to be done.
    FilePath fallback_dir = fallback_file_name_.DirName();
    // Create the directory if it does not exist.
    if (!file_util::PathExists(fallback_dir))
      file_util::CreateDirectory(fallback_dir);
    SaveBufferToFile(data_, fallback_file_name_);
  }  // Unsuccessful save is taken care of in SpellChecker::Initialize().

  // Set Flag that dictionary is not downloading anymore.
  ChromeThread::PostTask(
      ChromeThread::IO, FROM_HERE, on_dictionary_save_complete_callback_task_);
}

// Design: this task tries to read the dictionary from disk and load it into
// memory. It is executed on the file thread, and posts the results back to
// the IO thread.
// The task first checks for the existence of the dictionary in one of the two
// given locations. If it does not exist, the task informs the SpellChecker,
// which will try to download the directory and run a new ReadDictionaryTask.
class ReadDictionaryTask : public Task {
 public:
  ReadDictionaryTask(SpellChecker* spellchecker,
                     const FilePath& dict_file_name_app,
                     const FilePath& dict_file_name_usr)
        : spellchecker_(spellchecker),
          hunspell_(NULL),
          bdict_file_(NULL),
          custom_dictionary_file_name_(
              spellchecker->custom_dictionary_file_name_),
          dict_file_name_app_(dict_file_name_app),
          dict_file_name_usr_(dict_file_name_usr) {
  }

  virtual void Run() {
    FilePath bdict_file_path;
    if (file_util::PathExists(dict_file_name_app_)) {
      bdict_file_path = dict_file_name_app_;
    } else if (file_util::PathExists(dict_file_name_usr_)) {
      bdict_file_path = dict_file_name_usr_;
    } else {
      Finish(false);
      return;
    }

    bdict_file_ = new file_util::MemoryMappedFile;
    if (bdict_file_->Initialize(bdict_file_path)) {
      TimeTicks start_time = TimeTicks::Now();

      hunspell_ =
          new Hunspell(bdict_file_->data(), bdict_file_->length());

      // Add custom words to Hunspell.
      std::string contents;
      file_util::ReadFileToString(custom_dictionary_file_name_, &contents);
      std::vector<std::string> list_of_words;
      SplitString(contents, '\n', &list_of_words);
      for (std::vector<std::string>::iterator it = list_of_words.begin();
           it != list_of_words.end(); ++it) {
        hunspell_->add(it->c_str());
      }

      DHISTOGRAM_TIMES("Spellcheck.InitTime",
                       TimeTicks::Now() - start_time);
    } else {
      delete bdict_file_;
      bdict_file_ = NULL;
    }

    Finish(true);
  }

 private:
  void Finish(bool file_existed) {
    ChromeThread::PostTask(
        ChromeThread::IO, FROM_HERE,
        NewRunnableMethod(
            spellchecker_.get(), &SpellChecker::HunspellInited, hunspell_,
            bdict_file_, file_existed));
  }

  // The SpellChecker we are working for.
  scoped_refptr<SpellChecker> spellchecker_;
  Hunspell* hunspell_;
  file_util::MemoryMappedFile* bdict_file_;

  FilePath custom_dictionary_file_name_;
  FilePath dict_file_name_app_;
  FilePath dict_file_name_usr_;

  DISALLOW_COPY_AND_ASSIGN(ReadDictionaryTask);
};

void SpellChecker::SpellCheckLanguages(std::vector<std::string>* languages) {
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
       ++i) {
    languages->push_back(g_supported_spellchecker_languages[i].language);
  }
}

// This function returns the language-region version of language name.
// e.g. returns hi-IN for hi.
std::string SpellChecker::GetSpellCheckLanguageRegion(
    std::string input_language) {
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
       ++i) {
    std::string language(
        g_supported_spellchecker_languages[i].language);
    if (language ==  input_language)
      return std::string(
          g_supported_spellchecker_languages[i].language_region);
  }

  return input_language;
}


std::string SpellChecker::GetLanguageFromLanguageRegion(
    std::string input_language) {
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
       ++i) {
    std::string language(
        g_supported_spellchecker_languages[i].language_region);
    if (language ==  input_language)
      return std::string(g_supported_spellchecker_languages[i].language);
  }

  return input_language;
}

std::string SpellChecker::GetCorrespondingSpellCheckLanguage(
    const std::string& language) {
  // Look for exact match in the Spell Check language list.
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
       ++i) {
    // First look for exact match in the language region of the list.
    std::string spellcheck_language(
        g_supported_spellchecker_languages[i].language);
    if (spellcheck_language == language)
      return language;

    // Next, look for exact match in the language_region part of the list.
    std::string spellcheck_language_region(
        g_supported_spellchecker_languages[i].language_region);
    if (spellcheck_language_region == language)
      return g_supported_spellchecker_languages[i].language;
  }

  // Look for a match by comparing only language parts. All the 'en-RR'
  // except for 'en-GB' exactly matched in the above loop, will match
  // 'en-US'. This is not ideal because 'en-ZA', 'en-NZ' had
  // better be matched with 'en-GB'. This does not handle cases like
  // 'az-Latn-AZ' vs 'az-Arab-AZ', either, but we don't use 3-part
  // locale ids with a script code in the middle, yet.
  // TODO(jungshik): Add a better fallback.
  std::string language_part(language, 0, language.find('-'));
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(g_supported_spellchecker_languages);
       ++i) {
    std::string spellcheck_language(
        g_supported_spellchecker_languages[i].language_region);
    if (spellcheck_language.substr(0, spellcheck_language.find('-')) ==
        language_part)
      return spellcheck_language;
  }

  // No match found - return blank.
  return std::string();
}

int SpellChecker::GetSpellCheckLanguages(
    Profile* profile,
    std::vector<std::string>* languages) {
  StringPrefMember accept_languages_pref;
  StringPrefMember dictionary_language_pref;
  accept_languages_pref.Init(prefs::kAcceptLanguages, profile->GetPrefs(),
                             NULL);
  dictionary_language_pref.Init(prefs::kSpellCheckDictionary,
                                profile->GetPrefs(), NULL);
  std::string dictionary_language =
      WideToASCII(dictionary_language_pref.GetValue());

  // The current dictionary language should be there.
  languages->push_back(dictionary_language);

  // Now scan through the list of accept languages, and find possible mappings
  // from this list to the existing list of spell check languages.
  std::vector<std::string> accept_languages;

  if (SpellCheckerPlatform::SpellCheckerAvailable()) {
    SpellCheckerPlatform::GetAvailableLanguages(&accept_languages);
  } else {
    SplitString(WideToASCII(accept_languages_pref.GetValue()), ',',
                &accept_languages);
  }
  for (std::vector<std::string>::const_iterator i = accept_languages.begin();
       i != accept_languages.end(); ++i) {
    std::string language = GetCorrespondingSpellCheckLanguage(*i);
    if (!language.empty() &&
        std::find(languages->begin(), languages->end(), language) ==
        languages->end())
      languages->push_back(language);
  }

  for (size_t i = 0; i < languages->size(); ++i) {
    if ((*languages)[i] == dictionary_language)
      return i;
  }
  return -1;
}

FilePath SpellChecker::GetVersionedFileName(const std::string& input_language,
                                            const FilePath& dict_dir) {
  // The default dictionary version is 1-2. These versions have been augmented
  // with additional words found by the translation team.
  static const char kDefaultVersionString[] = "-1-2";

  // The following dictionaries have either not been augmented with additional
  // words (version 1-1) or have new words, as well as an upgraded dictionary
  // as of Feb 2009 (version 1-3).
  static const struct {
    // The language input.
    const char* language;

    // The corresponding version.
    const char* version;
  } special_version_string[] = {
    {"en-AU", "-1-1"},
    {"en-GB", "-1-1"},
    {"es-ES", "-1-1"},
    {"nl-NL", "-1-1"},
    {"ru-RU", "-1-1"},
    {"sv-SE", "-1-1"},
    {"he-IL", "-1-1"},
    {"el-GR", "-1-1"},
    {"hi-IN", "-1-1"},
    {"tr-TR", "-1-1"},
    {"et-EE", "-1-1"},
    {"fr-FR", "-1-4"}, // to fix crash, fr dictionary was updated to 1.4
    {"lt-LT", "-1-3"},
    {"pl-PL", "-1-3"}
  };

  // Generate the bdict file name using default version string or special
  // version string, depending on the language.
  std::string language = GetSpellCheckLanguageRegion(input_language);
  std::string versioned_bdict_file_name(language + kDefaultVersionString +
                                        ".bdic");
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(special_version_string); ++i) {
    if (language == special_version_string[i].language) {
      versioned_bdict_file_name =
          language + special_version_string[i].version + ".bdic";
      break;
    }
  }

  return dict_dir.AppendASCII(versioned_bdict_file_name);
}

SpellChecker::SpellChecker(const FilePath& dict_dir,
                           const std::string& language,
                           URLRequestContextGetter* request_context_getter,
                           const FilePath& custom_dictionary_file_name)
    : given_dictionary_directory_(dict_dir),
      custom_dictionary_file_name_(custom_dictionary_file_name),
      tried_to_init_(false),
      language_(language),
      tried_to_download_dictionary_file_(false),
      request_context_getter_(request_context_getter),
      obtaining_dictionary_(false),
      auto_spell_correct_turned_on_(false),
      is_using_platform_spelling_engine_(false),
      fetcher_(NULL),
      ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) {
  if (SpellCheckerPlatform::SpellCheckerAvailable()) {
    SpellCheckerPlatform::Init();
    if (SpellCheckerPlatform::PlatformSupportsLanguage(language)) {
      // If we have reached here, then we know that the current platform
      // supports the given language and we will use it instead of hunspell.
      SpellCheckerPlatform::SetLanguage(language);
      is_using_platform_spelling_engine_ = true;
    }
  }

  // Get the corresponding BDIC file name.
  bdic_file_name_ = GetVersionedFileName(language, dict_dir).BaseName();

  // Get the path to the custom dictionary file.
  if (custom_dictionary_file_name_.empty()) {
    FilePath personal_file_directory;
    PathService::Get(chrome::DIR_USER_DATA, &personal_file_directory);
    custom_dictionary_file_name_ =
        personal_file_directory.Append(chrome::kCustomDictionaryFileName);
  }

  // Use this dictionary language as the default one of the
  // SpellcheckCharAttribute object.
  character_attributes_.SetDefaultLanguage(language);
}

SpellChecker::~SpellChecker() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));
}

void SpellChecker::StartDictionaryDownload(const FilePath& file_name) {
  // Determine URL of file to download.
  static const char kDownloadServerUrl[] =
      "http://cache.pack.google.com/edgedl/chrome/dict/";
  GURL url = GURL(std::string(kDownloadServerUrl) + WideToUTF8(
      l10n_util::ToLower(bdic_file_name_.ToWStringHack())));
  fetcher_.reset(new URLFetcher(url, URLFetcher::GET, this));
  fetcher_->set_request_context(request_context_getter_);
  obtaining_dictionary_ = true;
  fetcher_->Start();
}

void SpellChecker::OnURLFetchComplete(const URLFetcher* source,
                                      const GURL& url,
                                      const URLRequestStatus& status,
                                      int response_code,
                                      const ResponseCookies& cookies,
                                      const std::string& data) {
  DCHECK(source);
  if ((response_code / 100) != 2) {
    obtaining_dictionary_ = false;
    return;
  }

  // Basic sanity check on the dictionary.
  // There's the small chance that we might see a 200 status code for a body
  // that represents some form of failure.
  if (data.size() < 4 || data[0] != 'B' || data[1] != 'D' || data[2] != 'i' ||
      data[3] != 'c') {
    obtaining_dictionary_ = false;
    return;
  }

  // Save the file in the file thread, and not here, the IO thread.
  FilePath first_attempt_file_name = given_dictionary_directory_.Append(
      bdic_file_name_);
  FilePath user_data_dir = GetFallbackDictionaryDownloadDirectory();
  FilePath fallback_file_name = user_data_dir.Append(bdic_file_name_);
  Task* dic_task = method_factory_.
      NewRunnableMethod(&SpellChecker::OnDictionarySaveComplete);
  ChromeThread::PostTask(
      ChromeThread::FILE, FROM_HERE,
      new SaveDictionaryTask(
          dic_task, first_attempt_file_name, fallback_file_name, data));
}

void SpellChecker::OnDictionarySaveComplete() {
  obtaining_dictionary_ = false;
  // Now that the dictionary is downloaded, continue trying to download.
  Initialize();
}

// Initialize SpellChecker. In this method, if the dictionary is not present
// in the local disk, it is fetched asynchronously.
bool SpellChecker::Initialize() {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));

  // Return false if the dictionary files are downloading.
  if (obtaining_dictionary_)
    return false;

  // Return false if tried to init and failed - don't try multiple times in
  // this session.
  if (tried_to_init_)
    return hunspell_.get() != NULL;

  StatsScope<StatsCounterTimer> timer(chrome::Counters::spellcheck_init());

  // The default place whether the spellcheck dictionary can reside is
  // chrome::DIR_APP_DICTIONARIES. However, for systemwide installations,
  // this directory may not have permissions for download. In that case, the
  // alternate directory for download is chrome::DIR_USER_DATA. We have to check
  // for the spellcheck dictionaries in both the directories. If not found in
  // either one, it has to be downloaded in either of the two.
  // TODO(sidchat): Some sort of UI to warn users that spellchecker is not
  // working at all (due to failed dictionary download)?

  // File name for downloading in DIR_APP_DICTIONARIES.
  FilePath dictionary_file_name_app = GetVersionedFileName(language_,
      given_dictionary_directory_);

  // Filename for downloading in the fallback dictionary download directory,
  // DIR_USER_DATA.
  FilePath dict_dir_userdata = GetFallbackDictionaryDownloadDirectory();
  FilePath dictionary_file_name_usr = GetVersionedFileName(language_,
      dict_dir_userdata);

  ChromeThread::PostTask(
      ChromeThread::FILE, FROM_HERE,
      new ReadDictionaryTask(
          this, dictionary_file_name_app, dictionary_file_name_usr));

  return hunspell_.get() != NULL;
}

void SpellChecker::HunspellInited(Hunspell* hunspell,
                                  file_util::MemoryMappedFile* bdict_file,
                                  bool file_existed) {
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));

  if (file_existed)
    tried_to_init_ = true;

  if (!hunspell) {
    if (!file_existed) {
      // File didn't exist. We need to download a dictionary.
      DoDictionaryDownload();
    }
    return;
  }


  bdict_file_.reset(bdict_file);
  hunspell_.reset(hunspell);
  // Add all the custom words we've gotten while Hunspell was loading.
  while (!custom_words_.empty()) {
    hunspell_->add(custom_words_.front().c_str());
    custom_words_.pop();
  }
}

void SpellChecker::DoDictionaryDownload() {
  // Download the dictionary file.
  if (request_context_getter_) {
    if (!tried_to_download_dictionary_file_) {
      FilePath dictionary_file_name_app = GetVersionedFileName(language_,
          given_dictionary_directory_);
      StartDictionaryDownload(dictionary_file_name_app);
      tried_to_download_dictionary_file_ = true;
    } else {
      // Don't try to download a dictionary more than once.
      tried_to_init_ = true;
    }
  } else {
    NOTREACHED();
  }
}

string16 SpellChecker::GetAutoCorrectionWord(const string16& word, int tag) {
  string16 autocorrect_word;
  if (!auto_spell_correct_turned_on_)
    return autocorrect_word;  // Return the empty string.

  int word_length = static_cast<int>(word.size());
  if (word_length < 2 || word_length > kMaxAutoCorrectWordSize)
    return autocorrect_word;

  char16 misspelled_word[kMaxAutoCorrectWordSize + 1];
  const char16* word_char = word.c_str();
  for (int i = 0; i <= kMaxAutoCorrectWordSize; i++) {
    if (i >= word_length)
      misspelled_word[i] = NULL;
    else
      misspelled_word[i] = word_char[i];
  }

  // Swap adjacent characters and spellcheck.
  int misspelling_start, misspelling_len;
  for (int i = 0; i < word_length - 1; i++) {
    // Swap.
    std::swap(misspelled_word[i], misspelled_word[i + 1]);

    // Check spelling.
    misspelling_start = misspelling_len = 0;
    SpellCheckWord(misspelled_word, word_length, tag, &misspelling_start,
        &misspelling_len, NULL);

    // Make decision: if only one swap produced a valid word, then we want to
    // return it. If we found two or more, we don't do autocorrection.
    if (misspelling_len == 0) {
      if (autocorrect_word.empty()) {
        autocorrect_word.assign(misspelled_word);
      } else {
        autocorrect_word.clear();
        break;
      }
    }

    // Restore the swapped characters.
    std::swap(misspelled_word[i], misspelled_word[i + 1]);
  }
  return autocorrect_word;
}

void SpellChecker::EnableAutoSpellCorrect(bool turn_on) {
  auto_spell_correct_turned_on_ = turn_on;
}

// Returns whether or not the given string is a valid contraction.
// This function is a fall-back when the SpellcheckWordIterator class
// returns a concatenated word which is not in the selected dictionary
// (e.g. "in'n'out") but each word is valid.
bool SpellChecker::IsValidContraction(const string16& contraction, int tag) {
  SpellcheckWordIterator word_iterator;
  word_iterator.Initialize(&character_attributes_, contraction.c_str(),
                           contraction.length(), false);

  string16 word;
  int word_start;
  int word_length;
  while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
    if (!CheckSpelling(word, tag))
      return false;
  }
  return true;
}

bool SpellChecker::SpellCheckWord(
    const char16* in_word,
    int in_word_len,
    int tag,
    int* misspelling_start,
    int* misspelling_len,
    std::vector<string16>* optional_suggestions) {
  DCHECK(in_word_len >= 0);
  DCHECK(misspelling_start && misspelling_len) << "Out vars must be given.";

  // This must always be called on the same thread (normally the I/O thread).
  DCHECK(ChromeThread::CurrentlyOn(ChromeThread::IO));

  // Check if the platform spellchecker is being used.
  if (!is_using_platform_spelling_engine_) {
    // If it isn't, try and init hunspell.
    Initialize();

    // Check to see if hunspell was successfuly initialized.
    if (!hunspell_.get())
      return true;  // Unable to spellcheck, return word is OK.
  }

  StatsScope<StatsRate> timer(chrome::Counters::spellcheck_lookup());

  *misspelling_start = 0;
  *misspelling_len = 0;
  if (in_word_len == 0)
    return true;  // No input means always spelled correctly.

  SpellcheckWordIterator word_iterator;
  string16 word;
  int word_start;
  int word_length;
  word_iterator.Initialize(&character_attributes_, in_word, in_word_len, true);
  while (word_iterator.GetNextWord(&word, &word_start, &word_length)) {
    // Found a word (or a contraction) that the spellchecker can check the
    // spelling of.
    bool word_ok = CheckSpelling(word, tag);
    if (word_ok)
      continue;

    // If the given word is a concatenated word of two or more valid words
    // (e.g. "hello:hello"), we should treat it as a valid word.
    if (IsValidContraction(word, tag))
      continue;

    *misspelling_start = word_start;
    *misspelling_len = word_length;

    // Get the list of suggested words.
    if (optional_suggestions)
      FillSuggestionList(word, optional_suggestions);
    return false;
  }

  return true;
}

// This task is called in the file loop to write the new word to the custom
// dictionary in disc.
class AddWordToCustomDictionaryTask : public Task {
 public:
  AddWordToCustomDictionaryTask(const FilePath& file_name,
                                const string16& word)
      : file_name_(file_name),
        word_(UTF16ToUTF8(word)) {
  }

 private:
  void Run();

  FilePath file_name_;
  std::string word_;
};

void AddWordToCustomDictionaryTask::Run() {
  // Add the word with a new line. Note that, although this would mean an
  // extra line after the list of words, this is potentially harmless and
  // faster, compared to verifying everytime whether to append a new line
  // or not.
  word_ += "\n";
  FILE* f = file_util::OpenFile(file_name_, "a+");
  if (f != NULL)
    fputs(word_.c_str(), f);
  file_util::CloseFile(f);
}

void SpellChecker::AddWord(const string16& word) {
  if (is_using_platform_spelling_engine_) {
    SpellCheckerPlatform::AddWord(word);
    return;
  }

  // Check if the |hunspell_| has been initialized at all.
  Initialize();

  // Add the word to hunspell.
  std::string word_to_add = UTF16ToUTF8(word);
  // Don't attempt to add an empty word, or one larger than Hunspell can handle
  if (!word_to_add.empty() && word_to_add.length() < MAXWORDLEN) {
    // Either add the word to |hunspell_|, or, if |hunspell_| is still loading,
    // defer it till after the load completes.
    if (hunspell_.get())
      hunspell_->add(word_to_add.c_str());
    else
      custom_words_.push(word_to_add);
  }

  // Now add the word to the custom dictionary file.
  ChromeThread::PostTask(
      ChromeThread::FILE, FROM_HERE,
      new AddWordToCustomDictionaryTask(custom_dictionary_file_name_, word));
}

bool SpellChecker::CheckSpelling(const string16& word_to_check, int tag) {
  bool word_correct = false;

  TimeTicks begin_time = TimeTicks::Now();
  if (is_using_platform_spelling_engine_) {
    word_correct = SpellCheckerPlatform::CheckSpelling(word_to_check, tag);
  } else {
    std::string word_to_check_utf8(UTF16ToUTF8(word_to_check));
    // Hunspell shouldn't let us exceed its max, but check just in case
    if (word_to_check_utf8.length() < MAXWORDLEN) {
      // |hunspell_->spell| returns 0 if the word is spelled correctly and
      // non-zero otherwsie.
      word_correct = (hunspell_->spell(word_to_check_utf8.c_str()) != 0);
    }
  }
  DHISTOGRAM_TIMES("Spellcheck.CheckTime", TimeTicks::Now() - begin_time);

  return word_correct;
}

void SpellChecker::FillSuggestionList(
    const string16& wrong_word,
    std::vector<string16>* optional_suggestions) {
  if (is_using_platform_spelling_engine_) {
    SpellCheckerPlatform::FillSuggestionList(wrong_word, optional_suggestions);
    return;
  }
  char** suggestions;
  TimeTicks begin_time = TimeTicks::Now();
  int number_of_suggestions = hunspell_->suggest(&suggestions,
      UTF16ToUTF8(wrong_word).c_str());
  DHISTOGRAM_TIMES("Spellcheck.SuggestTime",
                   TimeTicks::Now() - begin_time);

  // Populate the vector of WideStrings.
  for (int i = 0; i < number_of_suggestions; i++) {
    if (i < kMaxSuggestions)
      optional_suggestions->push_back(UTF8ToUTF16(suggestions[i]));
    free(suggestions[i]);
  }
  if (suggestions != NULL)
    free(suggestions);
}