// 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/chromeos/options/vpn_config_view.h"

#include "ash/system/chromeos/network/network_connect.h"
#include "base/bind.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/enrollment_dialog_view.h"
#include "chrome/browser/chromeos/net/onc_utils.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/common/net/x509_certificate_model.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/login/login_state.h"
#include "chromeos/network/network_configuration_handler.h"
#include "chromeos/network/network_event_log.h"
#include "chromeos/network/network_state.h"
#include "chromeos/network/network_state_handler.h"
#include "chromeos/network/network_ui_data.h"
#include "components/onc/onc_constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/combobox_model.h"
#include "ui/events/event.h"
#include "ui/views/controls/button/checkbox.h"
#include "ui/views/controls/combobox/combobox.h"
#include "ui/views/controls/label.h"
#include "ui/views/controls/textfield/textfield.h"
#include "ui/views/layout/grid_layout.h"
#include "ui/views/layout/layout_constants.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/dialog_client_view.h"

namespace {

enum ProviderTypeIndex {
  PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK = 0,
  PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT = 1,
  PROVIDER_TYPE_INDEX_OPEN_VPN = 2,
  PROVIDER_TYPE_INDEX_MAX = 3,
};

base::string16 ProviderTypeIndexToString(int index) {
  switch (index) {
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK:
      return l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_L2TP_IPSEC_PSK);
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT:
      return l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_L2TP_IPSEC_USER_CERT);
    case PROVIDER_TYPE_INDEX_OPEN_VPN:
      return l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_OPEN_VPN);
  }
  NOTREACHED();
  return base::string16();
}

int ProviderTypeToIndex(const std::string& provider_type,
                        const std::string& client_cert_id) {
  if (provider_type == shill::kProviderL2tpIpsec) {
    if (!client_cert_id.empty())
      return PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT;
    else
      return PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK;
  } else {
    DCHECK(provider_type == shill::kProviderOpenVpn);
    return PROVIDER_TYPE_INDEX_OPEN_VPN;
  }
}

// Translates the provider type to the name of the respective ONC dictionary
// containing configuration data for the type.
std::string ProviderTypeIndexToONCDictKey(int provider_type_index) {
  switch (provider_type_index) {
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK:
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT:
      return onc::vpn::kIPsec;
    case PROVIDER_TYPE_INDEX_OPEN_VPN:
      return onc::vpn::kOpenVPN;
  }
  NOTREACHED() << "Unhandled provider type index " << provider_type_index;
  return std::string();
}

std::string GetPemFromDictionary(
    const base::DictionaryValue* provider_properties,
    const std::string& key) {
  const base::ListValue* pems = NULL;
  if (!provider_properties->GetListWithoutPathExpansion(key, &pems))
    return std::string();
  std::string pem;
  pems->GetString(0, &pem);
  return pem;
}

}  // namespace

namespace chromeos {

namespace internal {

class ProviderTypeComboboxModel : public ui::ComboboxModel {
 public:
  ProviderTypeComboboxModel();
  virtual ~ProviderTypeComboboxModel();

  // Overridden from ui::ComboboxModel:
  virtual int GetItemCount() const OVERRIDE;
  virtual base::string16 GetItemAt(int index) OVERRIDE;

 private:
  DISALLOW_COPY_AND_ASSIGN(ProviderTypeComboboxModel);
};

class VpnServerCACertComboboxModel : public ui::ComboboxModel {
 public:
  VpnServerCACertComboboxModel();
  virtual ~VpnServerCACertComboboxModel();

  // Overridden from ui::ComboboxModel:
  virtual int GetItemCount() const OVERRIDE;
  virtual base::string16 GetItemAt(int index) OVERRIDE;

 private:
  DISALLOW_COPY_AND_ASSIGN(VpnServerCACertComboboxModel);
};

class VpnUserCertComboboxModel : public ui::ComboboxModel {
 public:
  VpnUserCertComboboxModel();
  virtual ~VpnUserCertComboboxModel();

  // Overridden from ui::ComboboxModel:
  virtual int GetItemCount() const OVERRIDE;
  virtual base::string16 GetItemAt(int index) OVERRIDE;

 private:
  DISALLOW_COPY_AND_ASSIGN(VpnUserCertComboboxModel);
};

// ProviderTypeComboboxModel ---------------------------------------------------

ProviderTypeComboboxModel::ProviderTypeComboboxModel() {
}

ProviderTypeComboboxModel::~ProviderTypeComboboxModel() {
}

int ProviderTypeComboboxModel::GetItemCount() const {
  return PROVIDER_TYPE_INDEX_MAX;
}

base::string16 ProviderTypeComboboxModel::GetItemAt(int index) {
  return ProviderTypeIndexToString(index);
}

// VpnServerCACertComboboxModel ------------------------------------------------

VpnServerCACertComboboxModel::VpnServerCACertComboboxModel() {
}

VpnServerCACertComboboxModel::~VpnServerCACertComboboxModel() {
}

int VpnServerCACertComboboxModel::GetItemCount() const {
  if (CertLibrary::Get()->CertificatesLoading())
    return 1;  // "Loading"
  // "Default" + certs.
  return CertLibrary::Get()->NumCertificates(
      CertLibrary::CERT_TYPE_SERVER_CA) + 1;
}

base::string16 VpnServerCACertComboboxModel::GetItemAt(int index) {
  if (CertLibrary::Get()->CertificatesLoading())
    return l10n_util::GetStringUTF16(
        IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CERT_LOADING);
  if (index == 0)
    return l10n_util::GetStringUTF16(
        IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CERT_SERVER_CA_DEFAULT);
  int cert_index = index - 1;
  return CertLibrary::Get()->GetCertDisplayStringAt(
      CertLibrary::CERT_TYPE_SERVER_CA, cert_index);
}

// VpnUserCertComboboxModel ----------------------------------------------------

VpnUserCertComboboxModel::VpnUserCertComboboxModel() {
}

VpnUserCertComboboxModel::~VpnUserCertComboboxModel() {
}

int VpnUserCertComboboxModel::GetItemCount() const {
  if (CertLibrary::Get()->CertificatesLoading())
    return 1;  // "Loading"
  int num_certs =
      CertLibrary::Get()->NumCertificates(CertLibrary::CERT_TYPE_USER);
  if (num_certs == 0)
    return 1;  // "None installed"
  return num_certs;
}

base::string16 VpnUserCertComboboxModel::GetItemAt(int index) {
  if (CertLibrary::Get()->CertificatesLoading()) {
    return l10n_util::GetStringUTF16(
        IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CERT_LOADING);
  }
  if (CertLibrary::Get()->NumCertificates(CertLibrary::CERT_TYPE_USER) == 0) {
    return l10n_util::GetStringUTF16(
        IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_USER_CERT_NONE_INSTALLED);
  }
  return CertLibrary::Get()->GetCertDisplayStringAt(
      CertLibrary::CERT_TYPE_USER, index);
}

}  // namespace internal

VPNConfigView::VPNConfigView(NetworkConfigView* parent,
                             const std::string& service_path)
    : ChildNetworkConfigView(parent, service_path),
      service_text_modified_(false),
      enable_psk_passphrase_(false),
      enable_user_cert_(false),
      enable_server_ca_cert_(false),
      enable_otp_(false),
      enable_group_name_(false),
      title_(0),
      layout_(NULL),
      server_textfield_(NULL),
      service_text_(NULL),
      service_textfield_(NULL),
      provider_type_combobox_(NULL),
      provider_type_text_label_(NULL),
      psk_passphrase_label_(NULL),
      psk_passphrase_textfield_(NULL),
      user_cert_label_(NULL),
      user_cert_combobox_(NULL),
      server_ca_cert_label_(NULL),
      server_ca_cert_combobox_(NULL),
      username_textfield_(NULL),
      user_passphrase_textfield_(NULL),
      otp_label_(NULL),
      otp_textfield_(NULL),
      group_name_label_(NULL),
      group_name_textfield_(NULL),
      save_credentials_checkbox_(NULL),
      error_label_(NULL),
      provider_type_index_(PROVIDER_TYPE_INDEX_MAX),
      weak_ptr_factory_(this) {
  Init();
}

VPNConfigView::~VPNConfigView() {
  RemoveAllChildViews(true);  // Destroy children before models
  CertLibrary::Get()->RemoveObserver(this);
}

base::string16 VPNConfigView::GetTitle() const {
  DCHECK_NE(title_, 0);
  return l10n_util::GetStringUTF16(title_);
}

views::View* VPNConfigView::GetInitiallyFocusedView() {
  if (service_path_.empty()) {
    // Put focus in the first editable field.
    if (server_textfield_)
      return server_textfield_;
    else if (service_textfield_)
      return service_textfield_;
    else if (provider_type_combobox_)
      return provider_type_combobox_;
    else if (psk_passphrase_textfield_ && psk_passphrase_textfield_->enabled())
      return psk_passphrase_textfield_;
    else if (user_cert_combobox_ && user_cert_combobox_->enabled())
      return user_cert_combobox_;
    else if (server_ca_cert_combobox_ && server_ca_cert_combobox_->enabled())
      return server_ca_cert_combobox_;
  }
  if (user_passphrase_textfield_)
    return user_passphrase_textfield_;
  else if (otp_textfield_)
    return otp_textfield_;
  return NULL;
}

bool VPNConfigView::CanLogin() {
  // Username is always required.
  if (GetUsername().empty())
    return false;

  // TODO(stevenjb): min kMinPassphraseLen length?
  if (service_path_.empty() &&
      (GetService().empty() || GetServer().empty()))
    return false;

  // Block login if certs are required but user has none.
  bool cert_required =
      GetProviderTypeIndex() == PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT;
  if (cert_required && (!HaveUserCerts() || !IsUserCertValid()))
    return false;

  return true;
}

void VPNConfigView::ContentsChanged(views::Textfield* sender,
                                    const base::string16& new_contents) {
  if (sender == server_textfield_ && !service_text_modified_) {
    // Set the service name to the server name up to '.', unless it has
    // been explicitly set by the user.
    base::string16 server = server_textfield_->text();
    base::string16::size_type n = server.find_first_of(L'.');
    service_name_from_server_ = server.substr(0, n);
    service_textfield_->SetText(service_name_from_server_);
  }
  if (sender == service_textfield_) {
    if (new_contents.empty())
      service_text_modified_ = false;
    else if (new_contents != service_name_from_server_)
      service_text_modified_ = true;
  }
  UpdateCanLogin();
}

bool VPNConfigView::HandleKeyEvent(views::Textfield* sender,
                                   const ui::KeyEvent& key_event) {
  if ((sender == psk_passphrase_textfield_ ||
       sender == user_passphrase_textfield_) &&
      key_event.key_code() == ui::VKEY_RETURN) {
    parent_->GetDialogClientView()->AcceptWindow();
  }
  return false;
}

void VPNConfigView::ButtonPressed(views::Button* sender,
                                  const ui::Event& event) {
}

void VPNConfigView::OnPerformAction(views::Combobox* combobox) {
  UpdateControls();
  UpdateErrorLabel();
  UpdateCanLogin();
}

void VPNConfigView::OnCertificatesLoaded(bool initial_load) {
  Refresh();
}

bool VPNConfigView::Login() {
  if (service_path_.empty()) {
    base::DictionaryValue properties;
    // Identifying properties
    properties.SetStringWithoutPathExpansion(
        shill::kTypeProperty, shill::kTypeVPN);
    properties.SetStringWithoutPathExpansion(
        shill::kNameProperty, GetService());
    properties.SetStringWithoutPathExpansion(
        shill::kProviderHostProperty, GetServer());
    properties.SetStringWithoutPathExpansion(
        shill::kProviderTypeProperty, GetProviderTypeString());

    SetConfigProperties(&properties);
    bool shared = false;
    bool modifiable = false;
    ChildNetworkConfigView::GetShareStateForLoginState(&shared, &modifiable);

    bool only_policy_autoconnect =
        onc::PolicyAllowsOnlyPolicyNetworksToAutoconnect(!shared);
    if (only_policy_autoconnect) {
      properties.SetBooleanWithoutPathExpansion(shill::kAutoConnectProperty,
                                                false);
    }

    ash::network_connect::CreateConfigurationAndConnect(&properties, shared);
  } else {
    const NetworkState* vpn = NetworkHandler::Get()->network_state_handler()->
        GetNetworkState(service_path_);
    if (!vpn) {
      // Shill no longer knows about this network (edge case).
      // TODO(stevenjb): Add notification for this.
      NET_LOG_ERROR("Network not found", service_path_);
      return true;  // Close dialog
    }
    base::DictionaryValue properties;
    SetConfigProperties(&properties);
    ash::network_connect::ConfigureNetworkAndConnect(
        service_path_, properties, false /* not shared */);
  }
  return true;  // Close dialog.
}

void VPNConfigView::Cancel() {
}

void VPNConfigView::InitFocus() {
  views::View* view_to_focus = GetInitiallyFocusedView();
  if (view_to_focus)
    view_to_focus->RequestFocus();
}

const std::string VPNConfigView::GetService() const {
  if (service_textfield_ != NULL)
    return GetTextFromField(service_textfield_, true);
  return service_path_;
}

const std::string VPNConfigView::GetServer() const {
  if (server_textfield_ != NULL)
    return GetTextFromField(server_textfield_, true);
  return std::string();
}

const std::string VPNConfigView::GetPSKPassphrase() const {
  if (psk_passphrase_textfield_ &&
      enable_psk_passphrase_ &&
      psk_passphrase_textfield_->visible())
    return GetPassphraseFromField(psk_passphrase_textfield_);
  return std::string();
}

const std::string VPNConfigView::GetUsername() const {
  return GetTextFromField(username_textfield_, true);
}

const std::string VPNConfigView::GetUserPassphrase() const {
  return GetPassphraseFromField(user_passphrase_textfield_);
}

const std::string VPNConfigView::GetGroupName() const {
  return GetTextFromField(group_name_textfield_, false);
}

const std::string VPNConfigView::GetOTP() const {
  return GetTextFromField(otp_textfield_, true);
}

const std::string VPNConfigView::GetServerCACertPEM() const {
  int index = server_ca_cert_combobox_ ?
      server_ca_cert_combobox_->selected_index() : 0;
  if (index == 0) {
    // First item is "Default".
    return std::string();
  } else {
    int cert_index = index - 1;
    return CertLibrary::Get()->GetServerCACertPEMAt(cert_index);
  }
}

void VPNConfigView::SetUserCertProperties(
    chromeos::client_cert::ConfigType client_cert_type,
    base::DictionaryValue* properties) const {
  if (!HaveUserCerts()) {
    // No certificate selected or not required.
    chromeos::client_cert::SetEmptyShillProperties(client_cert_type,
                                                   properties);
  } else {
    // Certificates are listed in the order they appear in the model.
    int index = user_cert_combobox_ ? user_cert_combobox_->selected_index() : 0;
    int slot_id = -1;
    const std::string pkcs11_id =
        CertLibrary::Get()->GetUserCertPkcs11IdAt(index, &slot_id);
    chromeos::client_cert::SetShillProperties(
        client_cert_type, slot_id, pkcs11_id, properties);
  }
}

bool VPNConfigView::GetSaveCredentials() const {
  return save_credentials_checkbox_->checked();
}

int VPNConfigView::GetProviderTypeIndex() const {
  if (provider_type_combobox_)
    return provider_type_combobox_->selected_index();
  return provider_type_index_;
}

std::string VPNConfigView::GetProviderTypeString() const {
  int index = GetProviderTypeIndex();
  switch (index) {
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK:
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT:
      return shill::kProviderL2tpIpsec;
    case PROVIDER_TYPE_INDEX_OPEN_VPN:
      return shill::kProviderOpenVpn;
  }
  NOTREACHED();
  return std::string();
}

void VPNConfigView::Init() {
  const NetworkState* vpn = NULL;
  if (!service_path_.empty()) {
    vpn = NetworkHandler::Get()->network_state_handler()->
        GetNetworkState(service_path_);
    DCHECK(vpn && vpn->type() == shill::kTypeVPN);
  }
  layout_ = views::GridLayout::CreatePanel(this);
  SetLayoutManager(layout_);

  // Observer any changes to the certificate list.
  CertLibrary::Get()->AddObserver(this);

  views::ColumnSet* column_set = layout_->AddColumnSet(0);
  // Label.
  column_set->AddColumn(views::GridLayout::LEADING, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0, 0);
  column_set->AddPaddingColumn(0, views::kRelatedControlSmallHorizontalSpacing);
  // Textfield, combobox.
  column_set->AddColumn(views::GridLayout::FILL, views::GridLayout::FILL, 1,
                        views::GridLayout::USE_PREF, 0,
                        ChildNetworkConfigView::kInputFieldMinWidth);
  column_set->AddPaddingColumn(0, views::kRelatedControlSmallHorizontalSpacing);
  // Policy indicator.
  column_set->AddColumn(views::GridLayout::CENTER, views::GridLayout::CENTER, 0,
                        views::GridLayout::USE_PREF, 0, 0);

  // Initialize members.
  service_text_modified_ = false;
  title_ = vpn ? IDS_OPTIONS_SETTINGS_JOIN_VPN : IDS_OPTIONS_SETTINGS_ADD_VPN;

  // By default enable all controls.
  enable_psk_passphrase_ = true;
  enable_user_cert_ = true;
  enable_server_ca_cert_ = true;
  enable_otp_ = true;
  enable_group_name_ = true;

  // Server label and input.
  layout_->StartRow(0, 0);
  views::View* server_label =
      new views::Label(l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVER_HOSTNAME));
  layout_->AddView(server_label);
  server_textfield_ = new views::Textfield();
  server_textfield_->set_controller(this);
  layout_->AddView(server_textfield_);
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
  if (!service_path_.empty()) {
    server_label->SetEnabled(false);
    server_textfield_->SetEnabled(false);
  }

  // Service label and name or input.
  layout_->StartRow(0, 0);
  layout_->AddView(new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_SERVICE_NAME)));
  if (service_path_.empty()) {
    service_textfield_ = new views::Textfield();
    service_textfield_->set_controller(this);
    layout_->AddView(service_textfield_);
    service_text_ = NULL;
  } else {
    service_text_ = new views::Label();
    service_text_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    layout_->AddView(service_text_);
    service_textfield_ = NULL;
  }
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // Provider type label and select.
  layout_->StartRow(0, 0);
  layout_->AddView(new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_PROVIDER_TYPE)));
  if (service_path_.empty()) {
    provider_type_combobox_model_.reset(
        new internal::ProviderTypeComboboxModel);
    provider_type_combobox_ = new views::Combobox(
        provider_type_combobox_model_.get());
    provider_type_combobox_->set_listener(this);
    layout_->AddView(provider_type_combobox_);
    provider_type_text_label_ = NULL;
  } else {
    provider_type_text_label_ = new views::Label();
    provider_type_text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
    layout_->AddView(provider_type_text_label_);
    provider_type_combobox_ = NULL;
  }
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // PSK passphrase label, input and visible button.
  layout_->StartRow(0, 0);
  psk_passphrase_label_ =  new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_PSK_PASSPHRASE));
  layout_->AddView(psk_passphrase_label_);
  psk_passphrase_textfield_ = new PassphraseTextfield();
  psk_passphrase_textfield_->set_controller(this);
  layout_->AddView(psk_passphrase_textfield_);
  layout_->AddView(
      new ControlledSettingIndicatorView(psk_passphrase_ui_data_));
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // Server CA certificate
  if (service_path_.empty()) {
    layout_->StartRow(0, 0);
    server_ca_cert_label_ = new views::Label(l10n_util::GetStringUTF16(
        IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_CERT_SERVER_CA));
    layout_->AddView(server_ca_cert_label_);
    server_ca_cert_combobox_model_.reset(
        new internal::VpnServerCACertComboboxModel());
    server_ca_cert_combobox_ = new views::Combobox(
        server_ca_cert_combobox_model_.get());
    layout_->AddView(server_ca_cert_combobox_);
    layout_->AddView(new ControlledSettingIndicatorView(ca_cert_ui_data_));
    layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);
  } else {
    server_ca_cert_label_ = NULL;
    server_ca_cert_combobox_ = NULL;
  }

  // User certificate label and input.
  layout_->StartRow(0, 0);
  user_cert_label_ = new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_USER_CERT));
  layout_->AddView(user_cert_label_);
  user_cert_combobox_model_.reset(
      new internal::VpnUserCertComboboxModel());
  user_cert_combobox_ = new views::Combobox(user_cert_combobox_model_.get());
  user_cert_combobox_->set_listener(this);
  layout_->AddView(user_cert_combobox_);
  layout_->AddView(new ControlledSettingIndicatorView(user_cert_ui_data_));
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // Username label and input.
  layout_->StartRow(0, 0);
  layout_->AddView(new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_USERNAME)));
  username_textfield_ = new views::Textfield();
  username_textfield_->set_controller(this);
  username_textfield_->SetEnabled(username_ui_data_.IsEditable());
  layout_->AddView(username_textfield_);
  layout_->AddView(new ControlledSettingIndicatorView(username_ui_data_));
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // User passphrase label, input and visble button.
  layout_->StartRow(0, 0);
  layout_->AddView(new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_USER_PASSPHRASE)));
  user_passphrase_textfield_ = new PassphraseTextfield();
  user_passphrase_textfield_->set_controller(this);
  user_passphrase_textfield_->SetEnabled(user_passphrase_ui_data_.IsEditable());
  layout_->AddView(user_passphrase_textfield_);
  layout_->AddView(
      new ControlledSettingIndicatorView(user_passphrase_ui_data_));
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // OTP label and input.
  layout_->StartRow(0, 0);
  otp_label_ = new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_OTP));
  layout_->AddView(otp_label_);
  otp_textfield_ = new views::Textfield();
  otp_textfield_->set_controller(this);
  layout_->AddView(otp_textfield_);
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // Group Name label and input.
  layout_->StartRow(0, 0);
  group_name_label_ = new views::Label(l10n_util::GetStringUTF16(
      IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_VPN_GROUP_NAME));
  layout_->AddView(group_name_label_);
  group_name_textfield_ =
      new views::Textfield();
  group_name_textfield_->set_controller(this);
  layout_->AddView(group_name_textfield_);
  layout_->AddView(new ControlledSettingIndicatorView(group_name_ui_data_));
  layout_->AddPaddingRow(0, views::kRelatedControlVerticalSpacing);

  // Save credentials
  layout_->StartRow(0, 0);
  save_credentials_checkbox_ = new views::Checkbox(
      l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_SAVE_CREDENTIALS));
  save_credentials_checkbox_->SetEnabled(
      save_credentials_ui_data_.IsEditable());
  layout_->SkipColumns(1);
  layout_->AddView(save_credentials_checkbox_);
  layout_->AddView(
      new ControlledSettingIndicatorView(save_credentials_ui_data_));

  // Error label.
  layout_->StartRow(0, 0);
  layout_->SkipColumns(1);
  error_label_ = new views::Label();
  error_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
  error_label_->SetEnabledColor(SK_ColorRED);
  layout_->AddView(error_label_);

  // Set or hide the UI, update comboboxes and error labels.
  Refresh();

  if (vpn) {
    NetworkHandler::Get()->network_configuration_handler()->GetProperties(
        service_path_,
        base::Bind(&VPNConfigView::InitFromProperties,
                   weak_ptr_factory_.GetWeakPtr()),
        base::Bind(&VPNConfigView::GetPropertiesError,
                   weak_ptr_factory_.GetWeakPtr()));
  }
}

void VPNConfigView::InitFromProperties(
    const std::string& service_path,
    const base::DictionaryValue& service_properties) {
  const NetworkState* vpn = NetworkHandler::Get()->network_state_handler()->
      GetNetworkState(service_path);
  if (!vpn) {
    NET_LOG_ERROR("Shill Error getting properties VpnConfigView", service_path);
    return;
  }

  std::string provider_type, server_hostname, username, group_name;
  bool psk_passphrase_required = false;
  bool user_passphrase_required = true;
  const base::DictionaryValue* provider_properties;
  if (service_properties.GetDictionaryWithoutPathExpansion(
          shill::kProviderProperty, &provider_properties)) {
    provider_properties->GetStringWithoutPathExpansion(
        shill::kTypeProperty, &provider_type);
    provider_properties->GetStringWithoutPathExpansion(
        shill::kHostProperty, &server_hostname);
    if (provider_type == shill::kProviderL2tpIpsec) {
      provider_properties->GetStringWithoutPathExpansion(
          shill::kL2tpIpsecClientCertIdProperty, &client_cert_id_);
      ca_cert_pem_ = GetPemFromDictionary(
          provider_properties, shill::kL2tpIpsecCaCertPemProperty);
      provider_properties->GetBooleanWithoutPathExpansion(
          shill::kL2tpIpsecPskRequiredProperty, &psk_passphrase_required);
      provider_properties->GetStringWithoutPathExpansion(
          shill::kL2tpIpsecUserProperty, &username);
      provider_properties->GetStringWithoutPathExpansion(
          shill::kL2tpIpsecTunnelGroupProperty, &group_name);
    } else if (provider_type == shill::kProviderOpenVpn) {
      provider_properties->GetStringWithoutPathExpansion(
          shill::kOpenVPNClientCertIdProperty, &client_cert_id_);
      ca_cert_pem_ = GetPemFromDictionary(
          provider_properties, shill::kOpenVPNCaCertPemProperty);
      provider_properties->GetStringWithoutPathExpansion(
          shill::kOpenVPNUserProperty, &username);
      provider_properties->GetBooleanWithoutPathExpansion(
          shill::kPassphraseRequiredProperty, &user_passphrase_required);
    }
  }
  bool save_credentials = false;
  service_properties.GetBooleanWithoutPathExpansion(
      shill::kSaveCredentialsProperty, &save_credentials);

  provider_type_index_ = ProviderTypeToIndex(provider_type, client_cert_id_);

  if (service_text_)
    service_text_->SetText(base::ASCIIToUTF16(vpn->name()));
  if (provider_type_text_label_)
    provider_type_text_label_->SetText(
        ProviderTypeIndexToString(provider_type_index_));

  if (server_textfield_ && !server_hostname.empty())
    server_textfield_->SetText(base::UTF8ToUTF16(server_hostname));
  if (username_textfield_ && !username.empty())
    username_textfield_->SetText(base::UTF8ToUTF16(username));
  if (group_name_textfield_ && !group_name.empty())
    group_name_textfield_->SetText(base::UTF8ToUTF16(group_name));
  if (psk_passphrase_textfield_)
    psk_passphrase_textfield_->SetShowFake(!psk_passphrase_required);
  if (user_passphrase_textfield_)
    user_passphrase_textfield_->SetShowFake(!user_passphrase_required);
  if (save_credentials_checkbox_)
    save_credentials_checkbox_->SetChecked(save_credentials);

  Refresh();
  UpdateCanLogin();
}

void VPNConfigView::ParseUIProperties(const NetworkState* vpn) {
  std::string type_dict_name =
      ProviderTypeIndexToONCDictKey(provider_type_index_);
  if (provider_type_index_ == PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK) {
    ParseVPNUIProperty(vpn, type_dict_name, ::onc::ipsec::kServerCARef,
                       &ca_cert_ui_data_);
    ParseVPNUIProperty(vpn, type_dict_name, ::onc::ipsec::kPSK,
                       &psk_passphrase_ui_data_);
    ParseVPNUIProperty(vpn, type_dict_name, ::onc::ipsec::kGroup,
                       &group_name_ui_data_);
  } else if (provider_type_index_ == PROVIDER_TYPE_INDEX_OPEN_VPN) {
    ParseVPNUIProperty(vpn, type_dict_name, ::onc::openvpn::kServerCARef,
                       &ca_cert_ui_data_);
  }
  ParseVPNUIProperty(vpn, type_dict_name, ::onc::client_cert::kClientCertRef,
                     &user_cert_ui_data_);

  const std::string credentials_dict_name(
      provider_type_index_ == PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK ?
      ::onc::vpn::kL2TP : type_dict_name);
  ParseVPNUIProperty(vpn, credentials_dict_name, ::onc::vpn::kUsername,
                     &username_ui_data_);
  ParseVPNUIProperty(vpn, credentials_dict_name, ::onc::vpn::kPassword,
                     &user_passphrase_ui_data_);
  ParseVPNUIProperty(vpn, credentials_dict_name, ::onc::vpn::kSaveCredentials,
                     &save_credentials_ui_data_);
}

void VPNConfigView::GetPropertiesError(
    const std::string& error_name,
    scoped_ptr<base::DictionaryValue> error_data) {
  NET_LOG_ERROR("Shill Error from VpnConfigView: " + error_name, "");
}

void VPNConfigView::SetConfigProperties(
    base::DictionaryValue* properties) {
  int provider_type_index = GetProviderTypeIndex();
  std::string user_passphrase = GetUserPassphrase();
  std::string user_name = GetUsername();
  std::string group_name = GetGroupName();
  switch (provider_type_index) {
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK: {
      std::string psk_passphrase = GetPSKPassphrase();
      if (!psk_passphrase.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecPskProperty, GetPSKPassphrase());
      }
      if (!group_name.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecTunnelGroupProperty, group_name);
      }
      if (!user_name.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecUserProperty, user_name);
      }
      if (!user_passphrase.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecPasswordProperty, user_passphrase);
      }
      break;
    }
    case PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT: {
      if (server_ca_cert_combobox_) {
        std::string ca_cert_pem = GetServerCACertPEM();
        base::ListValue* pem_list = new base::ListValue;
        if (!ca_cert_pem.empty())
          pem_list->AppendString(ca_cert_pem);
        properties->SetWithoutPathExpansion(shill::kL2tpIpsecCaCertPemProperty,
                                            pem_list);
      }
      SetUserCertProperties(client_cert::CONFIG_TYPE_IPSEC, properties);
      if (!group_name.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecTunnelGroupProperty, GetGroupName());
      }
      if (!user_name.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecUserProperty, user_name);
      }
      if (!user_passphrase.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kL2tpIpsecPasswordProperty, user_passphrase);
      }
      break;
    }
    case PROVIDER_TYPE_INDEX_OPEN_VPN: {
      if (server_ca_cert_combobox_) {
        std::string ca_cert_pem = GetServerCACertPEM();
        base::ListValue* pem_list = new base::ListValue;
        if (!ca_cert_pem.empty())
          pem_list->AppendString(ca_cert_pem);
        properties->SetWithoutPathExpansion(shill::kOpenVPNCaCertPemProperty,
                                            pem_list);
      }
      SetUserCertProperties(client_cert::CONFIG_TYPE_OPENVPN, properties);
      properties->SetStringWithoutPathExpansion(
          shill::kOpenVPNUserProperty, GetUsername());
      if (!user_passphrase.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kOpenVPNPasswordProperty, user_passphrase);
      }
      std::string otp = GetOTP();
      if (!otp.empty()) {
        properties->SetStringWithoutPathExpansion(
            shill::kOpenVPNOTPProperty, otp);
      }
      break;
    }
    case PROVIDER_TYPE_INDEX_MAX:
      NOTREACHED();
      break;
  }
  properties->SetBooleanWithoutPathExpansion(
      shill::kSaveCredentialsProperty, GetSaveCredentials());
}

void VPNConfigView::Refresh() {
  UpdateControls();

  // Set certificate combo boxes.
  if (server_ca_cert_combobox_) {
    server_ca_cert_combobox_->ModelChanged();
    if (enable_server_ca_cert_ && !ca_cert_pem_.empty()) {
      // Select the current server CA certificate in the combobox.
      int cert_index =
          CertLibrary::Get()->GetServerCACertIndexByPEM(ca_cert_pem_);
      if (cert_index >= 0) {
        // Skip item for "Default"
        server_ca_cert_combobox_->SetSelectedIndex(1 + cert_index);
      } else {
        server_ca_cert_combobox_->SetSelectedIndex(0);
      }
    } else {
      server_ca_cert_combobox_->SetSelectedIndex(0);
    }
  }

  if (user_cert_combobox_) {
    user_cert_combobox_->ModelChanged();
    if (enable_user_cert_ && !client_cert_id_.empty()) {
      int cert_index =
          CertLibrary::Get()->GetUserCertIndexByPkcs11Id(client_cert_id_);
      if (cert_index >= 0)
        user_cert_combobox_->SetSelectedIndex(cert_index);
      else
        user_cert_combobox_->SetSelectedIndex(0);
    } else {
      user_cert_combobox_->SetSelectedIndex(0);
    }
  }

  UpdateErrorLabel();
}

void VPNConfigView::UpdateControlsToEnable() {
  enable_psk_passphrase_ = false;
  enable_user_cert_ = false;
  enable_server_ca_cert_ = false;
  enable_otp_ = false;
  enable_group_name_ = false;
  int provider_type_index = GetProviderTypeIndex();
  if (provider_type_index == PROVIDER_TYPE_INDEX_L2TP_IPSEC_PSK) {
    enable_psk_passphrase_ = true;
    enable_group_name_ = true;
  } else if (provider_type_index == PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT) {
    enable_server_ca_cert_ = true;
    enable_user_cert_ = HaveUserCerts();
    enable_group_name_ = true;
  } else {  // PROVIDER_TYPE_INDEX_OPEN_VPN (default)
    enable_server_ca_cert_ = true;
    enable_user_cert_ = HaveUserCerts();
    enable_otp_ = true;
  }
}

void VPNConfigView::UpdateControls() {
  UpdateControlsToEnable();

  if (psk_passphrase_label_)
    psk_passphrase_label_->SetEnabled(enable_psk_passphrase_);
  if (psk_passphrase_textfield_)
    psk_passphrase_textfield_->SetEnabled(enable_psk_passphrase_ &&
                                          psk_passphrase_ui_data_.IsEditable());

  if (user_cert_label_)
    user_cert_label_->SetEnabled(enable_user_cert_);
  if (user_cert_combobox_)
    user_cert_combobox_->SetEnabled(enable_user_cert_ &&
                                    user_cert_ui_data_.IsEditable());

  if (server_ca_cert_label_)
    server_ca_cert_label_->SetEnabled(enable_server_ca_cert_);
  if (server_ca_cert_combobox_)
    server_ca_cert_combobox_->SetEnabled(enable_server_ca_cert_ &&
                                         ca_cert_ui_data_.IsEditable());

  if (otp_label_)
    otp_label_->SetEnabled(enable_otp_);
  if (otp_textfield_)
    otp_textfield_->SetEnabled(enable_otp_);

  if (group_name_label_)
    group_name_label_->SetEnabled(enable_group_name_);
  if (group_name_textfield_)
    group_name_textfield_->SetEnabled(enable_group_name_ &&
                                      group_name_ui_data_.IsEditable());
}

void VPNConfigView::UpdateErrorLabel() {
  // Error message.
  base::string16 error_msg;
  bool cert_required =
      GetProviderTypeIndex() == PROVIDER_TYPE_INDEX_L2TP_IPSEC_USER_CERT;
  if (cert_required && CertLibrary::Get()->CertificatesLoaded()) {
    if (!HaveUserCerts()) {
      error_msg = l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_PLEASE_INSTALL_USER_CERT);
    } else if (!IsUserCertValid()) {
      error_msg = l10n_util::GetStringUTF16(
          IDS_OPTIONS_SETTINGS_INTERNET_OPTIONS_REQUIRE_HARDWARE_BACKED);
    }
  }
  if (error_msg.empty() && !service_path_.empty()) {
    // TODO(kuan): differentiate between bad psk and user passphrases.
    const NetworkState* vpn = NetworkHandler::Get()->network_state_handler()->
        GetNetworkState(service_path_);
    if (vpn && vpn->connection_state() == shill::kStateFailure)
      error_msg = ash::network_connect::ErrorString(
          vpn->last_error(), vpn->path());
  }
  if (!error_msg.empty()) {
    error_label_->SetText(error_msg);
    error_label_->SetVisible(true);
  } else {
    error_label_->SetVisible(false);
  }
}

void VPNConfigView::UpdateCanLogin() {
  parent_->GetDialogClientView()->UpdateDialogButtons();
}

bool VPNConfigView::HaveUserCerts() const {
  return CertLibrary::Get()->NumCertificates(CertLibrary::CERT_TYPE_USER) > 0;
}

bool VPNConfigView::IsUserCertValid() const {
  if (!user_cert_combobox_ || !enable_user_cert_)
    return false;
  int index = user_cert_combobox_->selected_index();
  if (index < 0)
    return false;
  // Currently only hardware-backed user certificates are valid.
  if (CertLibrary::Get()->IsHardwareBacked() &&
      !CertLibrary::Get()->IsCertHardwareBackedAt(
          CertLibrary::CERT_TYPE_USER, index))
    return false;
  return true;
}

const std::string VPNConfigView::GetTextFromField(views::Textfield* textfield,
                                                  bool trim_whitespace) const {
  if (!textfield)
    return std::string();
  std::string untrimmed = base::UTF16ToUTF8(textfield->text());
  if (!trim_whitespace)
    return untrimmed;
  std::string result;
  base::TrimWhitespaceASCII(untrimmed, base::TRIM_ALL, &result);
  return result;
}

const std::string VPNConfigView::GetPassphraseFromField(
    PassphraseTextfield* textfield) const {
  if (!textfield)
    return std::string();
  return textfield->GetPassphrase();
}

void VPNConfigView::ParseVPNUIProperty(
    const NetworkState* network,
    const std::string& dict_key,
    const std::string& key,
    NetworkPropertyUIData* property_ui_data) {
  ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_NONE;
  const base::DictionaryValue* onc =
      onc::FindPolicyForActiveUser(network->guid(), &onc_source);

  VLOG_IF(1, !onc) << "No ONC found for VPN network " << network->guid();
  property_ui_data->ParseOncProperty(
      onc_source,
      onc,
      base::StringPrintf("%s.%s.%s",
                         ::onc::network_config::kVPN,
                         dict_key.c_str(),
                         key.c_str()));
}

}  // namespace chromeos