// Copyright 2016 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 "components/autofill/core/browser/country_names.h"

#include <stdint.h>

#include <memory>

#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/singleton.h"
#include "base/stl_util.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/country_data.h"
#include "components/autofill/core/common/autofill_l10n_util.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "ui/base/l10n/l10n_util.h"

namespace autofill {
namespace {

// A copy of the application locale string, which should be ready for
// CountryName's construction.
static base::LazyInstance<std::string> g_application_locale =
    LAZY_INSTANCE_INITIALIZER;

// Returns the ICU sort key corresponding to |str| for the given |collator|.
// Uses |buffer| as temporary storage, and might resize |buffer| as a side-
// effect. |buffer_size| should specify the |buffer|'s size, and is updated if
// the |buffer| is resized.
const std::string GetSortKey(const icu::Collator& collator,
                             const base::string16& str,
                             std::unique_ptr<uint8_t[]>* buffer,
                             int32_t* buffer_size) {
  DCHECK(buffer);
  DCHECK(buffer_size);

  icu::UnicodeString icu_str(str.c_str(), str.length());
  int32_t expected_size =
      collator.getSortKey(icu_str, buffer->get(), *buffer_size);
  if (expected_size > *buffer_size) {
    // If there wasn't enough space, grow the buffer and try again.
    *buffer_size = expected_size;
    buffer->reset(new uint8_t[*buffer_size]);
    DCHECK(buffer->get());

    expected_size = collator.getSortKey(icu_str, buffer->get(), *buffer_size);
    DCHECK_EQ(*buffer_size, expected_size);
  }

  return std::string(reinterpret_cast<const char*>(buffer->get()));
}

// Computes the value for CountryNames::common_names_.
std::map<std::string, std::string> GetCommonNames() {
  std::map<std::string, std::string> common_names;
  // Add 2- and 3-letter ISO country codes.
  for (const std::string& country_code :
       CountryDataMap::GetInstance()->country_codes()) {
    common_names.insert(std::make_pair(country_code, country_code));

    std::string iso3_country_code =
        icu::Locale(nullptr, country_code.c_str()).getISO3Country();

    // ICU list of countries can be out-of-date with CLDR.
    if (!iso3_country_code.empty())
      common_names.insert(std::make_pair(iso3_country_code, country_code));
  }

  // Add a few other common synonyms.
  common_names.insert(std::make_pair("UNITED STATES OF AMERICA", "US"));
  common_names.insert(std::make_pair("U.S.A.", "US"));
  common_names.insert(std::make_pair("GREAT BRITAIN", "GB"));
  common_names.insert(std::make_pair("UK", "GB"));
  common_names.insert(std::make_pair("BRASIL", "BR"));
  common_names.insert(std::make_pair("DEUTSCHLAND", "DE"));
  return common_names;
}

// Creates collator for |locale| and sets its attributes as needed.
std::unique_ptr<icu::Collator> CreateCollator(const icu::Locale& locale) {
  std::unique_ptr<icu::Collator> collator(
      autofill::l10n::GetCollatorForLocale(locale));
  if (!collator)
    return nullptr;

  // Compare case-insensitively and ignoring punctuation.
  UErrorCode ignored = U_ZERO_ERROR;
  collator->setAttribute(UCOL_STRENGTH, UCOL_SECONDARY, ignored);
  ignored = U_ZERO_ERROR;
  collator->setAttribute(UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, ignored);

  return collator;
}

// If |locale| is different from "en_US", returns a collator for "en_US" and
// sets its attributes as appropriate. Otherwise returns null.
std::unique_ptr<icu::Collator> CreateDefaultCollator(
    const icu::Locale& locale) {
  icu::Locale default_locale("en_US");

  if (default_locale != locale)
    return CreateCollator(default_locale);

  return nullptr;
}

// Returns the mapping of country names localized to |locale| to their
// corresponding country codes. The provided |collator| should be suitable for
// the locale. The collator being null is handled gracefully by returning an
// empty map, to account for the very rare cases when the collator fails to
// initialize.
std::map<std::string, std::string> GetLocalizedNames(
    const std::string& locale,
    const icu::Collator* collator) {
  if (!collator)
    return std::map<std::string, std::string>();

  std::map<std::string, std::string> localized_names;
  int32_t buffer_size = 1000;
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);

  for (const std::string& country_code :
       CountryDataMap::GetInstance()->country_codes()) {
    base::string16 country_name =
        l10n_util::GetDisplayNameForCountry(country_code, locale);
    std::string sort_key =
        GetSortKey(*collator, country_name, &buffer, &buffer_size);

    localized_names.insert(std::make_pair(sort_key, country_code));
  }
  return localized_names;
}

}  // namespace

// static
CountryNames* CountryNames::GetInstance() {
  return base::Singleton<CountryNames>::get();
}

// static
void CountryNames::SetLocaleString(const std::string& locale) {
  DCHECK(!locale.empty());
  // Application locale should never be empty. The empty value of
  // |g_application_locale| means that it has not been initialized yet.
  std::string* storage = g_application_locale.Pointer();
  if (storage->empty()) {
    *storage = locale;
  }
  // TODO(crbug.com/579971) CountryNames currently cannot adapt to changed
  // locale without Chrome's restart.
}

CountryNames::CountryNames(const std::string& locale_name)
    : locale_(locale_name.c_str()),
      collator_(CreateCollator(locale_)),
      default_collator_(CreateDefaultCollator(locale_)),
      common_names_(GetCommonNames()),
      localized_names_(GetLocalizedNames(locale_name, collator_.get())),
      default_localized_names_(
          GetLocalizedNames("en_US", default_collator_.get())) {}

CountryNames::CountryNames() : CountryNames(g_application_locale.Get()) {
  DCHECK(!g_application_locale.Get().empty());
}

CountryNames::~CountryNames() = default;

const std::string CountryNames::GetCountryCode(const base::string16& country) {
  // First, check common country names, including 2- and 3-letter country codes.
  std::string country_utf8 = base::UTF16ToUTF8(base::ToUpperASCII(country));
  const auto result = common_names_.find(country_utf8);
  if (result != common_names_.end())
    return result->second;

  // Next, check country names localized to the current locale.
  std::string country_code =
      GetCountryCodeForLocalizedName(country, localized_names_, *collator_);
  if (!country_code.empty())
    return country_code;

  // Finally, check country names localized to US English, unless done already.
  if (default_collator_) {
    return GetCountryCodeForLocalizedName(country, default_localized_names_,
                                          *default_collator_);
  }

  return std::string();
}

const std::string CountryNames::GetCountryCodeForLocalizedName(
    const base::string16& country_name,
    const std::map<std::string, std::string>& localized_names,
    const icu::Collator& collator) {
  // As recommended[1] by ICU, initialize the buffer size to four times the
  // source string length.
  // [1] http://userguide.icu-project.org/collation/api#TOC-Examples
  int32_t buffer_size = country_name.size() * 4;
  std::unique_ptr<uint8_t[]> buffer(new uint8_t[buffer_size]);
  std::string sort_key =
      GetSortKey(collator, country_name, &buffer, &buffer_size);

  auto result = localized_names.find(sort_key);

  if (result != localized_names.end())
    return result->second;

  return std::string();
}

}  // namespace autofill
