/*
 *  Copyright (C) 2005-2018 Team Kodi
 *  This file is part of Kodi - https://kodi.tv
 *
 *  SPDX-License-Identifier: GPL-2.0-or-later
 *  See LICENSES/README.md for more information.
 */

#include "LangInfo.h"

#include "DatabaseManager.h"
#include "ServiceBroker.h"
#include "XBDateTime.h"
#include "addons/AddonInstaller.h"
#include "addons/AddonManager.h"
#include "addons/LanguageResource.h"
#include "addons/RepositoryUpdater.h"
#include "addons/addoninfo/AddonType.h"
#include "messaging/ApplicationMessenger.h"
#include "pvr/PVRManager.h"
#include "resources/LocalizeStrings.h"
#include "resources/ResourcesComponent.h"
#include "settings/AdvancedSettings.h"
#include "settings/Settings.h"
#include "settings/SettingsComponent.h"
#include "settings/lib/Setting.h"
#include "settings/lib/SettingDefinitions.h"
#include "utils/CharsetConverter.h"
#include "utils/LangCodeExpander.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/XBMCTinyXML2.h"
#include "utils/XMLUtils.h"
#include "utils/i18n/Iso3166_1.h"
#include "utils/log.h"
#include "weather/WeatherManager.h"

#include <algorithm>
#include <array>
#include <span>

namespace
{
std::string GetDateStringWithFormat(const CDateTime& date, const std::string& format)
{
  // Return the formatted date together with the format used.
  // eg: '1/02/2003 (D/MM/YYYY)'
  return date.GetAsLocalizedDate(format) + " (" + format + ")";
}
} // namespace

using namespace KODI::LANGINFO;
using namespace KODI::UTILS;
using namespace KODI::UTILS::I18N;

static std::string shortDateFormats[] = {
    // clang-format off
  // short date formats using "/"
  "DD/MM/YYYY",
  "MM/DD/YYYY",
  "YYYY/MM/DD",
  "D/M/YYYY",
  // short date formats using "-"
  "DD-MM-YYYY",
  "MM-DD-YYYY",
  "YYYY-MM-DD",
  "YYYY-M-D",
  // short date formats using "."
  "DD.MM.YYYY",
  "DD.M.YYYY",
  "D.M.YYYY",
  "D. M. YYYY",
  "YYYY.MM.DD",
  // short date formats with abbreviated month and 2 digit year
  "D-mmm-YY",
  "DD-mmm-YY",
  "D mmm YY",
  "DD mmm YY",
    // clang-format on
};

static std::string longDateFormats[] = {
  "DDDD, D MMMM YYYY",
  "DDDD, DD MMMM YYYY",
  "DDDD, D. MMMM YYYY",
  "DDDD, DD. MMMM YYYY",
  "DDDD, MMMM D, YYYY",
  "DDDD, MMMM DD, YYYY",
  "DDDD D MMMM YYYY",
  "DDDD DD MMMM YYYY",
  "DDDD D. MMMM YYYY",
  "DDDD DD. MMMM YYYY",
  "D. MMMM YYYY",
  "DD. MMMM YYYY",
  "D. MMMM. YYYY",
  "DD. MMMM. YYYY",
  "YYYY. MMMM. D"
};

#define TIME_FORMAT_MM_SS         ":mm:ss"
#define TIME_FORMAT_SINGLE_12     "h" TIME_FORMAT_MM_SS
#define TIME_FORMAT_DOUBLE_12     "hh" TIME_FORMAT_MM_SS
#define TIME_FORMAT_SINGLE_24     "H" TIME_FORMAT_MM_SS
#define TIME_FORMAT_DOUBLE_24     "HH" TIME_FORMAT_MM_SS

#define TIME_FORMAT_12HOURS       "12hours"
#define TIME_FORMAT_24HOURS       "24hours"

struct TemperatureInfo
{
  CTemperature::Unit unit;
  std::string name;
};

static const auto temperatureInfo = std::array{TemperatureInfo{CTemperature::UnitFahrenheit, "f"},
                                               TemperatureInfo{CTemperature::UnitKelvin, "k"},
                                               TemperatureInfo{CTemperature::UnitCelsius, "c"},
                                               TemperatureInfo{CTemperature::UnitReaumur, "re"},
                                               TemperatureInfo{CTemperature::UnitRankine, "ra"},
                                               TemperatureInfo{CTemperature::UnitRomer, "ro"},
                                               TemperatureInfo{CTemperature::UnitDelisle, "de"},
                                               TemperatureInfo{CTemperature::UnitNewton, "n"}};

#define TEMP_UNIT_STRINGS         20027

struct SpeedInfo
{
  CSpeed::Unit unit;
  std::string name;
};

static const auto speedInfo = std::array{
    SpeedInfo{CSpeed::UnitKilometresPerHour, "kmh"},
    SpeedInfo{CSpeed::UnitMetresPerMinute, "mpmin"},
    SpeedInfo{CSpeed::UnitMetresPerSecond, "mps"},
    SpeedInfo{CSpeed::UnitFeetPerHour, "fth"},
    SpeedInfo{CSpeed::UnitFeetPerMinute, "ftm"},
    SpeedInfo{CSpeed::UnitFeetPerSecond, "fts"},
    SpeedInfo{CSpeed::UnitMilesPerHour, "mph"},
    SpeedInfo{CSpeed::UnitKnots, "kts"},
    SpeedInfo{CSpeed::UnitBeaufort, "beaufort"},
    SpeedInfo{CSpeed::UnitInchPerSecond, "inchs"},
    SpeedInfo{CSpeed::UnitYardPerSecond, "yards"},
    SpeedInfo{CSpeed::UnitFurlongPerFortnight, "fpf"},
};

#define SPEED_UNIT_STRINGS        20200

#define SETTING_REGIONAL_DEFAULT  "regional"

static std::string ToTimeFormat(bool use24HourClock, bool singleHour, bool meridiem)
{
  if (use24HourClock)
    return singleHour ? TIME_FORMAT_SINGLE_24 : TIME_FORMAT_DOUBLE_24;

  if (!meridiem)
    return singleHour ? TIME_FORMAT_SINGLE_12 : TIME_FORMAT_DOUBLE_12;

  return StringUtils::Format(
      CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12382),
      ToTimeFormat(false, singleHour, false));
}

static std::string ToSettingTimeFormat(const CDateTime& time, const std::string& timeFormat)
{
  return StringUtils::Format(
      CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20036),
      time.GetAsLocalizedTime(timeFormat, true), timeFormat);
}

static CTemperature::Unit StringToTemperatureUnit(const std::string& temperatureUnit)
{
  std::string unit(temperatureUnit);
  StringUtils::ToLower(unit);

  const auto it = std::ranges::find_if(temperatureInfo,
                                       [&unit](const auto& info) { return info.name == unit; });
  return it != temperatureInfo.end() ? it->unit : CTemperature::UnitCelsius;
}

static CSpeed::Unit StringToSpeedUnit(const std::string& speedUnit)
{
  std::string unit(speedUnit);
  StringUtils::ToLower(unit);

  const auto it =
      std::ranges::find_if(speedInfo, [&unit](const auto& info) { return info.name == unit; });
  return it != speedInfo.end() ? it->unit : CSpeed::UnitKilometresPerHour;
}

struct SortLanguage
{
  bool operator()(const StringSettingOption &left, const StringSettingOption &right) const
  {
    std::string strLeft = left.label;
    std::string strRight = right.label;
    StringUtils::ToLower(strLeft);
    StringUtils::ToLower(strRight);

    return strLeft.compare(strRight) < 0;
  }
};

struct SpecialLanguageSetting
{
  std::string_view m_code;
  int m_message;
};

// Elements sorted in order of appearance in the settings
constexpr auto specialAudioLangSettings = std::array{
    SpecialLanguageSetting{audioLanguageMediaDefault, 307},
    SpecialLanguageSetting{audioLanguageOriginal, 308},
    SpecialLanguageSetting{audioLanguageDefault, 309},
};

// Elements in order of appearance in the settings
constexpr auto specialSubtitlesLangSettings = std::array{
    SpecialLanguageSetting{subLanguageNone, 231},
    SpecialLanguageSetting{subLanguageForcedOnly, 13207},
    SpecialLanguageSetting{subLanguageOriginal, 308},
    SpecialLanguageSetting{subLanguageDefault, 309},
};

// Elements in order of appearance in the settings
constexpr auto specialSubtitlesDownloadLangSettings = std::array{
    SpecialLanguageSetting{subLanguageOriginal, 308},
    SpecialLanguageSetting{subLanguageDefault, 309},
};

namespace
{
/*!
 * \brief The language a disc library should be told about.
 * \param[in] language The language the user asked for.
 * \param[in] fallback The langinfo.xml value to stand in where no language was asked for.
 * \return The language, or the fallback where there is none. Not narrowed to any notation, as
 *         the accepted notation is defined by the disc library that receives it.
 */
CLanguageTag DiscLanguage(const CLanguageTag& language, const std::string& fallback)
{
  return language.IsEmpty() ? CLanguageTag::Parse(fallback) : language;
}

/*!
 * \brief The language a setting names.
 * \param[in] language The setting value.
 * \param[in] specialSettings The values of that setting which name a policy rather than a
 *            language, such as "original" or "none".
 * \return The language, or an empty tag where the setting names none. A code the user defined in
 *         advancedsettings.xml is taken as it stands, so it need not be a standard one.
 */
CLanguageTag LanguageFromSetting(const std::string& language,
                                 std::span<const SpecialLanguageSetting> specialSettings)
{
  if (language.empty() ||
      std::ranges::any_of(specialSettings, [&language](const SpecialLanguageSetting& setting)
                          { return StringUtils::EqualsNoCase(language, setting.m_code); }))
  {
    return {};
  }

  // Parse would keep text it does not recognize. An unrecognized setting must become an empty
  // tag instead: a kept one leaves callers preferring a language no stream can match, and the
  // fallback to the UI language never engages
  if (const auto tag = CLanguageTag::TryParse(language); tag.has_value())
    return *tag;

  CLog::LogF(LOGERROR, "'{}' does not name a language, ignoring it", language);
  return {};
}
} // namespace

CLangInfo::CRegion::CRegion()
{
  SetDefaults();
}

void CLangInfo::CRegion::SetDefaults()
{
  m_strName="N/A";
  m_strLangLocaleName = "English";

  m_strDateFormatShort="DD/MM/YYYY";
  m_strDateFormatLong="DDDD, D MMMM YYYY";
  m_strTimeFormat="HH:mm:ss";
  m_tempUnit = CTemperature::UnitCelsius;
  m_speedUnit = CSpeed::UnitKilometresPerHour;
  m_strTimeZone.clear();
}

void CLangInfo::CRegion::SetTemperatureUnit(const std::string& strUnit)
{
  m_tempUnit = StringToTemperatureUnit(strUnit);
}

void CLangInfo::CRegion::SetSpeedUnit(const std::string& strUnit)
{
  m_speedUnit = StringToSpeedUnit(strUnit);
}

void CLangInfo::CRegion::SetTimeZone(const std::string& strTimeZone)
{
  m_strTimeZone = strTimeZone;
}

CLocale CLangInfo::CRegion::GetLocale() const
{
  // langinfo.xml may state the language in any of the forms the expander accepts, and Windows
  // rewrites it at load, so it is narrowed here rather than assumed
  std::string language;
  if (!CLangCodeExpander::ConvertToISO6391(m_strLangLocaleName, language))
    language = m_strLangLocaleName;

  return {language, GetCodeAlpha2()};
}

std::string CLangInfo::CRegion::GetCodeAlpha2() const
{
  // The lookup table is keyed lowercase; ISO 3166-1 publishes the codes uppercase
  std::string code{m_strRegionLocaleName};
  StringUtils::ToLower(code);

  if (code.length() == 2)
  {
    if (!CIso3166_1::ContainsAlpha2(code))
      return "";
  }
  else
  {
    code = CIso3166_1::Alpha3ToAlpha2(code).value_or("");
  }

  StringUtils::ToUpper(code);
  return code;
}

std::string CLangInfo::CRegion::GetCodeAlpha3() const
{
  // ISO 3166-1 gives every region both forms, so only one of them needs parsing
  std::string alpha2{GetCodeAlpha2()};
  StringUtils::ToLower(alpha2);

  std::string alpha3{CIso3166_1::Alpha2ToAlpha3(alpha2).value_or("")};
  StringUtils::ToUpper(alpha3);
  return alpha3;
}

void CLangInfo::CRegion::SetGlobalLocale(CLangInfo& langInfo)
{
  std::string strLocale;
  if (!m_strRegionLocaleName.empty())
  {
    const CLocale locale{GetLocale()};

    // The name a platform's locale database answers to. Windows separates the parts with a
    // hyphen, other platforms use the POSIX form the locale renders itself as.
#ifdef TARGET_WINDOWS
    strLocale = locale.GetLanguageCode() + "-" + locale.GetTerritoryCode();
#else
    strLocale = locale.ToString();
#endif
#ifdef TARGET_POSIX
    strLocale += ".UTF-8";
#endif
  }
  langInfo.m_originalLocale = std::locale(
      std::locale::classic(), new custom_numpunct(m_cDecimalSep, m_cThousandsSep, m_strGrouping));

  CLog::Log(LOGDEBUG, "trying to set locale to {}", strLocale);

  // We need to set the locale to only change the collate. Otherwise,
  // decimal separator is changed depending of the current language
  // (ie. "," in French or Dutch instead of "."). This breaks atof() and
  // others similar functions.
#if !(defined(TARGET_FREEBSD) || defined(TARGET_DARWIN_OSX) || defined(__UCLIBC__))
  // on FreeBSD, darwin and uClibc-based systems libstdc++ is compiled with
  // "generic" locale support
  std::locale current_locale = std::locale::classic(); // C-Locale
  try
  {
    std::locale lcl = std::locale(strLocale.c_str());
    strLocale = lcl.name();
    current_locale = current_locale.combine< std::collate<wchar_t> >(lcl);
    current_locale = current_locale.combine< std::ctype<wchar_t> >(lcl);
    current_locale = current_locale.combine< std::time_get<wchar_t> >(lcl);
    current_locale = current_locale.combine< std::time_put<wchar_t> >(lcl);

    assert(std::use_facet< std::numpunct<char> >(current_locale).decimal_point() == '.');

  } catch(...) {
    current_locale = std::locale::classic();
    strLocale = "C";
  }

  langInfo.m_systemLocale = current_locale; //! @todo move to CLangInfo class
  langInfo.m_collationtype = 0;
  std::locale::global(current_locale);
#endif

#ifndef TARGET_WINDOWS
  if (setlocale(LC_COLLATE, strLocale.c_str()) == nullptr ||
      setlocale(LC_CTYPE, strLocale.c_str()) == nullptr ||
      setlocale(LC_TIME, strLocale.c_str()) == nullptr)
  {
    strLocale = "C";
    setlocale(LC_COLLATE, strLocale.c_str());
    setlocale(LC_CTYPE, strLocale.c_str());
    setlocale(LC_TIME, strLocale.c_str());
  }
#else
  std::wstring strLocaleW;
  g_charsetConverter.utf8ToW(strLocale, strLocaleW);
  if (_wsetlocale(LC_COLLATE, strLocaleW.c_str()) == nullptr ||
      _wsetlocale(LC_CTYPE, strLocaleW.c_str()) == nullptr ||
      _wsetlocale(LC_TIME, strLocaleW.c_str()) == nullptr)
  {
    strLocale = "C";
    strLocaleW = L"C";
    _wsetlocale(LC_COLLATE, strLocaleW.c_str());
    _wsetlocale(LC_CTYPE, strLocaleW.c_str());
    _wsetlocale(LC_TIME, strLocaleW.c_str());
  }
#endif

  g_charsetConverter.resetSystemCharset();
  CLog::Log(LOGINFO, "global locale set to {}", strLocale);

#ifdef TARGET_ANDROID
  // Force UTF8 for, e.g., vsnprintf
  setlocale(LC_ALL, "C.UTF-8");
#endif
}

CLangInfo::CLangInfo()
{
  SetDefaults();
  m_shortDateFormat = m_defaultRegion.m_strDateFormatShort;
  m_longDateFormat = m_defaultRegion.m_strDateFormatLong;
  m_timeFormat = m_defaultRegion.m_strTimeFormat;
  m_use24HourClock = DetermineUse24HourClockFromTimeFormat(m_defaultRegion.m_strTimeFormat);
  m_temperatureUnit = m_defaultRegion.m_tempUnit;
  m_speedUnit = m_defaultRegion.m_speedUnit;
  m_collationtype = 0;
}

CLangInfo::~CLangInfo() = default;

void CLangInfo::OnSettingChanged(const std::shared_ptr<const CSetting>& setting)
{
  if (setting == nullptr)
    return;

  auto settingsComponent = CServiceBroker::GetSettingsComponent();
  if (!settingsComponent)
    return;

  auto settings = settingsComponent->GetSettings();
  if (!settings)
    return;

  const std::string &settingId = setting->GetId();
  if (settingId == CSettings::SETTING_LOCALE_AUDIOLANGUAGE)
    SetAudioLanguage(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_SUBTITLELANGUAGE)
    SetSubtitleLanguage(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_LANGUAGE)
  {
    if (!SetLanguage(std::static_pointer_cast<const CSettingString>(setting)->GetValue()))
    {
      auto langsetting = settings->GetSetting(CSettings::SETTING_LOCALE_LANGUAGE);
      if (!langsetting)
      {
        CLog::Log(LOGERROR, "Failed to load setting for: {}", CSettings::SETTING_LOCALE_LANGUAGE);
        return;
      }

      std::static_pointer_cast<CSettingString>(langsetting)->Reset();
    }
  }
  else if (settingId == CSettings::SETTING_LOCALE_COUNTRY)
    SetCurrentRegion(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_SHORTDATEFORMAT)
    SetShortDateFormat(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_LONGDATEFORMAT)
    SetLongDateFormat(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_TIMEFORMAT)
    SetTimeFormat(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_USE24HOURCLOCK)
  {
    Set24HourClock(std::static_pointer_cast<const CSettingString>(setting)->GetValue());

    // update the time format
    settings->SetString(CSettings::SETTING_LOCALE_TIMEFORMAT,
                        PrepareTimeFormat(GetTimeFormat(), m_use24HourClock));
  }
  else if (settingId == CSettings::SETTING_LOCALE_TEMPERATUREUNIT)
    SetTemperatureUnit(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
  else if (settingId == CSettings::SETTING_LOCALE_SPEEDUNIT)
    SetSpeedUnit(std::static_pointer_cast<const CSettingString>(setting)->GetValue());
}

void CLangInfo::OnSettingsLoaded()
{
  // set the temperature and speed units based on the settings
  const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  SetShortDateFormat(settings->GetString(CSettings::SETTING_LOCALE_SHORTDATEFORMAT));
  SetLongDateFormat(settings->GetString(CSettings::SETTING_LOCALE_LONGDATEFORMAT));
  Set24HourClock(settings->GetString(CSettings::SETTING_LOCALE_USE24HOURCLOCK));
  SetTimeFormat(settings->GetString(CSettings::SETTING_LOCALE_TIMEFORMAT));
  SetTemperatureUnit(settings->GetString(CSettings::SETTING_LOCALE_TEMPERATUREUNIT));
  SetSpeedUnit(settings->GetString(CSettings::SETTING_LOCALE_SPEEDUNIT));
}

bool CLangInfo::Load(const std::string& strLanguage)
{
  SetDefaults();

  // Stands in until the file supplies one, and remains if it cannot be read. Set here rather
  // than in SetDefaults because parsing reaches the subtag registry, which does not yet
  // exist when this class is constructed.
  m_uiLanguage = CLanguageTag::Parse("eng");

  std::string strFileName = GetLanguageInfoPath(strLanguage);

  CXBMCTinyXML2 xmlDoc;
  if (!xmlDoc.LoadFile(strFileName))
  {
    CLog::Log(LOGERROR, "unable to load {}: {} at line {}", strFileName, xmlDoc.ErrorStr(),
              xmlDoc.ErrorLineNum());
    return false;
  }

  // get the matching language addon
  m_languageAddon = GetLanguageAddon(strLanguage);
  if (m_languageAddon == nullptr)
  {
    CLog::Log(LOGERROR, "Unknown language {}", strLanguage);
    return false;
  }

  // get some language-specific information from the language addon
  m_strGuiCharSet = m_languageAddon->GetGuiCharset();
  m_forceUnicodeFont = m_languageAddon->ForceUnicodeFont();
  m_strSubtitleCharSet = m_languageAddon->GetSubtitleCharset();
  m_strDVDMenuLanguage = m_languageAddon->GetDvdMenuLanguage();
  m_strDVDAudioLanguage = m_languageAddon->GetDvdAudioLanguage();
  m_strDVDSubtitleLanguage = m_languageAddon->GetDvdSubtitleLanguage();
  m_sortTokens = m_languageAddon->GetSortTokens();

  const auto* pRootElement = xmlDoc.RootElement();
  if (std::string(pRootElement->Value()) != "language")
  {
    CLog::Log(LOGERROR, "{} Doesn't contain <language>", strFileName);
    return false;
  }

  if (pRootElement->Attribute("locale"))
    m_defaultRegion.m_strLangLocaleName = pRootElement->Attribute("locale");

  m_uiLanguage = CLanguageTag::Parse(m_defaultRegion.m_strLangLocaleName);

  const auto* pRegions = pRootElement->FirstChildElement("regions");
  if (pRegions && !pRegions->NoChildren())
  {
    const auto* pRegion = pRegions->FirstChildElement("region");
    while (pRegion)
    {
      CRegion region(m_defaultRegion);
      region.m_strName = XMLUtils::GetAttribute(pRegion, "name");
      if (region.m_strName.empty())
        region.m_strName = CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(
            10005); // Not available

      if (pRegion->Attribute("locale"))
        region.m_strRegionLocaleName = pRegion->Attribute("locale");

#ifdef TARGET_WINDOWS
      // Windows need 3 chars regions code
      region.m_strRegionLocaleName = region.GetCodeAlpha3();
#endif

      const auto* pDateLong = pRegion->FirstChildElement("datelong");
      if (pDateLong && !pDateLong->NoChildren())
        region.m_strDateFormatLong = pDateLong->FirstChild()->Value();

      const auto* pDateShort = pRegion->FirstChildElement("dateshort");
      if (pDateShort && !pDateShort->NoChildren())
        region.m_strDateFormatShort = pDateShort->FirstChild()->Value();

      const auto* pTime = pRegion->FirstChildElement("time");
      if (pTime && !pTime->NoChildren())
      {
        region.m_strTimeFormat=pTime->FirstChild()->Value();
        region.m_strMeridiemSymbols[static_cast<int>(MeridiemSymbol::AM)] =
            XMLUtils::GetAttribute(pTime, "symbolAM");
        region.m_strMeridiemSymbols[static_cast<int>(MeridiemSymbol::PM)] =
            XMLUtils::GetAttribute(pTime, "symbolPM");
      }

      const auto* pTempUnit = pRegion->FirstChildElement("tempunit");
      if (pTempUnit && !pTempUnit->NoChildren())
        region.SetTemperatureUnit(pTempUnit->FirstChild()->Value());

      const auto* pSpeedUnit = pRegion->FirstChildElement("speedunit");
      if (pSpeedUnit && !pSpeedUnit->NoChildren())
        region.SetSpeedUnit(pSpeedUnit->FirstChild()->Value());

      const auto* pTimeZone = pRegion->FirstChildElement("timezone");
      if (pTimeZone && !pTimeZone->NoChildren())
        region.SetTimeZone(pTimeZone->FirstChild()->Value());

      const auto* pThousandsSep = pRegion->FirstChildElement("thousandsseparator");
      if (pThousandsSep)
      {
        if (!pThousandsSep->NoChildren())
        {
          region.m_cThousandsSep = pThousandsSep->FirstChild()->Value()[0];
          if (pThousandsSep->Attribute("groupingformat"))
            region.m_strGrouping = StringUtils::BinaryStringToString(pThousandsSep->Attribute("groupingformat"));
          else
            region.m_strGrouping = "\3";
        }
      }
      else
      {
        region.m_cThousandsSep = ',';
        region.m_strGrouping = "\3";
      }

      const auto* pDecimalSep = pRegion->FirstChildElement("decimalseparator");
      if (pDecimalSep)
      {
        if (!pDecimalSep->NoChildren())
          region.m_cDecimalSep = pDecimalSep->FirstChild()->Value()[0];
      }
      else
        region.m_cDecimalSep = '.';

      m_regions.insert(PAIR_REGIONS(region.m_strName, region));

      pRegion = pRegion->NextSiblingElement("region");
    }

    const std::string& strName = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_COUNTRY);
    SetCurrentRegion(strName);
  }
  g_charsetConverter.reinitCharsetsFromSettings();

  return true;
}

std::string CLangInfo::GetLanguagePath(const std::string &language)
{
  if (language.empty())
    return "";

  std::string addonId = ADDON::CLanguageResource::GetAddonId(language);

  std::string path = URIUtils::AddFileToFolder(GetLanguagePath(), addonId);
  URIUtils::AddSlashAtEnd(path);

  return path;
}

std::string CLangInfo::GetLanguageInfoPath(const std::string &language)
{
  if (language.empty())
    return "";

  return URIUtils::AddFileToFolder(GetLanguagePath(language), "langinfo.xml");
}

bool CLangInfo::UseLocaleCollation()
{
  if (m_collationtype == 0)
  {
    // Determine collation to use. When using MySQL/MariaDB or a platform that does not support
    // locale language collation then use accent folding internal equivalent of utf8_general_ci
    m_collationtype = 1;
    if (!StringUtils::EqualsNoCase(
            CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseMusic.type,
            "mysql") &&
        !StringUtils::EqualsNoCase(
            CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_databaseVideo.type,
            "mysql") &&
        CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_useLocaleCollation)
    {
      // Check that locale collation facet is implemented on the platform
      const std::collate<wchar_t>& coll = std::use_facet<std::collate<wchar_t>>(m_systemLocale);
      wchar_t lc = L'z';
      wchar_t rc = 0x00E2; // Latin small letter a with circumflex
      int comp_result = coll.compare(&lc, &lc + 1, &rc, &rc + 1);
      if (comp_result > 0)
        // Latin small letter a with circumflex put before z - collation works
        m_collationtype = 2;
    }
  }
  return m_collationtype == 2;
}

void CLangInfo::LoadTokens(const TiXmlNode* pTokens, Tokens& vecTokens)
{
  if (pTokens && !pTokens->NoChildren())
  {
    const TiXmlElement *pToken = pTokens->FirstChildElement("token");
    while (pToken)
    {
      std::string strSep= " ._";
      if (pToken->Attribute("separators"))
        strSep = pToken->Attribute("separators");
      if (pToken->FirstChild() && pToken->FirstChild()->Value())
      {
        if (strSep.empty())
          vecTokens.insert(pToken->FirstChild()->ValueStr());
        else
          for (unsigned int i=0;i<strSep.size();++i)
            vecTokens.insert(pToken->FirstChild()->ValueStr() + strSep[i]);
      }
      pToken = pToken->NextSiblingElement();
    }
  }
}

void CLangInfo::SetDefaults()
{
  m_regions.clear();

  //Reset default region
  m_defaultRegion.SetDefaults();

  // Set the default region, we may be unable to load langinfo.xml
  m_currentRegion = &m_defaultRegion;

  m_systemLocale = std::locale::classic();

  m_forceUnicodeFont = false;
  m_strGuiCharSet = "CP1252";
  m_strSubtitleCharSet = "CP1252";
  m_strDVDMenuLanguage = "en";
  m_strDVDAudioLanguage = "en";
  m_strDVDSubtitleLanguage = "en";
  m_sortTokens.clear();
}

std::string CLangInfo::GetGuiCharSet() const
{
  const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  const auto setting = settings->GetSetting(CSettings::SETTING_LOCALE_CHARSET);
  const auto charsetSetting = std::static_pointer_cast<CSettingString>(setting);

  return charsetSetting->IsDefault() ? m_strGuiCharSet : charsetSetting->GetValue();
}

std::string CLangInfo::GetSubtitleCharSet() const
{
  const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  const auto setting = settings->GetSetting(CSettings::SETTING_SUBTITLES_CHARSET);
  const auto charsetSetting = std::static_pointer_cast<CSettingString>(setting);
  return charsetSetting->IsDefault() ? m_strSubtitleCharSet : charsetSetting->GetValue();
}

void CLangInfo::GetAddonsLanguageCodes(std::map<std::string, std::string>& languages)
{
  ADDON::VECADDONS addons;
  CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::RESOURCE_LANGUAGE);
  std::ranges::transform(addons, std::inserter(languages, languages.end()),
                         [](const auto& addon)
                         {
                           const LanguageResourcePtr langAddon =
                               std::dynamic_pointer_cast<ADDON::CLanguageResource>(addon);
                           std::string langCode{langAddon->GetLocale().ToShortStringLC()};
                           StringUtils::Replace(langCode, '_', '-');
                           return std::pair{std::move(langCode), addon->Name()};
                         });
}

LanguageResourcePtr CLangInfo::GetLanguageAddon(const std::string& locale /* = "" */) const
{
  if (locale.empty() ||
      (m_languageAddon != nullptr &&
       (locale.compare(m_languageAddon->ID()) == 0 || m_languageAddon->GetLocale().Equals(locale))))
    return m_languageAddon;

  std::string addonId = ADDON::CLanguageResource::GetAddonId(locale);
  if (addonId.empty())
    addonId = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE);

  ADDON::AddonPtr addon;
  if (CServiceBroker::GetAddonMgr().GetAddon(addonId, addon, ADDON::AddonType::RESOURCE_LANGUAGE,
                                             ADDON::OnlyEnabled::CHOICE_YES) &&
      addon != nullptr)
    return std::dynamic_pointer_cast<ADDON::CLanguageResource>(addon);

  return nullptr;
}

std::string CLangInfo::ConvertEnglishNameToAddonLocale(const std::string& langName)
{
  ADDON::VECADDONS addons;
  CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::RESOURCE_LANGUAGE);
  const auto it =
      std::ranges::find_if(addons, [&langName](const auto& addon)
                           { return StringUtils::CompareNoCase(addon->Name(), langName) == 0; });

  if (it != addons.end())
  {
    const LanguageResourcePtr langAddon = std::dynamic_pointer_cast<ADDON::CLanguageResource>(*it);
    std::string locale = langAddon->GetLocale().ToShortStringLC();
    StringUtils::Replace(locale, '_', '-');
    return locale;
  }

  return "";
}

std::string CLangInfo::GetEnglishLanguageName(const std::string& locale /* = "" */) const
{
  LanguageResourcePtr addon = GetLanguageAddon(locale);
  if (addon == nullptr)
    return "";

  return addon->Name();
}

namespace
{
/*!
 * \brief The name of the language alone, with the region or script qualifier a langinfo.xml name
 *        may carry in parentheses removed - "English (Australia)" becomes "English".
 */
std::string BaseLanguageName(const std::string& name)
{
  const size_t openParen = name.find('(');
  if (openParen == std::string::npos)
    return name;

  std::string base = name.substr(0, openParen);
  StringUtils::TrimRight(base);
  return base;
}
} // namespace

std::string CLangInfo::GetLanguageAs(CLangCodeExpander::LANGFORMATS format, bool withRegion) const
{
  const std::string englishName{GetEnglishLanguageName()};

  std::string language;
  switch (format)
  {
    case CLangCodeExpander::ENGLISH_NAME:
      language = englishName;
      break;
    case CLangCodeExpander::ISO_NAME:
      language = BaseLanguageName(englishName);
      break;
    case CLangCodeExpander::ISO_639_1:
      CLangCodeExpander::ConvertToISO6391(BaseLanguageName(englishName), language);
      break;
    case CLangCodeExpander::ISO_639_2:
      CLangCodeExpander::ConvertToISO6392B(BaseLanguageName(englishName), language);
      break;
  }

  // The region, named in the notation the language format implies
  const auto regionInFormat = [this](CLangCodeExpander::LANGFORMATS fmt)
  {
    switch (fmt)
    {
      case CLangCodeExpander::ISO_639_1:
        return GetRegionCodeAlpha2();
      case CLangCodeExpander::ISO_639_2:
        return GetRegionCodeAlpha3();
      case CLangCodeExpander::ISO_NAME:
        return BaseLanguageName(GetCurrentRegion());
      default:
        return GetCurrentRegion();
    }
  };

  // Some languages have no code in the requested ISO 639 format - Asturian, for one, has no
  // ISO 639-1 code - and the conversion then yields nothing to join a region to
  if (withRegion && !language.empty())
  {
    if (const std::string regionCode{regionInFormat(format)}; !regionCode.empty())
      language += "-" + regionCode;
  }

  return language;
}

bool CLangInfo::SetLanguage(std::string language /* = "" */, bool reloadServices /* = true */)
{
  if (language.empty())
    language = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE);

  auto& addonMgr = CServiceBroker::GetAddonMgr();
  ADDON::AddonPtr addon;

  // Find the chosen language add-on if it's enabled
  if (!addonMgr.GetAddon(language, addon, ADDON::AddonType::RESOURCE_LANGUAGE,
                         ADDON::OnlyEnabled::CHOICE_YES))
  {
    if (!addonMgr.IsAddonInstalled(language) ||
        (addonMgr.IsAddonDisabled(language) && !addonMgr.EnableAddon(language)))
    {
      CLog::LogF(LOGWARNING, "Could not find or enable language add-on '{}', loading default...",
                 language);
      language = std::static_pointer_cast<const CSettingString>(
                     CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(
                         CSettings::SETTING_LOCALE_LANGUAGE))
                     ->GetDefault();

      if (!addonMgr.GetAddon(language, addon, ADDON::AddonType::RESOURCE_LANGUAGE,
                             ADDON::OnlyEnabled::CHOICE_NO))
      {
        CLog::LogF(LOGFATAL, "Could not find default language add-on '{}'", language);
        return false;
      }
    }
  }

  CLog::Log(LOGINFO, "CLangInfo: loading {} language information...", language);
  if (!Load(language))
  {
    CLog::LogF(LOGFATAL, "Failed to load {} language information", language);
    return false;
  }

  CLog::Log(LOGINFO, "CLangInfo: loading {} language strings...", language);
  auto& resources = CServiceBroker::GetResourcesComponent();
  if (!resources.GetLocalizeStrings().Load(GetLanguagePath(), language))
  {
    CLog::LogF(LOGFATAL, "Failed to load {} language strings", language);
    return false;
  }

  ADDON::VECADDONS addons;
  if (CServiceBroker::GetAddonMgr().GetInstalledAddons(addons))
  {
    const std::string locale = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOCALE_LANGUAGE);
    std::ranges::for_each(addons,
                          [&resources, &locale](const auto& ad)
                          {
                            const std::string path =
                                URIUtils::AddFileToFolder(ad->Path(), "resources", "language/");
                            resources.GetLocalizeStrings().LoadAddonStrings(path, locale, ad->ID());
                          });
  }

  if (reloadServices)
  {
    // also tell our weather and skin to reload as these are localized
    CServiceBroker::GetWeatherManager().Refresh();
    CServiceBroker::GetPVRManager().LocalizationChanged();
    CServiceBroker::GetDatabaseManager().LocalizationChanged();
    CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr,
                                               "ReloadSkin");
  }

  return true;
}

const CLanguageTag& CLangInfo::GetAudioLanguage(bool allowFallback) const
{
  if (allowFallback && m_audioLanguage.IsEmpty())
    return m_uiLanguage;

  return m_audioLanguage;
}

std::string CLangInfo::GetPreferredAudioLanguage() const
{
  const std::string setting{CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(
      CSettings::SETTING_LOCALE_AUDIOLANGUAGE)};

  if (StringUtils::EqualsNoCase(setting, audioLanguageMediaDefault) ||
      StringUtils::EqualsNoCase(setting, audioLanguageOriginal))
    return "";

  // Resolves "default" to the UI language. Narrowed to the notation the stream details
  // carry, which is what the caller compares against.
  return GetAudioLanguage(true).AsIso6392B();
}

void CLangInfo::SetAudioLanguage(const std::string& language)
{
  m_audioLanguage = LanguageFromSetting(language, specialAudioLangSettings);
}

const CLanguageTag& CLangInfo::GetSubtitleLanguage(bool allowFallback) const
{
  if (allowFallback && m_subtitleLanguage.IsEmpty())
    return m_uiLanguage;

  return m_subtitleLanguage;
}

void CLangInfo::SetSubtitleLanguage(const std::string& language)
{
  m_subtitleLanguage = LanguageFromSetting(language, specialSubtitlesLangSettings);
}

CLanguageTag CLangInfo::GetDVDMenuLanguage() const
{
  return DiscLanguage(CLanguageTag::Parse(m_currentRegion->m_strLangLocaleName),
                      m_strDVDMenuLanguage);
}

CLanguageTag CLangInfo::GetDVDAudioLanguage() const
{
  return DiscLanguage(m_audioLanguage, m_strDVDAudioLanguage);
}

CLanguageTag CLangInfo::GetDVDSubtitleLanguage() const
{
  return DiscLanguage(m_subtitleLanguage, m_strDVDSubtitleLanguage);
}

const CLocale& CLangInfo::GetLocale() const
{
  LanguageResourcePtr language = GetLanguageAddon();
  return language != nullptr ? language->GetLocale() : CLocale::Empty;
}

const std::string& CLangInfo::GetRegionLocale() const
{
  return m_currentRegion->m_strRegionLocaleName;
}

std::string CLangInfo::GetRegionCodeAlpha2() const
{
  return m_currentRegion->GetCodeAlpha2();
}

std::string CLangInfo::GetRegionCodeAlpha3() const
{
  return m_currentRegion->GetCodeAlpha3();
}

const std::locale& CLangInfo::GetOriginalLocale() const
{
  return m_originalLocale;
}

// Returns the format string for the date of the current language
const std::string& CLangInfo::GetDateFormat(bool bLongDate /* = false */) const
{
  return bLongDate ? GetLongDateFormat() : GetShortDateFormat();
}

void CLangInfo::SetDateFormat(const std::string& dateFormat, bool bLongDate /* = false */)
{
  if (bLongDate)
    SetLongDateFormat(dateFormat);
  else
    SetShortDateFormat(dateFormat);
}

const std::string& CLangInfo::GetShortDateFormat() const
{
  return m_shortDateFormat;
}

void CLangInfo::SetShortDateFormat(const std::string& shortDateFormat)
{
  std::string newShortDateFormat = shortDateFormat;
  if (shortDateFormat == SETTING_REGIONAL_DEFAULT)
    newShortDateFormat = m_currentRegion->m_strDateFormatShort;

  m_shortDateFormat = newShortDateFormat;
}

const std::string& CLangInfo::GetLongDateFormat() const
{
  return m_longDateFormat;
}

void CLangInfo::SetLongDateFormat(const std::string& longDateFormat)
{
  std::string newLongDateFormat = longDateFormat;
  if (longDateFormat == SETTING_REGIONAL_DEFAULT)
    newLongDateFormat = m_currentRegion->m_strDateFormatShort;

  m_longDateFormat = newLongDateFormat;
}

// Returns the format string for the time of the current language
const std::string& CLangInfo::GetTimeFormat() const
{
  return m_timeFormat;
}

void CLangInfo::SetTimeFormat(const std::string& timeFormat)
{
  std::string newTimeFormat = timeFormat;
  if (timeFormat == SETTING_REGIONAL_DEFAULT)
    newTimeFormat = m_currentRegion->m_strTimeFormat;

  m_timeFormat = PrepareTimeFormat(newTimeFormat, m_use24HourClock);
}

bool CLangInfo::Use24HourClock() const
{
  return m_use24HourClock;
}

void CLangInfo::Set24HourClock(bool use24HourClock)
{
  m_use24HourClock = use24HourClock;
}

void CLangInfo::Set24HourClock(const std::string& str24HourClock)
{
  bool use24HourClock = false;
  if (str24HourClock == TIME_FORMAT_12HOURS)
    use24HourClock = false;
  else if (str24HourClock == TIME_FORMAT_24HOURS)
    use24HourClock = true;
  else if (str24HourClock == SETTING_REGIONAL_DEFAULT)
  {
    Set24HourClock(m_currentRegion->m_strTimeFormat);
    return;
  }
  else
    use24HourClock = DetermineUse24HourClockFromTimeFormat(str24HourClock);

  if (m_use24HourClock == use24HourClock)
    return;

  m_use24HourClock = use24HourClock;
}

const std::string& CLangInfo::GetTimeZone() const
{
  return m_currentRegion->m_strTimeZone;
}

// Returns the AM/PM symbol of the current language
const std::string& CLangInfo::GetMeridiemSymbol(MeridiemSymbol symbol) const
{
  // nothing to return if we use 24-hour clock
  if (m_use24HourClock)
    return StringUtils::Empty;

  return MeridiemSymbolToString(symbol);
}

const std::string& CLangInfo::MeridiemSymbolToString(MeridiemSymbol symbol)
{
  switch (symbol)
  {
    case MeridiemSymbol::AM:
      return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(378);

    case MeridiemSymbol::PM:
      return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(379);

    default:
      break;
  }

  return StringUtils::Empty;
}

// Fills the array with the region names available for this language
void CLangInfo::GetRegionNames(std::vector<std::string>& array) const
{
  std::ranges::transform(m_regions, std::back_inserter(array),
                         [&rc = CServiceBroker::GetResourcesComponent()](const auto& region)
                         {
                           return region.first == "N/A"
                                      ? rc.GetLocalizeStrings().Get(10005) // Not available
                                      : region.first;
                         });
}

// Set the current region by its name, names from GetRegionNames() are valid.
// If the region is not found the first available region is set.
void CLangInfo::SetCurrentRegion(const std::string& strName)
{
  ITMAPREGIONS it=m_regions.find(strName);
  if (it!=m_regions.end())
    m_currentRegion=&it->second;
  else if (!m_regions.empty())
    m_currentRegion=&m_regions.begin()->second;
  else
    m_currentRegion=&m_defaultRegion;

  m_currentRegion->SetGlobalLocale(*this);

  const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
  if (settings->GetString(CSettings::SETTING_LOCALE_SHORTDATEFORMAT) == SETTING_REGIONAL_DEFAULT)
    SetShortDateFormat(m_currentRegion->m_strDateFormatShort);
  if (settings->GetString(CSettings::SETTING_LOCALE_LONGDATEFORMAT) == SETTING_REGIONAL_DEFAULT)
    SetLongDateFormat(m_currentRegion->m_strDateFormatLong);
  if (settings->GetString(CSettings::SETTING_LOCALE_USE24HOURCLOCK) == SETTING_REGIONAL_DEFAULT)
  {
    Set24HourClock(m_currentRegion->m_strTimeFormat);

    // update the time format
    SetTimeFormat(settings->GetString(CSettings::SETTING_LOCALE_TIMEFORMAT));
  }
  if (settings->GetString(CSettings::SETTING_LOCALE_TIMEFORMAT) == SETTING_REGIONAL_DEFAULT)
    SetTimeFormat(m_currentRegion->m_strTimeFormat);
  if (settings->GetString(CSettings::SETTING_LOCALE_TEMPERATUREUNIT) == SETTING_REGIONAL_DEFAULT)
    SetTemperatureUnit(m_currentRegion->m_tempUnit);
  if (settings->GetString(CSettings::SETTING_LOCALE_SPEEDUNIT) == SETTING_REGIONAL_DEFAULT)
    SetSpeedUnit(m_currentRegion->m_speedUnit);
}

// Returns the current region set for this language
const std::string& CLangInfo::GetCurrentRegion() const
{
  return m_currentRegion->m_strName;
}

CTemperature::Unit CLangInfo::GetTemperatureUnit() const
{
  return m_temperatureUnit;
}

void CLangInfo::SetTemperatureUnit(CTemperature::Unit temperatureUnit)
{
  if (m_temperatureUnit == temperatureUnit)
    return;

  m_temperatureUnit = temperatureUnit;

  // refresh weather manager as temperatures need re-translating
  // NOTE: this could be called before our service manager is up
  if (CServiceBroker::IsServiceManagerUp())
    CServiceBroker::GetWeatherManager().Refresh();
}

void CLangInfo::SetTemperatureUnit(const std::string& temperatureUnit)
{
  SetTemperatureUnit(temperatureUnit == SETTING_REGIONAL_DEFAULT
                         ? m_currentRegion->m_tempUnit
                         : StringToTemperatureUnit(temperatureUnit));
}

std::string CLangInfo::GetTemperatureAsString(const CTemperature& temperature) const
{
  if (!temperature.IsValid())
    return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(13205); // "Unknown"

  CTemperature::Unit temperatureUnit = GetTemperatureUnit();
  return StringUtils::Format("{}{}", temperature.ToString(temperatureUnit),
                             GetTemperatureUnitString());
}

// Returns the temperature unit string for the current language
const std::string& CLangInfo::GetTemperatureUnitString() const
{
  return GetTemperatureUnitString(m_temperatureUnit);
}

const std::string& CLangInfo::GetTemperatureUnitString(CTemperature::Unit temperatureUnit)
{
  return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(TEMP_UNIT_STRINGS +
                                                                          temperatureUnit);
}

void CLangInfo::SetSpeedUnit(CSpeed::Unit speedUnit)
{
  if (m_speedUnit == speedUnit)
    return;

  m_speedUnit = speedUnit;

  // refresh weather manager as speeds need re-translating
  // NOTE: this could be called before our service manager is up
  if (CServiceBroker::IsServiceManagerUp())
    CServiceBroker::GetWeatherManager().Refresh();
}

void CLangInfo::SetSpeedUnit(const std::string& speedUnit)
{
  SetSpeedUnit(speedUnit == SETTING_REGIONAL_DEFAULT ? m_currentRegion->m_speedUnit
                                                     : StringToSpeedUnit(speedUnit));
}

CSpeed::Unit CLangInfo::GetSpeedUnit() const
{
  return m_speedUnit;
}

std::string CLangInfo::GetSpeedAsString(const CSpeed& speed) const
{
  if (!speed.IsValid())
    return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(13205); // "Unknown"

  return StringUtils::Format("{}{}", speed.ToString(GetSpeedUnit()), GetSpeedUnitString());
}

// Returns the speed unit string for the current language
const std::string& CLangInfo::GetSpeedUnitString() const
{
  return GetSpeedUnitString(m_speedUnit);
}

const std::string& CLangInfo::GetSpeedUnitString(CSpeed::Unit speedUnit)
{
  return CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(SPEED_UNIT_STRINGS +
                                                                          speedUnit);
}

CLangInfo::Tokens CLangInfo::GetSortTokens() const
{
  Tokens sortTokens = m_sortTokens;
  for (const auto& t : CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_vecTokens)
    sortTokens.insert(t);

  return sortTokens;
}

bool CLangInfo::DetermineUse24HourClockFromTimeFormat(const std::string& timeFormat)
{
  // if the time format contains a "h" it's 12-hour and otherwise 24-hour clock format
  return timeFormat.find('h') == std::string::npos;
}

bool CLangInfo::DetermineUseMeridiemFromTimeFormat(const std::string& timeFormat)
{
  // if the time format contains "xx" it's using meridiem
  return timeFormat.find("xx") != std::string::npos;
}

std::string CLangInfo::PrepareTimeFormat(const std::string& timeFormat, bool use24HourClock)
{
  std::string preparedTimeFormat = timeFormat;
  if (use24HourClock)
  {
    // replace all "h" with "H"
    StringUtils::Replace(preparedTimeFormat, 'h', 'H');

    // remove any "xx" for meridiem
    StringUtils::Replace(preparedTimeFormat, "x", "");
  }
  else
    // replace all "H" with "h"
    StringUtils::Replace(preparedTimeFormat, 'H', 'h');

  StringUtils::Trim(preparedTimeFormat);

  return preparedTimeFormat;
}

void CLangInfo::SettingOptionsLanguageNamesFiller(const SettingConstPtr& /*setting*/,
                                                  std::vector<StringSettingOption>& list,
                                                  std::string& current)
{
  // find languages...
  ADDON::VECADDONS addons;
  if (!CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::RESOURCE_LANGUAGE))
    return;

  std::ranges::transform(addons, std::back_inserter(list), [](const auto& addon)
                         { return StringSettingOption{addon->Name(), addon->Name()}; });

  sort(list.begin(), list.end(), SortLanguage());
}

void CLangInfo::SettingOptionsISO6391LanguagesFiller(const SettingConstPtr& /*setting*/,
                                                     std::vector<StringSettingOption>& list,
                                                     std::string& /*current*/)
{
  std::vector<std::string> languages = CLangCodeExpander::GetLanguageNames(
      CLangCodeExpander::ISO_639_1, CLangCodeExpander::LANG_LIST::INCLUDE_USERDEFINED);

  std::ranges::transform(languages, std::back_inserter(list), [](const auto& language)
                         { return StringSettingOption{language, language}; });
}

void CLangInfo::SettingOptionsAudioStreamLanguagesFiller(const SettingConstPtr& /*setting*/,
                                                         std::vector<StringSettingOption>& list,
                                                         std::string& /*current*/)
{
  for (const auto& special : specialAudioLangSettings)
  {
    list.emplace_back(
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(special.m_message),
        std::string{special.m_code});
  }

  AddLanguages(list);
}

void CLangInfo::SettingOptionsSubtitleStreamLanguagesFiller(const SettingConstPtr& /*setting*/,
                                                            std::vector<StringSettingOption>& list,
                                                            std::string& /*current*/)
{
  for (const auto& special : specialSubtitlesLangSettings)
  {
    list.emplace_back(
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(special.m_message),
        std::string{special.m_code});
  }

  AddLanguages(list);
}

void CLangInfo::SettingOptionsSubtitleDownloadlanguagesFiller(
    const SettingConstPtr& /*setting*/,
    std::vector<StringSettingOption>& list,
    std::string& /*current*/)
{
  for (const auto& special : specialSubtitlesDownloadLangSettings)
  {
    list.emplace_back(
        CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(special.m_message),
        std::string{special.m_code});
  }

  AddLanguages(list);
}

void CLangInfo::SettingOptionsRegionsFiller(const SettingConstPtr& setting,
                                            std::vector<StringSettingOption>& list,
                                            std::string& current,
                                            const CLangInfo& langInfo)
{
  std::vector<std::string> regions;
  langInfo.GetRegionNames(regions);
  std::sort(regions.begin(), regions.end(), sortstringbyname());

  bool match = false;
  for (unsigned int i = 0; i < regions.size(); ++i)
  {
    std::string region = regions[i];
    list.emplace_back(region, region);

    if (!match && region == std::static_pointer_cast<const CSettingString>(setting)->GetValue())
    {
      match = true;
      current = region;
    }
  }

  if (!match && !regions.empty())
    current = regions[0];
}

void CLangInfo::SettingOptionsShortDateFormatsFiller(const SettingConstPtr& setting,
                                                     std::vector<StringSettingOption>& list,
                                                     std::string& current,
                                                     const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& shortDateFormatSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  CDateTime now = CDateTime::GetCurrentDateTime();

  list.emplace_back(
      StringUtils::Format(
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
          GetDateStringWithFormat(now, langInfo.m_currentRegion->m_strDateFormatShort)),
      SETTING_REGIONAL_DEFAULT);

  if (shortDateFormatSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  for (const std::string& shortDateFormat : shortDateFormats)
  {
    list.emplace_back(GetDateStringWithFormat(now, shortDateFormat), shortDateFormat);

    if (!match && shortDateFormatSetting == shortDateFormat)
    {
      match = true;
      current = shortDateFormat;
    }
  }

  if (!match && !list.empty())
    current = list[0].value;
}

void CLangInfo::SettingOptionsLongDateFormatsFiller(const SettingConstPtr& setting,
                                                    std::vector<StringSettingOption>& list,
                                                    std::string& current,
                                                    const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& longDateFormatSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  CDateTime now = CDateTime::GetCurrentDateTime();

  list.emplace_back(
      StringUtils::Format(
          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
          GetDateStringWithFormat(now, langInfo.m_currentRegion->m_strDateFormatLong)),
      SETTING_REGIONAL_DEFAULT);

  if (longDateFormatSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  for (const std::string& longDateFormat : longDateFormats)
  {
    list.emplace_back(GetDateStringWithFormat(now, longDateFormat), longDateFormat);

    if (!match && longDateFormatSetting == longDateFormat)
    {
      match = true;
      current = longDateFormat;
    }
  }

  if (!match && !list.empty())
    current = list[0].value;
}

void CLangInfo::SettingOptionsTimeFormatsFiller(const SettingConstPtr& setting,
                                                std::vector<StringSettingOption>& list,
                                                std::string& current,
                                                const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& timeFormatSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  CDateTime now = CDateTime::GetCurrentDateTime();
  bool use24hourFormat = langInfo.Use24HourClock();

  list.emplace_back(
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
                          ToSettingTimeFormat(now, langInfo.m_currentRegion->m_strTimeFormat)),
      SETTING_REGIONAL_DEFAULT);
  if (timeFormatSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  if (use24hourFormat)
  {
    list.emplace_back(ToSettingTimeFormat(now, TIME_FORMAT_SINGLE_24), TIME_FORMAT_SINGLE_24);
    if (timeFormatSetting == TIME_FORMAT_SINGLE_24)
    {
      current = TIME_FORMAT_SINGLE_24;
      match = true;
    }

    list.emplace_back(ToSettingTimeFormat(now, TIME_FORMAT_DOUBLE_24), TIME_FORMAT_DOUBLE_24);
    if (timeFormatSetting == TIME_FORMAT_DOUBLE_24)
    {
      current = TIME_FORMAT_DOUBLE_24;
      match = true;
    }
  }
  else
  {
    list.emplace_back(ToSettingTimeFormat(now, TIME_FORMAT_SINGLE_12), TIME_FORMAT_SINGLE_12);
    if (timeFormatSetting == TIME_FORMAT_SINGLE_12)
    {
      current = TIME_FORMAT_SINGLE_12;
      match = true;
    }

    list.emplace_back(ToSettingTimeFormat(now, TIME_FORMAT_DOUBLE_12), TIME_FORMAT_DOUBLE_12);
    if (timeFormatSetting == TIME_FORMAT_DOUBLE_12)
    {
      current = TIME_FORMAT_DOUBLE_12;
      match = true;
    }

    std::string timeFormatSingle12Meridiem = ToTimeFormat(false, true, true);
    list.emplace_back(ToSettingTimeFormat(now, timeFormatSingle12Meridiem), timeFormatSingle12Meridiem);
    if (timeFormatSetting == timeFormatSingle12Meridiem)
    {
      current = timeFormatSingle12Meridiem;
      match = true;
    }

    std::string timeFormatDouble12Meridiem = ToTimeFormat(false, false, true);
    list.emplace_back(ToSettingTimeFormat(now, timeFormatDouble12Meridiem), timeFormatDouble12Meridiem);
    if (timeFormatSetting == timeFormatDouble12Meridiem)
    {
      current = timeFormatDouble12Meridiem;
      match = true;
    }
  }

  if (!match && !list.empty())
    current = list[0].value;
}

void CLangInfo::SettingOptions24HourClockFormatsFiller(const SettingConstPtr& setting,
                                                       std::vector<StringSettingOption>& list,
                                                       std::string& current,
                                                       const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& clock24HourFormatSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  // determine the 24-hour clock format of the regional setting
  int regionalClock24HourFormatLabel =
      DetermineUse24HourClockFromTimeFormat(langInfo.m_currentRegion->m_strTimeFormat) ? 12384
                                                                                       : 12383;
  list.emplace_back(
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
                          CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(
                              regionalClock24HourFormatLabel)),
      SETTING_REGIONAL_DEFAULT);
  if (clock24HourFormatSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  list.emplace_back(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12383),
                    TIME_FORMAT_12HOURS);
  if (clock24HourFormatSetting == TIME_FORMAT_12HOURS)
  {
    current = TIME_FORMAT_12HOURS;
    match = true;
  }

  list.emplace_back(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(12384),
                    TIME_FORMAT_24HOURS);
  if (clock24HourFormatSetting == TIME_FORMAT_24HOURS)
  {
    current = TIME_FORMAT_24HOURS;
    match = true;
  }

  if (!match)
    current = list[0].value;
}

void CLangInfo::SettingOptionsTemperatureUnitsFiller(const SettingConstPtr& setting,
                                                     std::vector<StringSettingOption>& list,
                                                     std::string& current,
                                                     const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& temperatureUnitSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  list.emplace_back(
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
                          GetTemperatureUnitString(langInfo.m_currentRegion->m_tempUnit)),
      SETTING_REGIONAL_DEFAULT);
  if (temperatureUnitSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  for (const TemperatureInfo& info : temperatureInfo)
  {
    list.emplace_back(GetTemperatureUnitString(info.unit), info.name);

    if (!match && temperatureUnitSetting == info.name)
    {
      match = true;
      current = info.name;
    }
  }

  if (!match && !list.empty())
    current = list[0].value;
}

void CLangInfo::SettingOptionsSpeedUnitsFiller(const SettingConstPtr& setting,
                                               std::vector<StringSettingOption>& list,
                                               std::string& current,
                                               const CLangInfo& langInfo)
{
  bool match = false;
  const std::string& speedUnitSetting = std::static_pointer_cast<const CSettingString>(setting)->GetValue();

  list.emplace_back(
      StringUtils::Format(CServiceBroker::GetResourcesComponent().GetLocalizeStrings().Get(20035),
                          GetSpeedUnitString(langInfo.m_currentRegion->m_speedUnit)),
      SETTING_REGIONAL_DEFAULT);
  if (speedUnitSetting == SETTING_REGIONAL_DEFAULT)
  {
    match = true;
    current = SETTING_REGIONAL_DEFAULT;
  }

  for (const SpeedInfo& info : speedInfo)
  {
    list.emplace_back(GetSpeedUnitString(info.unit), info.name);

    if (!match && speedUnitSetting == info.name)
    {
      match = true;
      current = info.name;
    }
  }

  if (!match && !list.empty())
    current = list[0].value;
}

void CLangInfo::AddLanguages(std::vector<StringSettingOption> &list)
{
  std::vector<std::string> languages = CLangCodeExpander::GetLanguageNames(
      CLangCodeExpander::ISO_639_1, CLangCodeExpander::LANG_LIST::INCLUDE_ADDONS_USERDEFINED);

  std::ranges::transform(languages, std::back_inserter(list), [](const auto& language)
                         { return StringSettingOption{language, language}; });
}
