/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { getAddonAndLocalAPIsMocker } = ChromeUtils.importESModule(
  "resource://testing-common/LangPackMatcherTestUtils.sys.mjs"
);
const { LangPackMatcher } = ChromeUtils.importESModule(
  "resource://gre/modules/LangPackMatcher.sys.mjs"
);
const { sinon } = ChromeUtils.importESModule(
  "resource://testing-common/Sinon.sys.mjs"
);

const sandbox = sinon.createSandbox();
const mockAddonAndLocaleAPIs = getAddonAndLocalAPIsMocker(this, sandbox);

add_task(function initSandbox() {
  registerCleanupFunction(() => {
    sandbox.restore();
  });
});

add_task(function test_appLocaleLanguageMismatch() {
  sandbox.restore();
  mockAddonAndLocaleAPIs({
    systemLocale: "es-ES",
    appLocale: "en-US",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "es-ES",
    systemLocale: { baseName: "es-ES", language: "es", region: "ES" },
    appLocaleRaw: "en-US",
    appLocale: { baseName: "en-US", language: "en", region: "US" },
    matchType: "language-mismatch",
    canLiveReload: true,
    displayNames: {
      systemLanguage: "Español (ES)",
      appLanguage: "English (US)",
    },
  });
});

add_task(function test_appLocaleRegionMismatch() {
  sandbox.restore();
  mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "en-CA",
    appLocale: "en-US",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "en-CA",
    systemLocale: { baseName: "en-CA", language: "en", region: "CA" },
    appLocaleRaw: "en-US",
    appLocale: { baseName: "en-US", language: "en", region: "US" },
    matchType: "region-mismatch",
    canLiveReload: true,
    displayNames: {
      systemLanguage: "English (CA)",
      appLanguage: "English (US)",
    },
  });
});

add_task(function test_appLocaleScriptMismatch() {
  sandbox.restore();
  // Script mismatch:
  mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "zh-Hans-CN",
    appLocale: "zh-CN",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "zh-Hans-CN",
    systemLocale: { baseName: "zh-Hans-CN", language: "zh", region: "CN" },
    appLocaleRaw: "zh-CN",
    appLocale: { baseName: "zh-CN", language: "zh", region: "CN" },
    matchType: "match",
    canLiveReload: true,
    displayNames: {
      systemLanguage: "Chinese (Hans, China)",
      appLanguage: "简体中文",
    },
  });
});

add_task(function test_appLocaleInvalidSystem() {
  sandbox.restore();
  // Script mismatch:
  mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "Not valid",
    appLocale: "en-US",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "Not valid",
    systemLocale: null,
    appLocaleRaw: "en-US",
    appLocale: { baseName: "en-US", language: "en", region: "US" },
    matchType: "unknown",
    canLiveReload: null,
    displayNames: { systemLanguage: null, appLanguage: "English (US)" },
  });
});

add_task(function test_bidiSwitchDisabled() {
  Services.prefs.setBoolPref(
    "intl.multilingual.liveReloadBidirectional",
    false
  );
  sandbox.restore();
  // Script mismatch:
  mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "ar-EG",
    appLocale: "en-US",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "ar-EG",
    systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
    appLocaleRaw: "en-US",
    appLocale: { baseName: "en-US", language: "en", region: "US" },
    matchType: "language-mismatch",
    canLiveReload: false,
    displayNames: {
      systemLanguage: "Arabic (Egypt)",
      appLanguage: "English (US)",
    },
  });
  Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
});

add_task(async function test_bidiSwitchEnabled() {
  Services.prefs.setBoolPref("intl.multilingual.liveReloadBidirectional", true);
  sandbox.restore();
  // Script mismatch:
  mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "ar-EG",
    appLocale: "en-US",
  });

  deepEqual(LangPackMatcher.getAppAndSystemLocaleInfo(), {
    systemLocaleRaw: "ar-EG",
    systemLocale: { baseName: "ar-EG", language: "ar", region: "EG" },
    appLocaleRaw: "en-US",
    appLocale: { baseName: "en-US", language: "en", region: "US" },
    matchType: "language-mismatch",
    canLiveReload: true,
    displayNames: {
      systemLanguage: "Arabic (Egypt)",
      appLanguage: "English (US)",
    },
  });

  Services.prefs.clearUserPref("intl.multilingual.liveReloadBidirectional");
});

function shuffle(array) {
  return array
    .map(value => ({ value, sort: Math.random() }))
    .sort((a, b) => a.sort - b.sort)
    .map(({ value }) => value);
}

add_task(async function test_negotiateLangPacks() {
  const negotiations = [
    {
      // Exact match found.
      systemLocale: "en-US",
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: "en-US",
      expectedDisplayName: "English (US)",
    },
    {
      // Region-less match.
      systemLocale: "en-CA",
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: "en",
      expectedDisplayName: "English",
    },
    {
      // Fallback to a different region.
      systemLocale: "en-CA",
      availableLangPacks: ["en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: "en-US",
      expectedDisplayName: "English (US)",
    },
    {
      // Match with a script. zh-Hans-CN is the locale used with simplified
      // Chinese scripts, while zh-CN uses the Latin script.
      systemLocale: "zh-Hans-CN",
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-TW", "zh-Hans-CN"],
      expectedLangPack: "zh-Hans-CN",
      expectedDisplayName: "Chinese (Hans, China)",
    },
    {
      // Match excluding script but matching region. zh-Hant-TW should
      // match zh-TW before zh-CN.
      systemLocale: "zh-Hant-TW",
      availableLangPacks: ["en", "zh", "zh-CN", "zh-TW"],
      expectedLangPack: "zh-TW",
      expectedDisplayName: "正體中文",
    },
    {
      // No reasonable match could be found.
      systemLocale: "tlh", // Klingon
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: null,
      expectedDisplayName: null,
    },
    {
      // Weird, but valid locale identifiers.
      systemLocale: "en-US-u-hc-h23-ca-islamic-civil-ss-true",
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: "en-US",
      expectedDisplayName: "English (US)",
    },
    {
      // Invalid system locale
      systemLocale: "Not valid",
      availableLangPacks: ["en", "en-US", "zh", "zh-CN", "zh-Hans-CN"],
      expectedLangPack: null,
      expectedDisplayName: null,
    },
  ];

  for (const {
    systemLocale,
    availableLangPacks,
    expectedLangPack,
    expectedDisplayName,
  } of negotiations) {
    sandbox.restore();
    const { resolveLangPacks } = mockAddonAndLocaleAPIs({
      sandbox,
      systemLocale,
    });

    const promise = LangPackMatcher.negotiateLangPackForLanguageMismatch();
    // Shuffle the order to ensure that this test doesn't require on ordering of the
    // langpack responses.
    resolveLangPacks(shuffle(availableLangPacks));
    const { langPack, langPackDisplayName } = await promise;
    equal(
      langPack?.target_locale,
      expectedLangPack,
      `Resolve the systemLocale "${systemLocale}" with available langpacks: ${JSON.stringify(
        availableLangPacks
      )}`
    );
    equal(
      langPackDisplayName,
      expectedDisplayName,
      "The display name matches."
    );
  }
});

add_task(async function test_ensureLangPackInstalled() {
  sandbox.restore();
  const { resolveLangPacks, resolveInstaller } = mockAddonAndLocaleAPIs({
    sandbox,
    systemLocale: "es-ES",
    appLocale: "en-US",
  });

  const negotiatePromise =
    LangPackMatcher.negotiateLangPackForLanguageMismatch();
  resolveLangPacks(["es-ES"]);
  const { langPack } = await negotiatePromise;

  const installPromise1 = LangPackMatcher.ensureLangPackInstalled(langPack);
  const installPromise2 = LangPackMatcher.ensureLangPackInstalled(langPack);

  resolveInstaller(["fake langpack"]);

  info("Ensure both installers resolve when called twice in a row.");
  await installPromise1;
  await installPromise2;
  ok(true, "Both were called.");
});