// Copyright (c) 2011 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/sync/notifier/invalidation_notifier.h"

#include "base/logging.h"
#include "base/message_loop_proxy.h"
#include "chrome/browser/sync/notifier/sync_notifier_observer.h"
#include "chrome/browser/sync/protocol/service_constants.h"
#include "chrome/browser/sync/syncable/model_type_payload_map.h"
#include "jingle/notifier/base/const_communicator.h"
#include "jingle/notifier/base/notifier_options_util.h"
#include "jingle/notifier/communicator/connection_options.h"
#include "net/base/host_port_pair.h"
#include "net/url_request/url_request_context.h"
#include "talk/xmpp/jid.h"
#include "talk/xmpp/xmppclientsettings.h"

namespace sync_notifier {

InvalidationNotifier::InvalidationNotifier(
    const notifier::NotifierOptions& notifier_options,
    const std::string& client_info)
    : state_(STOPPED),
      notifier_options_(notifier_options),
      client_info_(client_info) {
  DCHECK_EQ(notifier::NOTIFICATION_SERVER,
            notifier_options.notification_method);
  DCHECK(notifier_options_.request_context_getter);
  // TODO(akalin): Replace NonThreadSafe checks with IO thread checks.
  DCHECK(notifier_options_.request_context_getter->GetIOMessageLoopProxy()->
      BelongsToCurrentThread());
}

InvalidationNotifier::~InvalidationNotifier() {
  DCHECK(non_thread_safe_.CalledOnValidThread());
}

void InvalidationNotifier::AddObserver(SyncNotifierObserver* observer) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  observers_.AddObserver(observer);
}

void InvalidationNotifier::RemoveObserver(SyncNotifierObserver* observer) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  observers_.RemoveObserver(observer);
}

void InvalidationNotifier::SetState(const std::string& state) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  invalidation_state_ = state;
}

void InvalidationNotifier::UpdateCredentials(
    const std::string& email, const std::string& token) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  VLOG(1) << "Updating credentials for " << email;
  buzz::XmppClientSettings xmpp_client_settings =
      notifier::MakeXmppClientSettings(notifier_options_,
                                       email, token, SYNC_SERVICE_NAME);
  if (state_ >= CONNECTING) {
    login_->UpdateXmppSettings(xmpp_client_settings);
  } else {
    notifier::ConnectionOptions options;
    VLOG(1) << "First time updating credentials: connecting";
    login_.reset(
        new notifier::Login(this,
                            xmpp_client_settings,
                            notifier::ConnectionOptions(),
                            notifier_options_.request_context_getter,
                            notifier::GetServerList(notifier_options_),
                            notifier_options_.try_ssltcp_first,
                            notifier_options_.auth_mechanism));
    login_->StartConnection();
    state_ = CONNECTING;
  }
}

void InvalidationNotifier::UpdateEnabledTypes(
    const syncable::ModelTypeSet& types) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  invalidation_client_.RegisterTypes(types);
}

void InvalidationNotifier::SendNotification() {
  DCHECK(non_thread_safe_.CalledOnValidThread());
}

void InvalidationNotifier::OnConnect(
    base::WeakPtr<talk_base::Task> base_task) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  VLOG(1) << "OnConnect";
  if (state_ >= STARTED) {
    invalidation_client_.ChangeBaseTask(base_task);
  } else {
    VLOG(1) << "First time connecting: starting invalidation client";
    // TODO(akalin): Make cache_guid() part of the client ID.  If we
    // do so and we somehow propagate it up to the server somehow, we
    // can make it so that we won't receive any notifications that
    // were generated from our own changes.
    const std::string kClientId = "invalidation_notifier";
    invalidation_client_.Start(
        kClientId, client_info_, invalidation_state_, this, this, base_task);
    invalidation_state_.clear();
    state_ = STARTED;
  }
}

void InvalidationNotifier::OnDisconnect() {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  VLOG(1) << "OnDisconnect";
}

void InvalidationNotifier::OnInvalidate(
    const syncable::ModelTypePayloadMap& type_payloads) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  FOR_EACH_OBSERVER(SyncNotifierObserver, observers_,
                    OnIncomingNotification(type_payloads));
}

void InvalidationNotifier::OnSessionStatusChanged(bool has_session) {
  FOR_EACH_OBSERVER(SyncNotifierObserver, observers_,
                    OnNotificationStateChange(has_session));
}

void InvalidationNotifier::WriteState(const std::string& state) {
  DCHECK(non_thread_safe_.CalledOnValidThread());
  VLOG(1) << "WriteState";
  FOR_EACH_OBSERVER(SyncNotifierObserver, observers_, StoreState(state));
}

}  // namespace sync_notifier