// 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/automation/testing_automation_provider.h"

#include "base/values.h"
#include "chrome/browser/automation/automation_provider_json.h"
#include "chrome/browser/automation/automation_provider_observers.h"
#include "chrome/browser/chromeos/cros/cros_library.h"
#include "chrome/browser/chromeos/cros/network_library.h"
#include "chrome/browser/chromeos/cros/power_library.h"
#include "chrome/browser/chromeos/cros/screen_lock_library.h"
#include "chrome/browser/chromeos/cros/update_library.h"
#include "chrome/browser/chromeos/login/existing_user_controller.h"
#include "chrome/browser/chromeos/login/screen_locker.h"
#include "chrome/browser/chromeos/proxy_cros_settings_provider.h"

using chromeos::CrosLibrary;
using chromeos::NetworkLibrary;
using chromeos::UserManager;
using chromeos::UpdateLibrary;

namespace {

bool EnsureCrosLibraryLoaded(AutomationProvider* provider,
                             IPC::Message* reply_message) {
  if (!CrosLibrary::Get()->EnsureLoaded()) {
    AutomationJSONReply(provider, reply_message).SendError(
        "Could not load cros library.");
    return false;
  }
  return true;
}

DictionaryValue* GetNetworkInfoDict(const chromeos::Network* network) {
  DictionaryValue* item = new DictionaryValue;
  item->SetString("name", network->name());
  item->SetString("device_path", network->device_path());
  item->SetString("ip_address", network->ip_address());
  item->SetString("status", network->GetStateString());
  return item;
}

Value* GetProxySetting(const std::string& setting_name) {
  chromeos::ProxyCrosSettingsProvider settings_provider;
  std::string setting_path = "cros.session.proxy.";
  setting_path.append(setting_name);

  if (setting_name == "ignorelist") {
    Value* value;
    if (settings_provider.Get(setting_path, &value))
      return value;
  } else {
    Value* setting;
    if (settings_provider.Get(setting_path, &setting)) {
      DictionaryValue* setting_dict = static_cast<DictionaryValue*>(setting);
      Value* value;
      bool found = setting_dict->Remove("value", &value);
      delete setting;
      if (found)
        return value;
    }
  }
  return NULL;
}

const char* UpdateStatusToString(chromeos::UpdateStatusOperation status) {
  switch (status) {
    case chromeos::UPDATE_STATUS_IDLE:
      return "idle";
    case chromeos::UPDATE_STATUS_CHECKING_FOR_UPDATE:
      return "checking for update";
    case chromeos::UPDATE_STATUS_UPDATE_AVAILABLE:
      return "update available";
    case chromeos::UPDATE_STATUS_DOWNLOADING:
      return "downloading";
    case chromeos::UPDATE_STATUS_VERIFYING:
      return "verifying";
    case chromeos::UPDATE_STATUS_FINALIZING:
      return "finalizing";
    case chromeos::UPDATE_STATUS_UPDATED_NEED_REBOOT:
      return "updated need reboot";
    case chromeos::UPDATE_STATUS_REPORTING_ERROR_EVENT:
      return "reporting error event";
    default:
      return "unknown";
  }
}

void GetReleaseTrackCallback(void* user_data, const char* track) {
  AutomationJSONReply* reply = static_cast<AutomationJSONReply*>(user_data);

  if (track == NULL) {
    reply->SendError("Unable to get release track.");
    delete reply;
    return;
  }

  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
  return_value->SetString("release_track", track);

  UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary();
  const UpdateLibrary::Status& status = update_library->status();
  chromeos::UpdateStatusOperation update_status = status.status;
  return_value->SetString("status", UpdateStatusToString(update_status));
  if (update_status == chromeos::UPDATE_STATUS_DOWNLOADING)
    return_value->SetDouble("download_progress", status.download_progress);
  if (status.last_checked_time > 0)
    return_value->SetInteger("last_checked_time", status.last_checked_time);
  if (status.new_size > 0)
    return_value->SetInteger("new_size", status.new_size);

  reply->SendSuccess(return_value.get());
  delete reply;
}

void UpdateCheckCallback(void* user_data, chromeos::UpdateResult result,
                         const char* error_msg) {
  AutomationJSONReply* reply = static_cast<AutomationJSONReply*>(user_data);
  if (result == chromeos::UPDATE_RESULT_SUCCESS)
    reply->SendSuccess(NULL);
  else
    reply->SendError(error_msg);
  delete reply;
}

}  // namespace

void TestingAutomationProvider::GetLoginInfo(DictionaryValue* args,
                                             IPC::Message* reply_message) {
  AutomationJSONReply reply(this, reply_message);
  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);

  const UserManager* user_manager = UserManager::Get();
  if (!user_manager)
    reply.SendError("No user manager!");
  const chromeos::ScreenLocker* screen_locker =
      chromeos::ScreenLocker::default_screen_locker();

  return_value->SetBoolean("is_owner", user_manager->current_user_is_owner());
  return_value->SetBoolean("is_logged_in", user_manager->user_is_logged_in());
  return_value->SetBoolean("is_screen_locked", screen_locker);
  if (user_manager->user_is_logged_in()) {
    return_value->SetBoolean("is_guest", user_manager->IsLoggedInAsGuest());
    return_value->SetString("email", user_manager->logged_in_user().email());
  }

  reply.SendSuccess(return_value.get());
}

// Logging in as guest will cause session_manager to restart Chrome with new
// flags. If you used EnableChromeTesting, you will have to call it again.
void TestingAutomationProvider::LoginAsGuest(DictionaryValue* args,
                                             IPC::Message* reply_message) {
  chromeos::ExistingUserController* controller =
      chromeos::ExistingUserController::current_controller();
  // Set up an observer (it will delete itself).
  new LoginManagerObserver(this, reply_message);
  controller->LoginAsGuest();
}

void TestingAutomationProvider::Login(DictionaryValue* args,
                                      IPC::Message* reply_message) {
  std::string username, password;
  if (!args->GetString("username", &username) ||
      !args->GetString("password", &password)) {
    AutomationJSONReply(this, reply_message).SendError(
        "Invalid or missing args.");
    return;
  }

  chromeos::ExistingUserController* controller =
      chromeos::ExistingUserController::current_controller();
  // Set up an observer (it will delete itself).
  new LoginManagerObserver(this, reply_message);
  controller->Login(username, password);
}

void TestingAutomationProvider::LockScreen(DictionaryValue* args,
                                           IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  new ScreenLockUnlockObserver(this, reply_message, true);
  CrosLibrary::Get()->GetScreenLockLibrary()->
      NotifyScreenLockRequested();
}

void TestingAutomationProvider::UnlockScreen(DictionaryValue* args,
                                             IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  new ScreenLockUnlockObserver(this, reply_message, false);
  CrosLibrary::Get()->GetScreenLockLibrary()->
      NotifyScreenUnlockRequested();
}

// Signing out could have undesirable side effects: session_manager is
// killed, so its children, including chrome and the window manager, will
// also be killed. Anything owned by chronos will probably be killed.
void TestingAutomationProvider::SignoutInScreenLocker(
    DictionaryValue* args, IPC::Message* reply_message) {
  AutomationJSONReply reply(this, reply_message);
  chromeos::ScreenLocker* screen_locker =
      chromeos::ScreenLocker::default_screen_locker();
  if (!screen_locker) {
    reply.SendError(
        "No default screen locker. Are you sure the screen is locked?");
    return;
  }

  // Send success before stopping session because if we're a child of
  // session manager then we'll die when the session is stopped.
  reply.SendSuccess(NULL);
  screen_locker->Signout();
}

void TestingAutomationProvider::GetBatteryInfo(DictionaryValue* args,
                                               IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  chromeos::PowerLibrary* power_library = CrosLibrary::Get()->GetPowerLibrary();
  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);

  return_value->SetBoolean("battery_is_present",
                           power_library->battery_is_present());
  return_value->SetBoolean("line_power_on", power_library->line_power_on());
  if (power_library->battery_is_present()) {
    return_value->SetBoolean("battery_fully_charged",
                             power_library->battery_fully_charged());
    return_value->SetDouble("battery_percentage",
                            power_library->battery_percentage());
    if (power_library->line_power_on()) {
      int time = power_library->battery_time_to_full().InSeconds();
      if (time > 0 || power_library->battery_fully_charged())
        return_value->SetInteger("battery_time_to_full", time);
    } else {
      int time = power_library->battery_time_to_empty().InSeconds();
      if (time > 0)
        return_value->SetInteger("battery_time_to_empty", time);
    }
  }

  AutomationJSONReply(this, reply_message).SendSuccess(return_value.get());
}

void TestingAutomationProvider::GetNetworkInfo(DictionaryValue* args,
                                               IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);

  // IP address.
  return_value->SetString("ip_address", network_library->IPAddress());

  // Currently connected networks.
  if (network_library->ethernet_network())
    return_value->SetString(
        "connected_ethernet",
        network_library->ethernet_network()->service_path());
  if (network_library->wifi_network())
    return_value->SetString("connected_wifi",
                            network_library->wifi_network()->service_path());
  if (network_library->cellular_network())
    return_value->SetString(
        "connected_cellular",
        network_library->cellular_network()->service_path());

  // Ethernet network.
  bool ethernet_available = network_library->ethernet_available();
  bool ethernet_enabled = network_library->ethernet_enabled();
  if (ethernet_available && ethernet_enabled) {
    const chromeos::EthernetNetwork* ethernet_network =
        network_library->ethernet_network();
    if (ethernet_network) {
      DictionaryValue* items = new DictionaryValue;
      DictionaryValue* item = GetNetworkInfoDict(ethernet_network);
      items->Set(ethernet_network->service_path(), item);
      return_value->Set("ethernet_networks", items);
    }
  }

  // Wi-fi networks.
  bool wifi_available = network_library->wifi_available();
  bool wifi_enabled = network_library->wifi_enabled();
  if (wifi_available && wifi_enabled) {
    const chromeos::WifiNetworkVector& wifi_networks =
        network_library->wifi_networks();
    DictionaryValue* items = new DictionaryValue;
    for (chromeos::WifiNetworkVector::const_iterator iter =
         wifi_networks.begin(); iter != wifi_networks.end(); ++iter) {
      const chromeos::WifiNetwork* wifi = *iter;
      DictionaryValue* item = GetNetworkInfoDict(wifi);
      item->SetInteger("strength", wifi->strength());
      item->SetBoolean("encrypted", wifi->encrypted());
      item->SetString("encryption", wifi->GetEncryptionString());
      items->Set(wifi->service_path(), item);
    }
    return_value->Set("wifi_networks", items);
  }

  // Cellular networks.
  bool cellular_available = network_library->cellular_available();
  bool cellular_enabled = network_library->cellular_enabled();
  if (cellular_available && cellular_enabled) {
    const chromeos::CellularNetworkVector& cellular_networks =
        network_library->cellular_networks();
    DictionaryValue* items = new DictionaryValue;
    for (size_t i = 0; i < cellular_networks.size(); ++i) {
      DictionaryValue* item = GetNetworkInfoDict(cellular_networks[i]);
      item->SetInteger("strength", cellular_networks[i]->strength());
      item->SetString("operator_name", cellular_networks[i]->operator_name());
      item->SetString("operator_code", cellular_networks[i]->operator_code());
      item->SetString("payment_url", cellular_networks[i]->payment_url());
      item->SetString("usage_url", cellular_networks[i]->usage_url());
      item->SetString("network_technology",
                      cellular_networks[i]->GetNetworkTechnologyString());
      item->SetString("connectivity_state",
                      cellular_networks[i]->GetConnectivityStateString());
      item->SetString("activation_state",
                      cellular_networks[i]->GetActivationStateString());
      item->SetString("roaming_state",
                      cellular_networks[i]->GetRoamingStateString());
      items->Set(cellular_networks[i]->service_path(), item);
    }
    return_value->Set("cellular_networks", items);
  }

  AutomationJSONReply(this, reply_message).SendSuccess(return_value.get());
}

void TestingAutomationProvider::NetworkScan(DictionaryValue* args,
                                            IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
  network_library->RequestNetworkScan();

  // Set up an observer (it will delete itself).
  new NetworkScanObserver(this, reply_message);
}

void TestingAutomationProvider::GetProxySettings(DictionaryValue* args,
                                                 IPC::Message* reply_message) {
  const char* settings[] = { "pacurl", "singlehttp", "singlehttpport",
                             "httpurl", "httpport", "httpsurl", "httpsport",
                             "type", "single", "ftpurl", "ftpport",
                             "socks", "socksport", "ignorelist" };

  scoped_ptr<DictionaryValue> return_value(new DictionaryValue);
  chromeos::ProxyCrosSettingsProvider settings_provider;

  for (size_t i = 0; i < arraysize(settings); ++i) {
    Value* setting = GetProxySetting(settings[i]);
    if (setting)
      return_value->Set(settings[i], setting);
  }

  AutomationJSONReply(this, reply_message).SendSuccess(return_value.get());
}

void TestingAutomationProvider::SetProxySettings(DictionaryValue* args,
                                                IPC::Message* reply_message) {
  AutomationJSONReply reply(this, reply_message);
  std::string key;
  Value* value;
  if (!args->GetString("key", &key) || !args->Get("value", &value)) {
    reply.SendError("Invalid or missing args.");
    return;
  }

  std::string setting_path = "cros.session.proxy.";
  setting_path.append(key);

  // ProxyCrosSettingsProvider will own the Value* passed to Set().
  chromeos::ProxyCrosSettingsProvider().Set(setting_path, value->DeepCopy());
  reply.SendSuccess(NULL);
}

void TestingAutomationProvider::ConnectToWifiNetwork(
    DictionaryValue* args, IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  AutomationJSONReply reply(this, reply_message);
  std::string service_path, password, identity, certpath;
  if (!args->GetString("service_path", &service_path) ||
      !args->GetString("password", &password) ||
      !args->GetString("identity", &identity) ||
      !args->GetString("certpath", &certpath)) {
    reply.SendError("Invalid or missing args.");
    return;
  }

  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
  chromeos::WifiNetwork* wifi =
      network_library->FindWifiNetworkByPath(service_path);
  if (!wifi) {
    reply.SendError("No network found with specified service path.");
    return;
  }
  if (!password.empty())
    wifi->SetPassphrase(password);
  if (!identity.empty())
    wifi->SetIdentity(identity);
  if (!certpath.empty())
    wifi->SetCertPath(certpath);

  // Set up an observer (it will delete itself).
  new ServicePathConnectObserver(this, reply_message, service_path);

  network_library->ConnectToWifiNetwork(wifi);
  network_library->RequestNetworkScan();
}

void TestingAutomationProvider::ConnectToHiddenWifiNetwork(
    DictionaryValue* args, IPC::Message* reply_message) {
  if (!CrosLibrary::Get()->EnsureLoaded()) {
    AutomationJSONReply(this, reply_message)
        .SendError("Could not load cros library.");
    return;
  }

  std::string ssid, security, password, identity, certpath;
  if (!args->GetString("ssid", &ssid) ||
      !args->GetString("security", &security) ||
      !args->GetString("password", &password) ||
      !args->GetString("identity", &identity) ||
      !args->GetString("certpath", &certpath)) {
    AutomationJSONReply(this, reply_message)
        .SendError("Invalid or missing args.");
    return;
  }

  std::map<std::string, chromeos::ConnectionSecurity> connection_security_map;
  connection_security_map["SECURITY_NONE"] = chromeos::SECURITY_NONE;
  connection_security_map["SECURITY_WEP"] = chromeos::SECURITY_WEP;
  connection_security_map["SECURITY_WPA"] = chromeos::SECURITY_WPA;
  connection_security_map["SECURITY_RSN"] = chromeos::SECURITY_RSN;
  connection_security_map["SECURITY_8021X"] = chromeos::SECURITY_8021X;

  if (connection_security_map.find(security) == connection_security_map.end()) {
    AutomationJSONReply(this, reply_message)
        .SendError("Unknown security type.");
    return;
  }
  chromeos::ConnectionSecurity connection_security =
      connection_security_map[security];

  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();

  // Set up an observer (it will delete itself).
  new SSIDConnectObserver(this, reply_message, ssid);

  network_library->ConnectToWifiNetwork(connection_security, ssid, password,
                                        identity, certpath);
}

void TestingAutomationProvider::DisconnectFromWifiNetwork(
    DictionaryValue* args, IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  AutomationJSONReply reply(this, reply_message);
  NetworkLibrary* network_library = CrosLibrary::Get()->GetNetworkLibrary();
  const chromeos::WifiNetwork* wifi = network_library->wifi_network();
  if (!wifi) {
    reply.SendError("Not connected to any wifi network.");
    return;
  }

  network_library->DisconnectFromNetwork(wifi);
  reply.SendSuccess(NULL);
}

void TestingAutomationProvider::GetUpdateInfo(DictionaryValue* args,
                                              IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary();
  AutomationJSONReply* reply = new AutomationJSONReply(this, reply_message);
  update_library->GetReleaseTrack(GetReleaseTrackCallback, reply);
}

void TestingAutomationProvider::UpdateCheck(
    DictionaryValue* args,
    IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary();
  AutomationJSONReply* reply = new AutomationJSONReply(this, reply_message);
  update_library->RequestUpdateCheck(UpdateCheckCallback, reply);
}

void TestingAutomationProvider::SetReleaseTrack(DictionaryValue* args,
                                                IPC::Message* reply_message) {
  if (!EnsureCrosLibraryLoaded(this, reply_message))
    return;

  AutomationJSONReply reply(this, reply_message);
  std::string track;
  if (!args->GetString("track", &track)) {
    reply.SendError("Invalid or missing args.");
    return;
  }

  UpdateLibrary* update_library = CrosLibrary::Get()->GetUpdateLibrary();
  update_library->SetReleaseTrack(track);
  reply.SendSuccess(NULL);
}