text: // Copyright (c) 2012 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/spellcheck_message_filter_mac.h"
  
  #include <algorithm>
  #include <functional>
  
  #include "base/barrier_closure.h"
  #include "base/bind.h"
  #include "chrome/browser/spellchecker/spellcheck_factory.h"
  #include "chrome/browser/spellchecker/spellcheck_platform_mac.h"
  #include "chrome/browser/spellchecker/spellcheck_service.h"
  #include "chrome/browser/spellchecker/spelling_service_client.h"
  #include "chrome/common/spellcheck_messages.h"
  #include "chrome/common/spellcheck_result.h"
  #include "content/public/browser/browser_context.h"
  #include "content/public/browser/render_process_host.h"
  
  using content::BrowserThread;
  using content::BrowserContext;
  
  namespace {
  
  bool CompareLocation(const SpellCheckResult& r1,
                       const SpellCheckResult& r2) {
    return r1.location < r2.location;
  }
  
  }  // namespace
  
  class SpellingRequest {
   public:
    SpellingRequest(SpellingServiceClient* client,
                    content::BrowserMessageFilter* destination,
                    int render_process_id);
  
    void RequestCheck(const base::string16& text,
                      int route_id,
                      int identifier,
                      int document_tag,
                      const std::vector<SpellCheckMarker>& markers);
   private:
    // Request server-side checking.
    void RequestRemoteCheck(const base::string16& text);
  
    // Request a check from local spell checker.
    void RequestLocalCheck(const base::string16& text, int document_tag);
  
    // Check if all pending requests are done, send reply to render process if so.
    void OnCheckCompleted();
  
    // Called when server-side checking is complete.
    void OnRemoteCheckCompleted(bool success,
                                const base::string16& text,
                                const std::vector<SpellCheckResult>& results);
  
    // Called when local checking is complete.
    void OnLocalCheckCompleted(const std::vector<SpellCheckResult>& results);
  
    std::vector<SpellCheckResult> local_results_;
    std::vector<SpellCheckResult> remote_results_;
  
    // Barrier closure for completion of both remote and local check.
    base::Closure completion_barrier_;
    bool remote_success_;
  
    SpellingServiceClient* client_;  // Owned by |destination|.
    content::BrowserMessageFilter* destination_;  // ref-counted.
    int render_process_id_;
  
    int route_id_;
    int identifier_;
    int document_tag_;
    std::vector<SpellCheckMarker> markers_;
  };
  
  SpellingRequest::SpellingRequest(SpellingServiceClient* client,
                                   content::BrowserMessageFilter* destination,
                                   int render_process_id)
      : remote_success_(false),
        client_(client),
        destination_(destination),
        render_process_id_(render_process_id),
        route_id_(-1),
        identifier_(-1),
        document_tag_(-1) {
    destination_->AddRef();
  }
  
  void SpellingRequest::RequestCheck(
      const base::string16& text,
      int route_id,
      int identifier,
      int document_tag,
      const std::vector<SpellCheckMarker>& markers) {
    DCHECK(!text.empty());
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  
    route_id_ = route_id;
    identifier_ = identifier;
    document_tag_ = document_tag;
    markers_ = markers;
  
    // Send the remote query out.
    completion_barrier_ =
        BarrierClosure(2,
                       base::Bind(&SpellingRequest::OnCheckCompleted,
                       base::Owned(this)));
    RequestRemoteCheck(text);
    RequestLocalCheck(text, document_tag_);
  }
  
  void SpellingRequest::RequestRemoteCheck(const base::string16& text) {
    BrowserContext* context = NULL;
    content::RenderProcessHost* host =
        content::RenderProcessHost::FromID(render_process_id_);
    if (host)
      context = host->GetBrowserContext();
  
    client_->RequestTextCheck(
      context,
      SpellingServiceClient::SPELLCHECK,
      text,
      base::Bind(&SpellingRequest::OnRemoteCheckCompleted,
                 base::Unretained(this)));
  }
  
  void SpellingRequest::RequestLocalCheck(const base::string16& text,
                                          int document_tag) {
    spellcheck_mac::RequestTextCheck(
        document_tag,
        text,
        base::Bind(&SpellingRequest::OnLocalCheckCompleted,
                   base::Unretained(this)));
  }
  
  void SpellingRequest::OnCheckCompleted() {
    // Final completion can happen on any thread - don't DCHECK thread.
    const std::vector<SpellCheckResult>* check_results = &local_results_;
    if (remote_success_) {
      std::sort(remote_results_.begin(), remote_results_.end(), CompareLocation);
      std::sort(local_results_.begin(), local_results_.end(), CompareLocation);
      SpellCheckMessageFilterMac::CombineResults(&remote_results_,
                                                 local_results_);
      check_results = &remote_results_;
    }
  
    destination_->Send(
        new SpellCheckMsg_RespondTextCheck(
            route_id_,
            identifier_,
            *check_results));
    destination_->Release();
  
    // Object is self-managed - at this point, its life span is over.
  }
  
  void SpellingRequest::OnRemoteCheckCompleted(
      bool success,
      const base::string16& text,
      const std::vector<SpellCheckResult>& results) {
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    remote_success_ = success;
    remote_results_ = results;
  
    SpellcheckService* spellcheck_service =
        SpellcheckServiceFactory::GetForRenderProcessId(render_process_id_);
    if (spellcheck_service) {
      spellcheck_service->GetFeedbackSender()->OnSpellcheckResults(
          render_process_id_,
          text,
          markers_,
          &remote_results_);
    }
  
    completion_barrier_.Run();
  }
  
  void SpellingRequest::OnLocalCheckCompleted(
      const std::vector<SpellCheckResult>& results) {
    // Local checking can happen on any thread - don't DCHECK thread.
    local_results_ = results;
    completion_barrier_.Run();
  }
  
  
  SpellCheckMessageFilterMac::SpellCheckMessageFilterMac(int render_process_id)
      : render_process_id_(render_process_id),
        client_(new SpellingServiceClient) {
  }
  
  void SpellCheckMessageFilterMac::OverrideThreadForMessage(
      const IPC::Message& message, BrowserThread::ID* thread) {
    if (message.type() == SpellCheckHostMsg_RequestTextCheck::ID)
      *thread = BrowserThread::UI;
  }
  
  bool SpellCheckMessageFilterMac::OnMessageReceived(const IPC::Message& message,
                                                     bool* message_was_ok) {
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP_EX(SpellCheckMessageFilterMac, message, *message_was_ok)
      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_CheckSpelling,
                          OnCheckSpelling)
      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_FillSuggestionList,
                          OnFillSuggestionList)
      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_ShowSpellingPanel,
                          OnShowSpellingPanel)
      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_UpdateSpellingPanelWithMisspelledWord,
                          OnUpdateSpellingPanelWithMisspelledWord)
      IPC_MESSAGE_HANDLER(SpellCheckHostMsg_RequestTextCheck,
                          OnRequestTextCheck)
      IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
  }
  
  // static
  void SpellCheckMessageFilterMac::CombineResults(
      std::vector<SpellCheckResult>* remote_results,
      const std::vector<SpellCheckResult>& local_results) {
    std::vector<SpellCheckResult>::const_iterator local_iter(
        local_results.begin());
    std::vector<SpellCheckResult>::iterator remote_iter;
  
    for (remote_iter = remote_results->begin();
         remote_iter != remote_results->end();
         ++remote_iter) {
      // Discard all local results occurring before remote result.
      while (local_iter != local_results.end() &&
             local_iter->location < remote_iter->location) {
        local_iter++;
      }
  
      // Unless local and remote result coincide, result is GRAMMAR.
      remote_iter->decoration = SpellCheckResult::GRAMMAR;
      if (local_iter != local_results.end() &&
          local_iter->location == remote_iter->location &&
          local_iter->length == remote_iter->length) {
        remote_iter->decoration = SpellCheckResult::SPELLING;
      }
    }
  }
  
  SpellCheckMessageFilterMac::~SpellCheckMessageFilterMac() {}
  
  void SpellCheckMessageFilterMac::OnCheckSpelling(const base::string16& word,
                                                   int route_id,
                                                   bool* correct) {
    *correct = spellcheck_mac::CheckSpelling(word, ToDocumentTag(route_id));
  }
  
  void SpellCheckMessageFilterMac::OnFillSuggestionList(
      const base::string16& word,
      std::vector<base::string16>* suggestions) {
    spellcheck_mac::FillSuggestionList(word, suggestions);
  }
  
  void SpellCheckMessageFilterMac::OnShowSpellingPanel(bool show) {
    spellcheck_mac::ShowSpellingPanel(show);
  }
  
  void SpellCheckMessageFilterMac::OnUpdateSpellingPanelWithMisspelledWord(
      const base::string16& word) {
    spellcheck_mac::UpdateSpellingPanelWithMisspelledWord(word);
  }
  
  void SpellCheckMessageFilterMac::OnRequestTextCheck(
      int route_id,
      int identifier,
      const base::string16& text,
      std::vector<SpellCheckMarker> markers) {
    DCHECK(!text.empty());
    DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    // Erase invalid markers (with offsets out of boundaries of text length).
    markers.erase(
        std::remove_if(
            markers.begin(),
            markers.end(),
            std::not1(SpellCheckMarker::IsValidPredicate(text.length()))),
        markers.end());
    // SpellingRequest self-destructs.
    SpellingRequest* request =
      new SpellingRequest(client_.get(), this, render_process_id_);
    request->RequestCheck(
        text, route_id, identifier, ToDocumentTag(route_id), markers);
  }
  
  int SpellCheckMessageFilterMac::ToDocumentTag(int route_id) {
    if (!tag_map_.count(route_id))
      tag_map_[route_id] = spellcheck_mac::GetDocumentTag();
    return tag_map_[route_id];
  }
  
  // TODO(groby): We are currently not notified of retired tags. We need
  // to track destruction of RenderViewHosts on the browser process side
  // to update our mappings when a document goes away.
  void SpellCheckMessageFilterMac::RetireDocumentTag(int route_id) {
    spellcheck_mac::CloseDocumentWithTag(ToDocumentTag(route_id));
    tag_map_.erase(route_id);
  }
  
  