// 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 "remoting/host/setup/host_starter.h"

#include "base/guid.h"
#include "base/thread_task_runner_handle.h"
#include "base/values.h"
#include "google_apis/google_api_keys.h"
#include "remoting/host/pin_hash.h"
#include "remoting/host/setup/oauth_helper.h"

namespace {
const int kMaxGetTokensRetries = 3;
}  // namespace

namespace remoting {

HostStarter::HostStarter(
    scoped_ptr<gaia::GaiaOAuthClient> oauth_client,
    scoped_ptr<remoting::ServiceClient> service_client,
    scoped_refptr<remoting::DaemonController> daemon_controller)
    : oauth_client_(oauth_client.Pass()),
      service_client_(service_client.Pass()),
      daemon_controller_(daemon_controller),
      consent_to_data_collection_(false),
      unregistering_host_(false),
      weak_ptr_factory_(this),
      weak_ptr_(weak_ptr_factory_.GetWeakPtr()) {
  main_task_runner_ = base::ThreadTaskRunnerHandle::Get();
}

HostStarter::~HostStarter() {
}

scoped_ptr<HostStarter> HostStarter::Create(
    const std::string& chromoting_hosts_url,
    net::URLRequestContextGetter* url_request_context_getter) {
  scoped_ptr<gaia::GaiaOAuthClient> oauth_client(
      new gaia::GaiaOAuthClient(url_request_context_getter));
  scoped_ptr<remoting::ServiceClient> service_client(
      new remoting::ServiceClient(
          chromoting_hosts_url, url_request_context_getter));
  scoped_refptr<remoting::DaemonController> daemon_controller(
      remoting::DaemonController::Create());
  return scoped_ptr<HostStarter>(
      new HostStarter(oauth_client.Pass(), service_client.Pass(),
                      daemon_controller));
}

void HostStarter::StartHost(
    const std::string& host_name,
    const std::string& host_pin,
    bool consent_to_data_collection,
    const std::string& auth_code,
    const std::string& redirect_url,
    CompletionCallback on_done) {
  DCHECK(main_task_runner_->BelongsToCurrentThread());
  DCHECK(on_done_.is_null());

  host_name_ = host_name;
  host_pin_ = host_pin;
  consent_to_data_collection_ = consent_to_data_collection;
  on_done_ = on_done;
  oauth_client_info_.client_id =
      google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING);
  oauth_client_info_.client_secret =
      google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING);
  oauth_client_info_.redirect_uri = redirect_url;
  // Map the authorization code to refresh and access tokens.
  oauth_client_->GetTokensFromAuthCode(oauth_client_info_, auth_code,
                                       kMaxGetTokensRetries, this);
}

void HostStarter::OnGetTokensResponse(
    const std::string& refresh_token,
    const std::string& access_token,
    int expires_in_seconds) {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnGetTokensResponse, weak_ptr_,
        refresh_token, access_token, expires_in_seconds));
    return;
  }
  refresh_token_ = refresh_token;
  access_token_ = access_token;
  // Get the email corresponding to the access token.
  oauth_client_->GetUserEmail(access_token_, 1, this);
}

void HostStarter::OnRefreshTokenResponse(
    const std::string& access_token,
    int expires_in_seconds) {
  // We never request a refresh token, so this call is not expected.
  NOTREACHED();
}

// This function is called twice: once with the host owner credentials, and once
// with the service account credentials.
void HostStarter::OnGetUserEmailResponse(const std::string& user_email) {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnGetUserEmailResponse, weak_ptr_, user_email));
    return;
  }

  if (host_owner_.empty()) {
    // This is the first callback, with the host owner credentials. Store the
    // owner's email, and register the host.
    host_owner_ = user_email;
    host_id_ = base::GenerateGUID();
    key_pair_ = RsaKeyPair::Generate();

    std::string host_client_id;
    host_client_id = google_apis::GetOAuth2ClientID(
        google_apis::CLIENT_REMOTING_HOST);

    service_client_->RegisterHost(
        host_id_, host_name_, key_pair_->GetPublicKey(), host_client_id,
        access_token_, this);
  } else {
    // This is the second callback, with the service account credentials.
    // This email is the service account's email, used to login to XMPP.
    xmpp_login_ = user_email;
    StartHostProcess();
  }
}

void HostStarter::OnHostRegistered(const std::string& authorization_code) {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnHostRegistered, weak_ptr_, authorization_code));
    return;
  }

  if (authorization_code.empty()) {
    // No service account code, start the host with the owner's credentials.
    xmpp_login_ = host_owner_;
    StartHostProcess();
    return;
  }

  // Received a service account authorization code, update oauth_client_info_
  // to use the service account client keys, and get service account tokens.
  oauth_client_info_.client_id =
      google_apis::GetOAuth2ClientID(
          google_apis::CLIENT_REMOTING_HOST);
  oauth_client_info_.client_secret =
      google_apis::GetOAuth2ClientSecret(
          google_apis::CLIENT_REMOTING_HOST);
  oauth_client_info_.redirect_uri = "oob";
  oauth_client_->GetTokensFromAuthCode(
      oauth_client_info_, authorization_code, kMaxGetTokensRetries, this);
}

void HostStarter::StartHostProcess() {
  // Start the host.
  std::string host_secret_hash = remoting::MakeHostPinHash(host_id_, host_pin_);
  scoped_ptr<base::DictionaryValue> config(new base::DictionaryValue());
  if (host_owner_ != xmpp_login_) {
    config->SetString("host_owner", host_owner_);
  }
  config->SetString("xmpp_login", xmpp_login_);
  config->SetString("oauth_refresh_token", refresh_token_);
  config->SetString("host_id", host_id_);
  config->SetString("host_name", host_name_);
  config->SetString("private_key", key_pair_->ToString());
  config->SetString("host_secret_hash", host_secret_hash);
  daemon_controller_->SetConfigAndStart(
      config.Pass(), consent_to_data_collection_,
      base::Bind(&HostStarter::OnHostStarted, base::Unretained(this)));
}

void HostStarter::OnHostStarted(DaemonController::AsyncResult result) {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnHostStarted, weak_ptr_, result));
    return;
  }
  if (result != DaemonController::RESULT_OK) {
    unregistering_host_ = true;
    service_client_->UnregisterHost(host_id_, access_token_, this);
    return;
  }
  CompletionCallback cb = on_done_;
  on_done_.Reset();
  cb.Run(START_COMPLETE);
}

void HostStarter::OnOAuthError() {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnOAuthError, weak_ptr_));
    return;
  }
  CompletionCallback cb = on_done_;
  on_done_.Reset();
  if (unregistering_host_) {
    LOG(ERROR) << "OAuth error occurred when unregistering host.";
    cb.Run(START_ERROR);
  } else {
    cb.Run(OAUTH_ERROR);
  }
}

void HostStarter::OnNetworkError(int response_code) {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnNetworkError, weak_ptr_, response_code));
    return;
  }
  CompletionCallback cb = on_done_;
  on_done_.Reset();
  if (unregistering_host_) {
    LOG(ERROR) << "Network error occurred when unregistering host.";
    cb.Run(START_ERROR);
  } else {
    cb.Run(NETWORK_ERROR);
  }
}

void HostStarter::OnHostUnregistered() {
  if (!main_task_runner_->BelongsToCurrentThread()) {
    main_task_runner_->PostTask(FROM_HERE, base::Bind(
        &HostStarter::OnHostUnregistered, weak_ptr_));
    return;
  }
  CompletionCallback cb = on_done_;
  on_done_.Reset();
  cb.Run(START_ERROR);
}

}  // namespace remoting
