// Copyright 2010 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.i18n.MessageFormatTest');
goog.setTestOnly('goog.i18n.MessageFormatTest');

goog.require('goog.i18n.MessageFormat');
goog.require('goog.i18n.NumberFormatSymbols_hr');
goog.require('goog.i18n.pluralRules');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.jsunit');

// Testing stubs that autoreset after each test run.
var stubs = new goog.testing.PropertyReplacer();

function tearDown() {
  stubs.reset();
}

function testEmptyPattern() {
  var fmt = new goog.i18n.MessageFormat('');
  assertEquals('', fmt.format({}));
}

function testMissingLeftCurlyBrace() {
  var err =
      assertThrows(function() { new goog.i18n.MessageFormat('\'\'{}}'); });
  assertEquals('Assertion failed: No matching { for }.', err.message);
}

function testTooManyLeftCurlyBraces() {
  var err = assertThrows(function() { new goog.i18n.MessageFormat('{} {'); });
  assertEquals(
      'Assertion failed: There are mismatched { or } in the pattern.',
      err.message);
}

function testSimpleReplacement() {
  var fmt = new goog.i18n.MessageFormat('New York in {SEASON} is nice.');
  assertEquals(
      'New York in the Summer is nice.', fmt.format({'SEASON': 'the Summer'}));
}

function testSimpleSelect() {
  var fmt = new goog.i18n.MessageFormat(
      '{GENDER, select,' +
      'male {His} ' +
      'female {Her} ' +
      'other {Its}}' +
      ' bicycle is {GENDER, select, male {blue} female {red} other {green}}.');

  assertEquals('His bicycle is blue.', fmt.format({'GENDER': 'male'}));
  assertEquals('Her bicycle is red.', fmt.format({'GENDER': 'female'}));
  assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'other'}));
  assertEquals('Its bicycle is green.', fmt.format({'GENDER': 'whatever'}));
}

function testSimplePlural() {
  var fmt = new goog.i18n.MessageFormat(
      'I see {NUM_PEOPLE, plural, offset:1 ' +
      '=0 {no one at all in {PLACE}.} ' +
      '=1 {{PERSON} in {PLACE}.} ' +
      'one {{PERSON} and one other person in {PLACE}.} ' +
      'other {{PERSON} and # other people in {PLACE}.}}');

  assertEquals(
      'I see no one at all in Belgrade.',
      fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'}));
  assertEquals(
      'I see Markus in Berlin.',
      fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'}));
  assertEquals(
      'I see Mark and one other person in Athens.',
      fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'}));
  assertEquals(
      'I see Cibu and 99 other people in the cubes.',
      fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
}

function testSimplePluralNoOffset() {
  var fmt = new goog.i18n.MessageFormat(
      'I see {NUM_PEOPLE, plural, ' +
      '=0 {no one at all} ' +
      '=1 {{PERSON}} ' +
      'one {{PERSON} and one other person} ' +
      'other {{PERSON} and # other people}} in {PLACE}.');

  assertEquals(
      'I see no one at all in Belgrade.',
      fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Belgrade'}));
  assertEquals(
      'I see Markus in Berlin.',
      fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markus', 'PLACE': 'Berlin'}));
  assertEquals(
      'I see Mark and 2 other people in Athens.',
      fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Mark', 'PLACE': 'Athens'}));
  assertEquals(
      'I see Cibu and 100 other people in the cubes.',
      fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
}

function testSelectNestedInPlural() {
  var fmt = new goog.i18n.MessageFormat(
      '{CIRCLES, plural, ' +
      'one {{GENDER, select, ' +
      '  female {{WHO} added you to her circle} ' +
      '  other  {{WHO} added you to his circle}}} ' +
      'other {{GENDER, select, ' +
      '  female {{WHO} added you to her # circles} ' +
      '  other  {{WHO} added you to his # circles}}}}');

  assertEquals(
      'Jelena added you to her circle',
      fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 1}));
  assertEquals(
      'Milan added you to his 1,234 circles',
      fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1234}));
}

function testPluralNestedInSelect() {
  // Added offset just for testing purposes. It doesn't make sense
  // to have it otherwise.
  var fmt = new goog.i18n.MessageFormat(
      '{GENDER, select, ' +
      'female {{NUM_GROUPS, plural, ' +
      '  one {{WHO} added you to her group} ' +
      '  other {{WHO} added you to her # groups}}} ' +
      'other {{NUM_GROUPS, plural, offset:1' +
      '  one {{WHO} added you to his group} ' +
      '  other {{WHO} added you to his # groups}}}}');

  assertEquals(
      'Jelena added you to her group',
      fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'NUM_GROUPS': 1}));
  assertEquals(
      'Milan added you to his 1,233 groups',
      fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'NUM_GROUPS': 1234}));
}

function testLiteralOpenCurlyBrace() {
  var fmt = new goog.i18n.MessageFormat(
      "Anna's house" + " has '{0} and # in the roof' and {NUM_COWS} cows.");
  assertEquals(
      "Anna's house has {0} and # in the roof and 5 cows.",
      fmt.format({'NUM_COWS': '5'}));
}

function testLiteralClosedCurlyBrace() {
  var fmt = new goog.i18n.MessageFormat(
      "Anna's house" + " has '{'0'} and # in the roof' and {NUM_COWS} cows.");
  assertEquals(
      "Anna's house has {0} and # in the roof and 5 cows.",
      fmt.format({'NUM_COWS': '5'}));
}

function testLiteralPoundSign() {
  var fmt = new goog.i18n.MessageFormat(
      "Anna's house" + " has '{0}' and '# in the roof' and {NUM_COWS} cows.");
  assertEquals(
      "Anna's house has {0} and # in the roof and 5 cows.",
      fmt.format({'NUM_COWS': '5'}));
}

function testNoLiteralsForSingleQuotes() {
  var fmt =
      new goog.i18n.MessageFormat("Anna's house" + " 'has {NUM_COWS} cows'.");
  assertEquals("Anna's house 'has 5 cows'.", fmt.format({'NUM_COWS': '5'}));
}

function testConsecutiveSingleQuotesAreReplacedWithOneSingleQuote() {
  var fmt = new goog.i18n.MessageFormat("Anna''s house a'{''''b'");
  assertEquals("Anna's house a{''b", fmt.format({}));
}

function testConsecutiveSingleQuotesBeforeSpecialCharDontCreateLiteral() {
  var fmt = new goog.i18n.MessageFormat("a''{NUM_COWS}'b");
  assertEquals("a'5'b", fmt.format({'NUM_COWS': '5'}));
}

function testSerbianSimpleSelect() {
  stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);

  var fmt = new goog.i18n.MessageFormat(
      '{GENDER, select, ' +
      'female {Njen} other {Njegov}} bicikl je ' +
      '{GENDER, select, female {crven} other {plav}}.');

  assertEquals('Njegov bicikl je plav.', fmt.format({'GENDER': 'male'}));
  assertEquals('Njen bicikl je crven.', fmt.format({'GENDER': 'female'}));
}

function testSerbianSimplePlural() {
  stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);

  var fmt = new goog.i18n.MessageFormat(
      'Ja {NUM_PEOPLE, plural, offset:1 ' +
      '=0 {ne vidim nikoga} ' +
      '=1 {vidim {PERSON}} ' +
      'one {vidim {PERSON} i jos # osobu} ' +
      'few {vidim {PERSON} i jos # osobe} ' +
      'many {vidim {PERSON} i jos # osoba} ' +
      'other {{PERSON} i jos # osoba}} ' +
      'u {PLACE}.');

  assertEquals(
      'Ja ne vidim nikoga u Beogradu.',
      fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'}));
  assertEquals(
      'Ja vidim Markusa u Berlinu.',
      fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'}));
  assertEquals(
      'Ja vidim Marka i jos 1 osobu u Atini.',
      fmt.format({'NUM_PEOPLE': 2, 'PERSON': 'Marka', 'PLACE': 'Atini'}));
  assertEquals(
      'Ja vidim Petra i jos 3 osobe u muzeju.',
      fmt.format({'NUM_PEOPLE': 4, 'PERSON': 'Petra', 'PLACE': 'muzeju'}));
  assertEquals(
      'Ja vidim Cibua i jos 99 osoba u bazenu.',
      fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'}));
}

function testSerbianSimplePluralNoOffset() {
  stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);

  var fmt = new goog.i18n.MessageFormat(
      'Ja {NUM_PEOPLE, plural, ' +
      '=0 {ne vidim nikoga} ' +
      '=1 {vidim {PERSON}} ' +
      'one {vidim {PERSON} i jos # osobu} ' +
      'few {vidim {PERSON} i jos # osobe} ' +
      'many {vidim {PERSON} i jos # osoba} ' +
      'other {{PERSON} i jos # osoba}} ' +
      'u {PLACE}.');

  assertEquals(
      'Ja ne vidim nikoga u Beogradu.',
      fmt.format({'NUM_PEOPLE': 0, 'PLACE': 'Beogradu'}));
  assertEquals(
      'Ja vidim Markusa u Berlinu.',
      fmt.format({'NUM_PEOPLE': 1, 'PERSON': 'Markusa', 'PLACE': 'Berlinu'}));
  assertEquals(
      'Ja vidim Marka i jos 21 osobu u Atini.',
      fmt.format({'NUM_PEOPLE': 21, 'PERSON': 'Marka', 'PLACE': 'Atini'}));
  assertEquals(
      'Ja vidim Petra i jos 3 osobe u muzeju.',
      fmt.format({'NUM_PEOPLE': 3, 'PERSON': 'Petra', 'PLACE': 'muzeju'}));
  assertEquals(
      'Ja vidim Cibua i jos 100 osoba u bazenu.',
      fmt.format({'NUM_PEOPLE': 100, 'PERSON': 'Cibua', 'PLACE': 'bazenu'}));
}

function testSerbianSelectNestedInPlural() {
  stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.beSelect_);
  stubs.set(goog.i18n, 'NumberFormatSymbols', goog.i18n.NumberFormatSymbols_hr);

  var fmt = new goog.i18n.MessageFormat(
      '{CIRCLES, plural, ' +
      'one {{GENDER, select, ' +
      '  female {{WHO} vas je dodala u njen # kruzok} ' +
      '  other  {{WHO} vas je dodao u njegov # kruzok}}} ' +
      'few {{GENDER, select, ' +
      '  female {{WHO} vas je dodala u njena # kruzoka} ' +
      '  other  {{WHO} vas je dodao u njegova # kruzoka}}} ' +
      'many {{GENDER, select, ' +
      '  female {{WHO} vas je dodala u njenih # kruzoka} ' +
      '  other  {{WHO} vas je dodao u njegovih # kruzoka}}} ' +
      'other {{GENDER, select, ' +
      '  female {{WHO} vas je dodala u njenih # kruzoka} ' +
      '  other  {{WHO} vas je dodao u njegovih # kruzoka}}}}');

  assertEquals(
      'Jelena vas je dodala u njen 21 kruzok',
      fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 21}));
  assertEquals(
      'Jelena vas je dodala u njena 3 kruzoka',
      fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 3}));
  assertEquals(
      'Jelena vas je dodala u njenih 5 kruzoka',
      fmt.format({'GENDER': 'female', 'WHO': 'Jelena', 'CIRCLES': 5}));
  assertEquals(
      'Milan vas je dodao u njegovih 1.235 kruzoka',
      fmt.format({'GENDER': 'male', 'WHO': 'Milan', 'CIRCLES': 1235}));
}

function testFallbackToOtherOptionInPlurals() {
  // Use Arabic plural rules since they have all six cases.
  // Only locale and numbers matter, the actual language of the message
  // does not.
  stubs.set(goog.i18n.pluralRules, 'select', goog.i18n.pluralRules.arSelect_);

  var fmt = new goog.i18n.MessageFormat(
      '{NUM_MINUTES, plural, ' +
      'other {# minutes}}');

  // These numbers exercise all cases for the arabic plural rules.
  assertEquals('0 minutes', fmt.format({'NUM_MINUTES': 0}));
  assertEquals('1 minutes', fmt.format({'NUM_MINUTES': 1}));
  assertEquals('2 minutes', fmt.format({'NUM_MINUTES': 2}));
  assertEquals('3 minutes', fmt.format({'NUM_MINUTES': 3}));
  assertEquals('11 minutes', fmt.format({'NUM_MINUTES': 11}));
  assertEquals('1.5 minutes', fmt.format({'NUM_MINUTES': 1.5}));
}

function testPoundShowsNumberMinusOffsetInAllCases() {
  var fmt = new goog.i18n.MessageFormat(
      '{SOME_NUM, plural, offset:1 ' +
      '=0 {#} =1 {#} =2 {#}one {#} other {#}}');

  assertEquals('-1', fmt.format({'SOME_NUM': '0'}));
  assertEquals('0', fmt.format({'SOME_NUM': '1'}));
  assertEquals('1', fmt.format({'SOME_NUM': '2'}));
  assertEquals('20', fmt.format({'SOME_NUM': '21'}));
}

function testSpecialCharactersInParamaterDontChangeFormat() {
  var fmt = new goog.i18n.MessageFormat(
      '{SOME_NUM, plural,' +
      'other {# {GROUP}}}');

  // Test pound sign.
  assertEquals(
      '10 group#1', fmt.format({'SOME_NUM': '10', 'GROUP': 'group#1'}));
  // Test other special characters in parameters, like { and }.
  assertEquals('10 } {', fmt.format({'SOME_NUM': '10', 'GROUP': '} {'}));
}

function testMissingOrInvalidPluralParameter() {
  var fmt = new goog.i18n.MessageFormat(
      '{SOME_NUM, plural,' +
      'other {result}}');

  // Key name doesn't match A != SOME_NUM.
  assertEquals(
      'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'}));

  // Value is not a number.
  assertEquals(
      'Undefined or invalid parameter - SOME_NUM',
      fmt.format({'SOME_NUM': 'Value'}));
}

function testMissingSelectParameter() {
  var fmt = new goog.i18n.MessageFormat(
      '{GENDER, select,' +
      'other {result}}');

  // Key name doesn't match A != GENDER.
  assertEquals('Undefined parameter - GENDER', fmt.format({A: 'female'}));
}

function testMissingSimplePlaceholder() {
  var fmt = new goog.i18n.MessageFormat('{result}');

  // Key name doesn't match A != result.
  assertEquals('Undefined parameter - result', fmt.format({A: 'none'}));
}

function testPluralWithIgnorePound() {
  var fmt = new goog.i18n.MessageFormat(
      '{SOME_NUM, plural,' +
      'other {# {GROUP}}}');

  // Test pound sign.
  assertEquals(
      '# group#1',
      fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': 'group#1'}));
  // Test other special characters in parameters, like { and }.
  assertEquals(
      '# } {', fmt.formatIgnoringPound({'SOME_NUM': '10', 'GROUP': '} {'}));
}

function testSimplePluralWithIgnorePound() {
  var fmt = new goog.i18n.MessageFormat(
      'I see {NUM_PEOPLE, plural, offset:1 ' +
      '=0 {no one at all in {PLACE}.} ' +
      '=1 {{PERSON} in {PLACE}.} ' +
      'one {{PERSON} and one other person in {PLACE}.} ' +
      'other {{PERSON} and # other people in {PLACE}.}}');

  assertEquals(
      'I see Cibu and # other people in the cubes.',
      fmt.formatIgnoringPound(
          {'NUM_PEOPLE': 100, 'PERSON': 'Cibu', 'PLACE': 'the cubes'}));
}

function testSimpleOrdinal() {
  var fmt = new goog.i18n.MessageFormat(
      '{NUM_FLOOR, selectordinal, ' +
      'one {Take the elevator to the #st floor.}' +
      'two {Take the elevator to the #nd floor.}' +
      'few {Take the elevator to the #rd floor.}' +
      'other {Take the elevator to the #th floor.}}');

  assertEquals(
      'Take the elevator to the 1st floor.', fmt.format({'NUM_FLOOR': 1}));
  assertEquals(
      'Take the elevator to the 2nd floor.', fmt.format({'NUM_FLOOR': 2}));
  assertEquals(
      'Take the elevator to the 3rd floor.', fmt.format({'NUM_FLOOR': 3}));
  assertEquals(
      'Take the elevator to the 4th floor.', fmt.format({'NUM_FLOOR': 4}));
  assertEquals(
      'Take the elevator to the 23rd floor.', fmt.format({'NUM_FLOOR': 23}));
  // Esoteric example.
  assertEquals(
      'Take the elevator to the 0th floor.', fmt.format({'NUM_FLOOR': 0}));
}

function testOrdinalWithNegativeValue() {
  var fmt = new goog.i18n.MessageFormat(
      '{NUM_FLOOR, selectordinal, ' +
      'one {Take the elevator to the #st floor.}' +
      'two {Take the elevator to the #nd floor.}' +
      'few {Take the elevator to the #rd floor.}' +
      'other {Take the elevator to the #th floor.}}');

  try {
    fmt.format({'NUM_FLOOR': -2});
  } catch (e) {
    assertEquals(
        'Assertion failed: Argument index smaller than offset.', e.message);
    return;
  }
  fail('Expected an error to be thrown');
}

function testSimpleOrdinalWithIgnorePound() {
  var fmt = new goog.i18n.MessageFormat(
      '{NUM_FLOOR, selectordinal, ' +
      'one {Take the elevator to the #st floor.}' +
      'two {Take the elevator to the #nd floor.}' +
      'few {Take the elevator to the #rd floor.}' +
      'other {Take the elevator to the #th floor.}}');

  assertEquals(
      'Take the elevator to the #th floor.',
      fmt.formatIgnoringPound({'NUM_FLOOR': 100}));
}

function testMissingOrInvalidOrdinalParameter() {
  var fmt = new goog.i18n.MessageFormat(
      '{SOME_NUM, selectordinal,' +
      'other {result}}');

  // Key name doesn't match A != SOME_NUM.
  assertEquals(
      'Undefined or invalid parameter - SOME_NUM', fmt.format({A: '10'}));

  // Value is not a number.
  assertEquals(
      'Undefined or invalid parameter - SOME_NUM',
      fmt.format({'SOME_NUM': 'Value'}));
}
