// Copyright 2015 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 "ios/chrome/browser/ui/webui/sync_internals/sync_internals_message_handler.h"

#include <vector>

#include "base/logging.h"
#include "components/browser_sync/browser/profile_sync_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/sync_driver/about_sync_util.h"
#include "components/sync_driver/sync_service.h"
#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
#include "ios/chrome/browser/signin/signin_manager_factory.h"
#include "ios/chrome/browser/sync/ios_chrome_profile_sync_service_factory.h"
#include "ios/chrome/common/channel_info.h"
#include "ios/public/provider/web/web_ui_ios.h"
#include "ios/web/public/web_thread.h"
#include "sync/internal_api/public/events/protocol_event.h"
#include "sync/internal_api/public/sessions/commit_counters.h"
#include "sync/internal_api/public/sessions/status_counters.h"
#include "sync/internal_api/public/sessions/update_counters.h"
#include "sync/internal_api/public/util/weak_handle.h"
#include "sync/js/js_event_details.h"

using syncer::JsEventDetails;
using syncer::ModelTypeSet;
using syncer::WeakHandle;

SyncInternalsMessageHandler::SyncInternalsMessageHandler()
    : is_registered_(false),
      is_registered_for_counters_(false),
      weak_ptr_factory_(this) {}

SyncInternalsMessageHandler::~SyncInternalsMessageHandler() {
  if (js_controller_)
    js_controller_->RemoveJsEventHandler(this);

  sync_driver::SyncService* service = GetSyncService();
  if (service && service->HasObserver(this)) {
    service->RemoveObserver(this);
    service->RemoveProtocolEventObserver(this);
  }

  if (service && is_registered_for_counters_) {
    service->RemoveTypeDebugInfoObserver(this);
  }
}

void SyncInternalsMessageHandler::RegisterMessages() {
  DCHECK_CURRENTLY_ON(web::WebThread::UI);

  web_ui()->RegisterMessageCallback(
      sync_driver::sync_ui_util::kRegisterForEvents,
      base::Bind(&SyncInternalsMessageHandler::HandleRegisterForEvents,
                 base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      sync_driver::sync_ui_util::kRegisterForPerTypeCounters,
      base::Bind(&SyncInternalsMessageHandler::HandleRegisterForPerTypeCounters,
                 base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      sync_driver::sync_ui_util::kRequestUpdatedAboutInfo,
      base::Bind(&SyncInternalsMessageHandler::HandleRequestUpdatedAboutInfo,
                 base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      sync_driver::sync_ui_util::kRequestListOfTypes,
      base::Bind(&SyncInternalsMessageHandler::HandleRequestListOfTypes,
                 base::Unretained(this)));

  web_ui()->RegisterMessageCallback(
      sync_driver::sync_ui_util::kGetAllNodes,
      base::Bind(&SyncInternalsMessageHandler::HandleGetAllNodes,
                 base::Unretained(this)));
}

void SyncInternalsMessageHandler::HandleRegisterForEvents(
    const base::ListValue* args) {
  DCHECK(args->empty());

  // is_registered_ flag protects us from double-registering.  This could
  // happen on a page refresh, where the JavaScript gets re-run but the
  // message handler remains unchanged.
  sync_driver::SyncService* service = GetSyncService();
  if (service && !is_registered_) {
    service->AddObserver(this);
    service->AddProtocolEventObserver(this);
    js_controller_ = service->GetJsController();
    js_controller_->AddJsEventHandler(this);
    is_registered_ = true;
  }
}

void SyncInternalsMessageHandler::HandleRegisterForPerTypeCounters(
    const base::ListValue* args) {
  DCHECK(args->empty());

  if (sync_driver::SyncService* service = GetSyncService()) {
    if (!is_registered_for_counters_) {
      service->AddTypeDebugInfoObserver(this);
      is_registered_for_counters_ = true;
    } else {
      // Re-register to ensure counters get re-emitted.
      service->RemoveTypeDebugInfoObserver(this);
      service->AddTypeDebugInfoObserver(this);
    }
  }
}

void SyncInternalsMessageHandler::HandleRequestUpdatedAboutInfo(
    const base::ListValue* args) {
  DCHECK(args->empty());
  SendAboutInfo();
}

void SyncInternalsMessageHandler::HandleRequestListOfTypes(
    const base::ListValue* args) {
  DCHECK(args->empty());
  base::DictionaryValue event_details;
  scoped_ptr<base::ListValue> type_list(new base::ListValue());
  ModelTypeSet protocol_types = syncer::ProtocolTypes();
  for (ModelTypeSet::Iterator it = protocol_types.First(); it.Good();
       it.Inc()) {
    type_list->Append(new base::StringValue(ModelTypeToString(it.Get())));
  }
  event_details.Set(sync_driver::sync_ui_util::kTypes, type_list.release());
  web_ui()->CallJavascriptFunction(
      sync_driver::sync_ui_util::kDispatchEvent,
      base::StringValue(sync_driver::sync_ui_util::kOnReceivedListOfTypes),
      event_details);
}

void SyncInternalsMessageHandler::HandleGetAllNodes(
    const base::ListValue* args) {
  DCHECK_EQ(1U, args->GetSize());
  int request_id = 0;
  bool success = args->GetInteger(0, &request_id);
  DCHECK(success);

  sync_driver::SyncService* service = GetSyncService();
  if (service) {
    service->GetAllNodes(
        base::Bind(&SyncInternalsMessageHandler::OnReceivedAllNodes,
                   weak_ptr_factory_.GetWeakPtr(), request_id));
  }
}

void SyncInternalsMessageHandler::OnReceivedAllNodes(
    int request_id,
    scoped_ptr<base::ListValue> nodes) {
  base::FundamentalValue id(request_id);
  web_ui()->CallJavascriptFunction(
      sync_driver::sync_ui_util::kGetAllNodesCallback, id, *nodes);
}

void SyncInternalsMessageHandler::OnStateChanged() {
  SendAboutInfo();
}

void SyncInternalsMessageHandler::OnProtocolEvent(
    const syncer::ProtocolEvent& event) {
  scoped_ptr<base::DictionaryValue> value(
      syncer::ProtocolEvent::ToValue(event));
  web_ui()->CallJavascriptFunction(
      sync_driver::sync_ui_util::kDispatchEvent,
      base::StringValue(sync_driver::sync_ui_util::kOnProtocolEvent), *value);
}

void SyncInternalsMessageHandler::OnCommitCountersUpdated(
    syncer::ModelType type,
    const syncer::CommitCounters& counters) {
  EmitCounterUpdate(type, sync_driver::sync_ui_util::kCommit,
                    counters.ToValue());
}

void SyncInternalsMessageHandler::OnUpdateCountersUpdated(
    syncer::ModelType type,
    const syncer::UpdateCounters& counters) {
  EmitCounterUpdate(type, sync_driver::sync_ui_util::kUpdate,
                    counters.ToValue());
}

void SyncInternalsMessageHandler::OnStatusCountersUpdated(
    syncer::ModelType type,
    const syncer::StatusCounters& counters) {
  EmitCounterUpdate(type, sync_driver::sync_ui_util::kStatus,
                    counters.ToValue());
}

void SyncInternalsMessageHandler::EmitCounterUpdate(
    syncer::ModelType type,
    const std::string& counter_type,
    scoped_ptr<base::DictionaryValue> value) {
  scoped_ptr<base::DictionaryValue> details(new base::DictionaryValue());
  details->SetString(sync_driver::sync_ui_util::kModelType,
                     ModelTypeToString(type));
  details->SetString(sync_driver::sync_ui_util::kCounterType, counter_type);
  details->Set(sync_driver::sync_ui_util::kCounters, value.release());
  web_ui()->CallJavascriptFunction(
      sync_driver::sync_ui_util::kDispatchEvent,
      base::StringValue(sync_driver::sync_ui_util::kOnCountersUpdated),
      *details);
}

void SyncInternalsMessageHandler::HandleJsEvent(const std::string& name,
                                                const JsEventDetails& details) {
  DVLOG(1) << "Handling event: " << name << " with details "
           << details.ToString();
  web_ui()->CallJavascriptFunction(sync_driver::sync_ui_util::kDispatchEvent,
                                   base::StringValue(name), details.Get());
}

void SyncInternalsMessageHandler::SendAboutInfo() {
  ios::ChromeBrowserState* browser_state =
      ios::ChromeBrowserState::FromWebUIIOS(web_ui());
  SigninManager* signin_manager =
      ios::SigninManagerFactory::GetForBrowserState(browser_state);
  sync_driver::SyncService* sync_service = GetSyncService();
  scoped_ptr<base::DictionaryValue> value =
      sync_driver::sync_ui_util::ConstructAboutInformation(
          sync_service, signin_manager, GetChannel());
  web_ui()->CallJavascriptFunction(
      sync_driver::sync_ui_util::kDispatchEvent,
      base::StringValue(sync_driver::sync_ui_util::kOnAboutInfoUpdated),
      *value);
}

// Gets the SyncService of the underlying original profile. May return null.
sync_driver::SyncService* SyncInternalsMessageHandler::GetSyncService() {
  ios::ChromeBrowserState* browser_state =
      ios::ChromeBrowserState::FromWebUIIOS(web_ui());
  return IOSChromeProfileSyncServiceFactory::GetForBrowserState(
      browser_state->GetOriginalChromeBrowserState());
}