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

#include "base/command_line.h"
#include "base/i18n/number_formatting.h"
#include "base/i18n/time_formatting.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/options/options_window.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/net/gaia/google_service_auth_error.h"
#include "chrome/common/url_constants.h"
#include "grit/browser_resources.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"

typedef GoogleServiceAuthError AuthError;

namespace sync_ui_util {

namespace {

// Given an authentication state, this helper function returns the appropriate
// status message and, if necessary, the text that should appear in the
// re-login link.
void GetStatusLabelsForAuthError(const AuthError& auth_error,
    ProfileSyncService* service, string16* status_label,
    string16* link_label) {
  if (link_label)
    link_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL));
  if (auth_error.state() == AuthError::INVALID_GAIA_CREDENTIALS ||
      auth_error.state() == AuthError::ACCOUNT_DELETED ||
      auth_error.state() == AuthError::ACCOUNT_DISABLED) {
    // If the user name is empty then the first login failed, otherwise the
    // credentials are out-of-date.
    if (service->GetAuthenticatedUsername().empty())
      status_label->assign(
          l10n_util::GetStringUTF16(IDS_SYNC_INVALID_USER_CREDENTIALS));
    else
      status_label->assign(
          l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_INFO_OUT_OF_DATE));
  } else if (auth_error.state() == AuthError::SERVICE_UNAVAILABLE) {
    DCHECK(service->GetAuthenticatedUsername().empty());
    status_label->assign(
        l10n_util::GetStringUTF16(IDS_SYNC_SERVICE_UNAVAILABLE));
  } else if (auth_error.state() == AuthError::CONNECTION_FAILED) {
    // Note that there is little the user can do if the server is not
    // reachable. Since attempting to re-connect is done automatically by
    // the Syncer, we do not show the (re)login link.
    status_label->assign(
        l10n_util::GetStringFUTF16(IDS_SYNC_SERVER_IS_UNREACHABLE,
                              l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
    if (link_label)
      link_label->clear();
  } else {
    status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_ERROR_SIGNING_IN));
  }
}

// Returns the message that should be displayed when the user is authenticated
// and can connect to the sync server. If the user hasn't yet authenticated, an
// empty string is returned.
string16 GetSyncedStateStatusLabel(ProfileSyncService* service) {
  string16 label;
  string16 user_name(service->GetAuthenticatedUsername());
  if (user_name.empty())
    return label;

  const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess();
  return l10n_util::GetStringFUTF16(
      browser_command_line.HasSwitch(switches::kMultiProfiles) ?
          IDS_PROFILES_SYNCED_TO_USER_WITH_TIME :
          IDS_SYNC_ACCOUNT_SYNCED_TO_USER_WITH_TIME,
      user_name,
      service->GetLastSyncedTimeString());
}

// TODO(akalin): Write unit tests for these three functions below.

// status_label and link_label must either be both NULL or both non-NULL.
MessageType GetStatusInfo(ProfileSyncService* service,
                          string16* status_label,
                          string16* link_label) {
  DCHECK_EQ(status_label == NULL, link_label == NULL);

  MessageType result_type(SYNCED);

  if (!service) {
    return PRE_SYNCED;
  }

  if (service->HasSyncSetupCompleted()) {
    ProfileSyncService::Status status(service->QueryDetailedSyncStatus());
    const AuthError& auth_error = service->GetAuthError();

    // Either show auth error information with a link to re-login, auth in prog,
    // or note that everything is OK with the last synced time.
    if (status.authenticated && !service->observed_passphrase_required()) {
      // Everything is peachy.
      if (status_label) {
        status_label->assign(GetSyncedStateStatusLabel(service));
      }
      DCHECK_EQ(auth_error.state(), AuthError::NONE);
    } else if (service->UIShouldDepictAuthInProgress()) {
      if (status_label) {
        status_label->assign(
          l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
      }
      result_type = PRE_SYNCED;
    } else if (service->observed_passphrase_required()) {
      if (service->passphrase_required_for_decryption()) {
        // NOT first machine.
        // Show a link ("needs attention"), but still indicate the
        // current synced status.  Return SYNC_PROMO so that
        // the configure link will still be shown.
        if (status_label && link_label) {
          status_label->assign(GetSyncedStateStatusLabel(service));
          link_label->assign(
              l10n_util::GetStringUTF16(IDS_SYNC_PASSWORD_SYNC_ATTENTION));
        }
        result_type = SYNC_PROMO;
      } else {
        // First machine.  Don't show promotion, just show everything
        // normal.
        if (status_label)
          status_label->assign(GetSyncedStateStatusLabel(service));
      }
    } else if (auth_error.state() != AuthError::NONE) {
      if (status_label && link_label) {
        GetStatusLabelsForAuthError(auth_error, service,
                                    status_label, link_label);
      }
      result_type = SYNC_ERROR;
    }
  } else {
    // Either show auth error information with a link to re-login, auth in prog,
    // or provide a link to continue with setup.
    result_type = PRE_SYNCED;
    if (service->SetupInProgress()) {
      ProfileSyncService::Status status(service->QueryDetailedSyncStatus());
      const AuthError& auth_error = service->GetAuthError();
      if (status_label) {
        status_label->assign(
            l10n_util::GetStringUTF16(IDS_SYNC_NTP_SETUP_IN_PROGRESS));
      }
      if (service->UIShouldDepictAuthInProgress()) {
        if (status_label) {
          status_label->assign(
              l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL));
        }
      } else if (auth_error.state() != AuthError::NONE) {
        if (status_label) {
          status_label->clear();
          GetStatusLabelsForAuthError(auth_error, service, status_label, NULL);
        }
        result_type = SYNC_ERROR;
      } else if (!status.authenticated) {
        if (status_label) {
          status_label->assign(
              l10n_util::GetStringUTF16(IDS_SYNC_ACCOUNT_DETAILS_NOT_ENTERED));
        }
      }
    } else if (service->unrecoverable_error_detected()) {
      result_type = SYNC_ERROR;
      if (status_label) {
        status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_SETUP_ERROR));
      }
    }
  }
  return result_type;
}

// Returns the status info for use on the new tab page, where we want slightly
// different information than in the settings panel.
MessageType GetStatusInfoForNewTabPage(ProfileSyncService* service,
                                       string16* status_label,
                                       string16* link_label) {
  DCHECK(status_label);
  DCHECK(link_label);

  if (service->HasSyncSetupCompleted() &&
      service->observed_passphrase_required()) {
    if (!service->passphrase_required_for_decryption()) {
      // First machine migrating to passwords.  Show as a promotion.
      if (status_label && link_label) {
        status_label->assign(
            l10n_util::GetStringFUTF16(
                IDS_SYNC_NTP_PASSWORD_PROMO,
                l10n_util::GetStringUTF16(IDS_PRODUCT_NAME)));
        link_label->assign(
            l10n_util::GetStringUTF16(IDS_SYNC_NTP_PASSWORD_ENABLE));
      }
      return SYNC_PROMO;
    } else {
      // NOT first machine.
      // Show a link and present as an error ("needs attention").
      if (status_label && link_label) {
        status_label->assign(string16());
        link_label->assign(
            l10n_util::GetStringUTF16(IDS_SYNC_CONFIGURE_ENCRYPTION));
      }
      return SYNC_ERROR;
    }
  }

  // Fallback to default.
  return GetStatusInfo(service, status_label, link_label);
}

}  // namespace

MessageType GetStatusLabels(ProfileSyncService* service,
                            string16* status_label,
                            string16* link_label) {
  DCHECK(status_label);
  DCHECK(link_label);
  return sync_ui_util::GetStatusInfo(service, status_label, link_label);
}

MessageType GetStatusLabelsForNewTabPage(ProfileSyncService* service,
                                         string16* status_label,
                                         string16* link_label) {
  DCHECK(status_label);
  DCHECK(link_label);
  return sync_ui_util::GetStatusInfoForNewTabPage(
      service, status_label, link_label);
}

MessageType GetStatus(ProfileSyncService* service) {
  return sync_ui_util::GetStatusInfo(service, NULL, NULL);
}

bool ShouldShowSyncErrorButton(ProfileSyncService* service) {
  return service &&
         ((!service->IsManaged() &&
           service->HasSyncSetupCompleted()) &&
         (GetStatus(service) == sync_ui_util::SYNC_ERROR ||
          service->observed_passphrase_required()));
}

string16 GetSyncMenuLabel(ProfileSyncService* service) {
  MessageType type = GetStatus(service);

  if (type == sync_ui_util::SYNCED)
    return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNCED_LABEL);
  else if (type == sync_ui_util::SYNC_ERROR)
    return l10n_util::GetStringUTF16(IDS_SYNC_MENU_SYNC_ERROR_LABEL);
  else
    return l10n_util::GetStringUTF16(IDS_SYNC_START_SYNC_BUTTON_LABEL);
}

void OpenSyncMyBookmarksDialog(Profile* profile,
                               Browser* browser,
                               ProfileSyncService::SyncEventCodes code) {
  ProfileSyncService* service =
    profile->GetOriginalProfile()->GetProfileSyncService();
  if (!service || !service->IsSyncEnabled()) {
    LOG(DFATAL) << "OpenSyncMyBookmarksDialog called with sync disabled";
    return;
  }

  if (service->HasSyncSetupCompleted()) {
    bool create_window = browser == NULL;
    if (create_window)
      browser = Browser::Create(profile);
    browser->ShowOptionsTab(chrome::kPersonalOptionsSubPage);
    if (create_window)
      browser->window()->Show();
  } else {
    service->ShowLoginDialog(NULL);
    ProfileSyncService::SyncEvent(code);  // UMA stats
  }
}

void AddBoolSyncDetail(ListValue* details,
                       const std::string& stat_name,
                       bool stat_value) {
  DictionaryValue* val = new DictionaryValue;
  val->SetString("stat_name", stat_name);
  val->SetBoolean("stat_value", stat_value);
  details->Append(val);
}

void AddIntSyncDetail(ListValue* details, const std::string& stat_name,
                      int64 stat_value) {
  DictionaryValue* val = new DictionaryValue;
  val->SetString("stat_name", stat_name);
  val->SetString("stat_value", base::FormatNumber(stat_value));
  details->Append(val);
}

string16 ConstructTime(int64 time_in_int) {
  base::Time time = base::Time::FromInternalValue(time_in_int);

  // If time is null the format function returns a time in 1969.
  if (time.is_null())
    return string16();
  return base::TimeFormatFriendlyDateAndTime(time);
}

std::string MakeSyncAuthErrorText(
    const GoogleServiceAuthError::State& state) {
  switch (state) {
    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
    case GoogleServiceAuthError::ACCOUNT_DELETED:
    case GoogleServiceAuthError::ACCOUNT_DISABLED:
    case GoogleServiceAuthError::SERVICE_UNAVAILABLE:
      return "INVALID_GAIA_CREDENTIALS";
    case GoogleServiceAuthError::USER_NOT_SIGNED_UP:
      return "USER_NOT_SIGNED_UP";
    case GoogleServiceAuthError::CONNECTION_FAILED:
      return "CONNECTION_FAILED";
    default:
      return std::string();
  }
}

void ConstructAboutInformation(ProfileSyncService* service,
                               DictionaryValue* strings) {
  CHECK(strings);
  if (!service || !service->HasSyncSetupCompleted()) {
    strings->SetString("summary", "SYNC DISABLED");
  } else {
    sync_api::SyncManager::Status full_status(
        service->QueryDetailedSyncStatus());

    strings->SetString("service_url", service->sync_service_url().spec());
    strings->SetString("summary",
                       ProfileSyncService::BuildSyncStatusSummaryText(
                       full_status.summary));

    strings->Set("authenticated",
                 new FundamentalValue(full_status.authenticated));
    strings->SetString("auth_problem",
                       sync_ui_util::MakeSyncAuthErrorText(
                       service->GetAuthError().state()));

    strings->SetString("time_since_sync", service->GetLastSyncedTimeString());

    ListValue* details = new ListValue();
    strings->Set("details", details);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Server Up",
                                    full_status.server_up);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Server Reachable",
                                    full_status.server_reachable);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Server Broken",
                                    full_status.server_broken);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Notifications Enabled",
                                    full_status.notifications_enabled);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Notifications Received",
                                   full_status.notifications_received);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Notifications Sent",
                                   full_status.notifications_sent);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Unsynced Count",
                                   full_status.unsynced_count);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Conflicting Count",
                                   full_status.conflicting_count);
    sync_ui_util::AddBoolSyncDetail(details, "Syncing", full_status.syncing);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Initial Sync Ended",
                                    full_status.initial_sync_ended);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Syncer Stuck",
                                    full_status.syncer_stuck);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Updates Available",
                                   full_status.updates_available);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Updates Downloaded (All)",
                                   full_status.updates_received);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Updates Downloaded (Tombstones)",
                                   full_status.tombstone_updates_received);
    sync_ui_util::AddBoolSyncDetail(details,
                                    "Disk Full",
                                    full_status.disk_full);
    sync_ui_util::AddIntSyncDetail(details,
                                   "Max Consecutive Errors",
                                   full_status.max_consecutive_errors);

    if (service->unrecoverable_error_detected()) {
      strings->Set("unrecoverable_error_detected", new FundamentalValue(true));
      strings->SetString("unrecoverable_error_message",
                         service->unrecoverable_error_message());
      tracked_objects::Location loc(service->unrecoverable_error_location());
      std::string location_str;
      loc.Write(true, true, &location_str);
      strings->SetString("unrecoverable_error_location", location_str);
    } else if (!service->sync_initialized()) {
      strings->SetString("summary", "Sync not yet initialized");
    } else {
      browser_sync::ModelSafeRoutingInfo routes;
      service->GetModelSafeRoutingInfo(&routes);
      ListValue* routing_info = new ListValue();
      strings->Set("routing_info", routing_info);
      browser_sync::ModelSafeRoutingInfo::const_iterator it = routes.begin();
      for (; it != routes.end(); ++it) {
        DictionaryValue* val = new DictionaryValue;
        val->SetString("model_type", ModelTypeToString(it->first));
        val->SetString("group", ModelSafeGroupToString(it->second));
        routing_info->Append(val);
      }

      sync_ui_util::AddBoolSyncDetail(details,
          "Autofill Migrated",
          service->GetAutofillMigrationState() ==
          syncable::MIGRATED);
      syncable::AutofillMigrationDebugInfo info =
          service->GetAutofillMigrationDebugInfo();

      sync_ui_util::AddIntSyncDetail(details,
                                     "Bookmarks created during migration",
                                     info.bookmarks_added_during_migration);
      sync_ui_util::AddIntSyncDetail(details,
          "Autofill entries created during migration",
          info.autofill_entries_added_during_migration);
      sync_ui_util::AddIntSyncDetail(details,
          "Autofill Profiles created during migration",
          info.autofill_profile_added_during_migration);

      DictionaryValue* val = new DictionaryValue;
      val->SetString("stat_name", "Autofill Migration Time");
      val->SetString("stat_value", ConstructTime(info.autofill_migration_time));
      details->Append(val);
    }
  }
}

}  // namespace sync_ui_util