// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/soda/constants.h"

#include <optional>
#include <string>

#include "base/containers/fixed_flat_map.h"
#include "base/containers/flat_map.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/i18n/legacy_language_tag_helpers.h"
#include "base/metrics/field_trial_params.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "components/component_updater/component_updater_paths.h"
#include "components/crx_file/id_util.h"
#include "components/language/core/browser/pref_names.h"
#include "components/prefs/pref_service.h"
#include "media/base/media_switches.h"
#include "ui/base/l10n/l10n_util.h"

namespace speech {
namespace {
constexpr auto kChineseLocaleMap =
    base::MakeFixedFlatMap<std::string_view, std::string_view>(
        {{"cmn-hans-cn", "cmn-Hans-CN"},
         {"cmn-hant-tw", "cmn-Hant-TW"},
         {"zh-cn", "cmn-Hans-CN"},
         {"zh-hans-cn", "cmn-Hans-CN"},
         {"zh-hant-tw", "cmn-Hant-TW"},
         {"zh-tw", "cmn-Hant-TW"}});

constexpr auto kSodaLanguageToBcp47Map =
    base::MakeFixedFlatMap<std::string_view, std::string_view>({
        {"cmn-hans-cn", "zh"}, {"cmn-hant-tw", "zh-Hant"}, {"da-dk", "da-DK"},
        {"de-be", "de-BE"},    {"de-ch", "de-CH"},         {"de-de", "de-DE"},
        {"en-au", "en-AU"},    {"en-gb", "en-GB"},         {"en-ie", "en-IE"},
        {"en-in", "en-IN"},    {"en-sg", "en-SG"},         {"en-us", "en-US"},
        {"es-es", "es-ES"},    {"es-us", "es-US"},         {"fr-be", "fr-BE"},
        {"fr-ca", "fr-CA"},    {"fr-ch", "fr-CH"},         {"fr-fr", "fr-FR"},
        {"hi-in", "hi-IN"},    {"id-id", "id-ID"},         {"it-it", "it-IT"},
        {"ja-jp", "ja-JP"},    {"ko-kr", "ko-KR"},         {"nb-no", "nb-NO"},
        {"nl-nl", "nl-NL"},    {"pl-pl", "pl-PL"},         {"pt-br", "pt-BR"},
        {"ru-ru", "ru-RU"},    {"sv-se", "sv-SE"},         {"th-th", "th-TH"},
        {"tr-tr", "tr-TR"},    {"vi-vn", "vi-VN"},
    });

}  // namespace

// If `language_name` is Chinese variant, then return the master locale.
// Otherwise, return `language_name`.
const std::string MaybeMapToChineseLocale(std::string_view language_name) {
  auto chinese_locale =
      kChineseLocaleMap.find(base::ToLowerASCII(language_name));
  if (chinese_locale != kChineseLocaleMap.end()) {
    return std::string(chinese_locale->second);
  }

  return std::string(language_name);
}

std::optional<std::string> GetBCP47LanguageCodeFromSodaLanguage(
    std::string_view soda_language) {
  auto it = kSodaLanguageToBcp47Map.find(base::ToLowerASCII(soda_language));
  if (it != kSodaLanguageToBcp47Map.end()) {
    return std::string(it->second);
  }
  return std::nullopt;
}

const char kUsEnglishLocale[] = "en-US";

const char kEnglishLocaleNoCountry[] = "en";
const char kChineseLocaleNoCountry[] = "cmn";

const char kSodaPreemptiveDownloadStarted[] =
    "SodaInstaller.PreemptiveDownloadStarted";

const char kSodaBinaryInstallationResult[] =
    "SodaInstaller.BinaryInstallationResult";

const char kSodaBinaryInstallationSuccessTimeTaken[] =
    "SodaInstaller.BinaryInstallationSuccessTime";

const char kSodaBinaryInstallationFailureTimeTaken[] =
    "SodaInstaller.BinaryInstallationFailureTime";

#if BUILDFLAG(IS_WIN)
constexpr base::FilePath::CharType kSodaBinaryRelativePath[] =
    FILE_PATH_LITERAL("SODAFiles/SODA.dll");
#else
constexpr base::FilePath::CharType kSodaBinaryRelativePath[] =
    FILE_PATH_LITERAL("SODAFiles/libsoda.so");
#endif

constexpr base::FilePath::CharType kSodaTestBinaryRelativePath[] =
    FILE_PATH_LITERAL("libsoda.so");

constexpr base::FilePath::CharType kSodaTestResourcesRelativePath[] =
    FILE_PATH_LITERAL("third_party/soda/resources/");

constexpr base::FilePath::CharType kSodaInstallationRelativePath[] =
    FILE_PATH_LITERAL("SODA");

constexpr base::FilePath::CharType kSodaLanguagePacksRelativePath[] =
    FILE_PATH_LITERAL("SODALanguagePacks");

constexpr base::FilePath::CharType kSodaLanguagePackDirectoryRelativePath[] =
    FILE_PATH_LITERAL("SODAModels");

const base::FilePath GetSodaDirectory() {
  base::FilePath components_dir;
  base::PathService::Get(component_updater::DIR_COMPONENT_USER,
                         &components_dir);

  return components_dir.empty()
             ? base::FilePath()
             : components_dir.Append(kSodaInstallationRelativePath);
}

const base::FilePath GetSodaLanguagePacksDirectory() {
  base::FilePath components_dir;
  base::PathService::Get(component_updater::DIR_COMPONENT_USER,
                         &components_dir);

  return components_dir.empty()
             ? base::FilePath()
             : components_dir.Append(kSodaLanguagePacksRelativePath);
}

const base::FilePath GetSodaTestResourcesDirectory() {
  base::FilePath test_data_root;
  base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &test_data_root);
  DCHECK(!test_data_root.empty());
  return test_data_root.empty()
             ? base::FilePath()
             : test_data_root.Append(kSodaTestResourcesRelativePath);
}

const base::FilePath GetLatestSodaLanguagePackDirectory(
    std::string_view language) {
  base::FileEnumerator enumerator(
      GetSodaLanguagePacksDirectory().AppendASCII(language), false,
      base::FileEnumerator::DIRECTORIES);

  // Use the lexographical order of the directory names to determine the latest
  // version. This mirrors the logic in the component updater.
  base::FilePath latest_version_dir;
  for (base::FilePath version_dir = enumerator.Next(); !version_dir.empty();
       version_dir = enumerator.Next()) {
    latest_version_dir =
        latest_version_dir < version_dir ? version_dir : latest_version_dir;
  }

  return latest_version_dir.Append(kSodaLanguagePackDirectoryRelativePath);
}

const base::FilePath GetLatestSodaDirectory() {
  base::FileEnumerator enumerator(GetSodaDirectory(), false,
                                  base::FileEnumerator::DIRECTORIES);
  base::FilePath latest_version_dir;
  for (base::FilePath version_dir = enumerator.Next(); !version_dir.empty();
       version_dir = enumerator.Next()) {
    latest_version_dir =
        latest_version_dir < version_dir ? version_dir : latest_version_dir;
  }

  return latest_version_dir;
}

const base::FilePath GetSodaBinaryPath() {
  base::FilePath soda_dir = GetLatestSodaDirectory();
  return soda_dir.empty() ? base::FilePath()
                          : soda_dir.Append(kSodaBinaryRelativePath);
}

const base::FilePath GetSodaTestBinaryPath() {
  base::FilePath test_dir = GetSodaTestResourcesDirectory();
  return test_dir.empty() ? base::FilePath()
                          : test_dir.Append(kSodaTestBinaryRelativePath);
}

std::optional<SodaLanguagePackComponentConfig> GetLanguageComponentConfig(
    LanguageCode language_code) {
  for (const SodaLanguagePackComponentConfig& config :
       kLanguageComponentConfigs) {
    if (config.language_code == language_code) {
      return config;
    }
  }

  return std::nullopt;
}

std::optional<SodaLanguagePackComponentConfig> GetLanguageComponentConfig(
    std::string_view language_name) {
  auto locale = MaybeMapToChineseLocale(language_name);
  for (const SodaLanguagePackComponentConfig& config :
       kLanguageComponentConfigs) {
    if (base::EqualsCaseInsensitiveASCII(config.language_name, locale)) {
      return config;
    }
  }

  return std::nullopt;
}

std::optional<SodaLanguagePackComponentConfig>
GetLanguageComponentConfigMatchingLanguageSubtag(
    std::string_view language_name) {
  // Use full locale to get Chinese variant config.
  auto locale = MaybeMapToChineseLocale(language_name);
  if (locale.substr(0, 3) == kChineseLocaleNoCountry) {
    return GetLanguageComponentConfig(locale);
  }

  for (const SodaLanguagePackComponentConfig& config :
       kLanguageComponentConfigs) {
    if (base::i18n::GetLanguageSubtagUsingLanguageTag(
            base::ToLowerASCII(config.language_name)) ==
        base::i18n::GetLanguageSubtagUsingLanguageTag(
            base::ToLowerASCII(language_name))) {
      return config;
    }
  }

  return std::nullopt;
}

LanguageCode GetLanguageCodeByComponentId(std::string_view component_id) {
  for (const SodaLanguagePackComponentConfig& config :
       kLanguageComponentConfigs) {
    if (crx_file::id_util::GenerateIdFromHash(config.public_key_sha) ==
        component_id) {
      return config.language_code;
    }
  }

  return LanguageCode::kNone;
}

std::string GetLanguageName(LanguageCode language_code) {
  std::string language_name;
  if (language_code != LanguageCode::kNone) {
    std::optional<SodaLanguagePackComponentConfig> language_config =
        GetLanguageComponentConfig(language_code);
    if (language_config.has_value()) {
      language_name = language_config.value().language_name;
    }
  }

  return language_name;
}

LanguageCode GetLanguageCode(std::string_view language_name) {
  std::optional<SodaLanguagePackComponentConfig> language_config =
      GetLanguageComponentConfig(language_name);
  if (language_config.has_value()) {
    return language_config.value().language_code;
  }
  return LanguageCode::kNone;
}

const std::u16string GetLanguageDisplayName(std::string_view language_name,
                                            std::string_view display_locale) {
  if (language_name.substr(0, 3) == kChineseLocaleNoCountry) {
    return l10n_util::GetDisplayNameForLocale(language_name.substr(0, 8),
                                              display_locale, true);

  } else {
    return l10n_util::GetDisplayNameForLocaleWithoutCountry(
        language_name, display_locale, true);
  }
}

const std::string GetInstallationSuccessTimeMetricForLanguagePack(
    const LanguageCode& language_code) {
  auto config = GetLanguageComponentConfig(language_code);
  DCHECK(config && !config->language_name.empty());
  return GetInstallationSuccessTimeMetricForLanguage(config->language_name);
}
const std::string GetInstallationSuccessTimeMetricForLanguage(
    std::string_view language) {
  return base::StrCat(
      {"SodaInstaller.Language.", language, ".InstallationSuccessTime"});
}

const std::string GetInstallationFailureTimeMetricForLanguagePack(
    const LanguageCode& language_code) {
  auto config = GetLanguageComponentConfig(language_code);
  DCHECK(config && !config->language_name.empty());
  return GetInstallationFailureTimeMetricForLanguage(config->language_name);
}

const std::string GetInstallationFailureTimeMetricForLanguage(
    std::string_view language) {
  return base::StrCat(
      {"SodaInstaller.Language.", language, ".InstallationFailureTime"});
}

const std::string GetInstallationResultMetricForLanguagePack(
    const LanguageCode& language_code) {
  auto config = GetLanguageComponentConfig(language_code);
  DCHECK(config && !config->language_name.empty());
  return speech::GetInstallationResultMetricForLanguage(config->language_name);
}

const std::string GetInstallationResultMetricForLanguage(
    std::string_view language) {
  return base::StrCat(
      {"SodaInstaller.Language.", language, ".InstallationResult"});
}

const std::string GetUninstalledDueToExpirationMetricForLanguage(
    std::string_view language) {
  return base::StrCat(
      {"SodaInstaller.Language.", language, ".UninstalledDueToExpiration"});
}

const std::string GetRedownloadedAfterExpirationMetricForLanguage(
    std::string_view language) {
  return base::StrCat(
      {"SodaInstaller.Language.", language, ".RedownloadedAfterExpiration"});
}

std::string_view GetDefaultLiveCaptionLanguage(
    std::string_view application_locale,
    const PrefService& profile_prefs) {
  std::optional<SodaLanguagePackComponentConfig> application_locale_config =
      GetLanguageComponentConfigMatchingLanguageSubtag(application_locale);

  if (application_locale_config.has_value() &&
      application_locale_config.value().language_code != LanguageCode::kNone) {
    return application_locale_config.value().language_name;
  }

  std::string accept_languages_pref =
      profile_prefs.GetString(language::prefs::kAcceptLanguages);
  for (std::string language :
       base::SplitString(accept_languages_pref, ",", base::TRIM_WHITESPACE,
                         base::SPLIT_WANT_NONEMPTY)) {
    std::optional<SodaLanguagePackComponentConfig> config =
        GetLanguageComponentConfigMatchingLanguageSubtag(language);
    if (config.has_value() &&
        config.value().language_code != LanguageCode::kNone) {
      return config.value().language_name;
    }
  }

  return kUsEnglishLocale;
}

}  // namespace speech
