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

#include <windows.h>

#include <string>
#include <string_view>

#include "base/strings/string_util.h"
#include "base/strings/string_util_win.h"
#include "content/browser/sandbox_support_impl.h"

namespace content {
namespace {
LCID CallLocaleNameToLCID(std::u16string_view locale) {
  CHECK(locale.size() < LOCALE_NAME_MAX_LENGTH);
  return ::LocaleNameToLCID(base::as_wcstr(locale), 0);
}

std::u16string GetLocaleInfoString(LCID lcid,
                                   LCTYPE type,
                                   bool force_defaults) {
  if (force_defaults) {
    type = type | LOCALE_NOUSEROVERRIDE;
  }
  int size_with_nul = ::GetLocaleInfo(lcid, type, 0, 0);
  if (size_with_nul <= 0) {
    return std::u16string();
  }
  std::u16string buffer;
  // basic_string guarantees that `buffer` can be indexed from
  // [0, size], with the requirement that buffer[size] is only set
  // to NUL.
  buffer.resize(size_with_nul - 1);
  ::GetLocaleInfo(lcid, type, base::as_writable_wcstr(buffer.data()),
                  size_with_nul);
  return buffer;
}

std::vector<std::u16string> GetLocaleInfoStrings(LCID lcid,
                                                 base::span<const LCTYPE> types,
                                                 bool force_defaults,
                                                 bool allow_empty) {
  std::vector<std::u16string> strings;
  for (const auto& type : types) {
    auto str = GetLocaleInfoString(lcid, type, force_defaults);
    if (str.empty() && !allow_empty) {
      return {};
    }
    strings.push_back(std::move(str));
  }
  return strings;
}

std::optional<DWORD> GetLocaleInfoDWORD(LCID lcid,
                                        LCTYPE type,
                                        bool force_defaults) {
  DWORD result = 0;
  if (force_defaults) {
    type = type | LOCALE_NOUSEROVERRIDE;
  }
  if (::GetLocaleInfo(lcid, type | LOCALE_RETURN_NUMBER,
                      reinterpret_cast<LPWSTR>(&result),
                      sizeof(DWORD) / sizeof(TCHAR)) > 0) {
    return result;
  }
  return std::nullopt;
}

std::u16string_view LanguageCode(const std::u16string_view locale) {
  size_t dash_pos = locale.find('-');
  if (dash_pos == std::u16string::npos) {
    return locale;
  }
  return locale.substr(0, dash_pos);
}

LCID LCIDFromLocale(const std::u16string& locale, bool force_defaults) {
  auto default_language_code = GetLocaleInfoString(
      LOCALE_USER_DEFAULT, LOCALE_SISO639LANGNAME, force_defaults);
  auto locale_language_code = LanguageCode(locale);
  if (base::EqualsCaseInsensitiveASCII(locale_language_code,
                                       default_language_code)) {
    return LOCALE_USER_DEFAULT;
  }
  return CallLocaleNameToLCID(locale);
}

}  // namespace

SandboxSupportImpl::SandboxSupportImpl() = default;
SandboxSupportImpl::~SandboxSupportImpl() = default;

void SandboxSupportImpl::BindReceiver(
    mojo::PendingReceiver<mojom::SandboxSupport> receiver) {
  receivers_.Add(this, std::move(receiver));
}

void SandboxSupportImpl::LcidAndFirstDayOfWeek(
    const std::u16string& locale,
    const std::u16string& default_language,
    bool force_defaults,
    LcidAndFirstDayOfWeekCallback callback) {
  if (locale.size() > LOCALE_NAME_MAX_LENGTH ||
      default_language.size() > LOCALE_NAME_MAX_LENGTH) {
    receivers_.ReportBadMessage("Locale larger than LOCALE_NAME_MAX_LENGTH.");
    return;
  }
  LCID lcid = LCIDFromLocale(locale, force_defaults);
  if (!lcid) {
    lcid = LCIDFromLocale(default_language, force_defaults);
  }
  auto first_day =
      GetLocaleInfoDWORD(lcid, LOCALE_IFIRSTDAYOFWEEK, force_defaults);
  int first_day_of_week = ((first_day.has_value() ? *first_day : 0) + 1) % 7;
  std::move(callback).Run(lcid, first_day_of_week);
}

// https://learn.microsoft.com/en-us/windows/win32/intl/locale-idigitsubstitution
constexpr inline DWORD kDigitSubstitution0to9 = 1;
// https://learn.microsoft.com/en-us/windows/win32/intl/locale-ineg-constants
constexpr inline DWORD kNegNumberSignPrefix = 1;

void SandboxSupportImpl::DigitsAndSigns(uint32_t lcid,
                                        bool force_defaults,
                                        DigitsAndSignsCallback callback) {
  DWORD digit_substitution =
      GetLocaleInfoDWORD(lcid, LOCALE_IDIGITSUBSTITUTION, force_defaults)
          .value_or(kDigitSubstitution0to9);
  std::u16string digits;
  if (digit_substitution != kDigitSubstitution0to9) {
    digits = GetLocaleInfoString(lcid, LOCALE_SNATIVEDIGITS, force_defaults);
  }
  auto decimal = GetLocaleInfoString(lcid, LOCALE_SDECIMAL, force_defaults);
  auto thousand = GetLocaleInfoString(lcid, LOCALE_STHOUSAND, force_defaults);
  auto negative_sign =
      GetLocaleInfoString(lcid, LOCALE_SNEGATIVESIGN, force_defaults);
  DWORD negnumber = GetLocaleInfoDWORD(lcid, LOCALE_INEGNUMBER, force_defaults)
                        .value_or(kNegNumberSignPrefix);
  std::move(callback).Run(digit_substitution, digits, decimal, thousand,
                          negative_sign, negnumber);
}

void SandboxSupportImpl::LocaleStrings(uint32_t lcid,
                                       bool force_defaults,
                                       LcTypeStrings collection,
                                       LocaleStringsCallback callback) {
  std::vector<std::u16string> result;
  switch (collection) {
    case LcTypeStrings::kMonths: {
      static constexpr LCTYPE kTypes[12] = {
          LOCALE_SMONTHNAME1,  LOCALE_SMONTHNAME2,  LOCALE_SMONTHNAME3,
          LOCALE_SMONTHNAME4,  LOCALE_SMONTHNAME5,  LOCALE_SMONTHNAME6,
          LOCALE_SMONTHNAME7,  LOCALE_SMONTHNAME8,  LOCALE_SMONTHNAME9,
          LOCALE_SMONTHNAME10, LOCALE_SMONTHNAME11, LOCALE_SMONTHNAME12,
      };
      result = GetLocaleInfoStrings(lcid, kTypes, force_defaults, false);
      break;
    }
    case LcTypeStrings::kShortMonths: {
      static constexpr LCTYPE kTypes[12] = {
          LOCALE_SABBREVMONTHNAME1,  LOCALE_SABBREVMONTHNAME2,
          LOCALE_SABBREVMONTHNAME3,  LOCALE_SABBREVMONTHNAME4,
          LOCALE_SABBREVMONTHNAME5,  LOCALE_SABBREVMONTHNAME6,
          LOCALE_SABBREVMONTHNAME7,  LOCALE_SABBREVMONTHNAME8,
          LOCALE_SABBREVMONTHNAME9,  LOCALE_SABBREVMONTHNAME10,
          LOCALE_SABBREVMONTHNAME11, LOCALE_SABBREVMONTHNAME12,
      };
      result = GetLocaleInfoStrings(lcid, kTypes, force_defaults, false);
      break;
    }
    case LcTypeStrings::kShortWeekDays: {
      static constexpr LCTYPE kTypes[7] = {
          // Numbered 1 (Monday) - 7 (Sunday), so do 7, then 1-6
          LOCALE_SSHORTESTDAYNAME7, LOCALE_SSHORTESTDAYNAME1,
          LOCALE_SSHORTESTDAYNAME2, LOCALE_SSHORTESTDAYNAME3,
          LOCALE_SSHORTESTDAYNAME4, LOCALE_SSHORTESTDAYNAME5,
          LOCALE_SSHORTESTDAYNAME6};
      result = GetLocaleInfoStrings(lcid, kTypes, force_defaults, false);
      break;
    }
    case LcTypeStrings::kAmPm: {
      static constexpr LCTYPE kTypes[2] = {
          LOCALE_S1159,
          LOCALE_S2359,
      };
      result = GetLocaleInfoStrings(lcid, kTypes, force_defaults, true);
      break;
    }
  }

  std::move(callback).Run(result);
}

void SandboxSupportImpl::LocaleString(uint32_t lcid,
                                      bool force_defaults,
                                      LcTypeString type,
                                      LocaleStringCallback callback) {
  LCTYPE lctype;
  switch (type) {
    case LcTypeString::kShortDate:
      lctype = LOCALE_SSHORTDATE;
      break;
    case LcTypeString::kYearMonth:
      lctype = LOCALE_SYEARMONTH;
      break;
    case LcTypeString::kTimeFormat:
      lctype = LOCALE_STIMEFORMAT;
      break;
    case LcTypeString::kShortTime:
      lctype = LOCALE_SSHORTTIME;
      break;
  }
  auto result = GetLocaleInfoString(lcid, lctype, force_defaults);
  std::move(callback).Run(result);
}

}  // namespace content