// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter_tools/src/localizations/gen_l10n_types.dart';
import 'package:flutter_tools/src/localizations/message_parser.dart';
import '../src/common.dart';

void main() {
  // Going to test that operator== is overloaded properly since the rest
  // of the test depends on it.
  testWithoutContext('node equality', () {
    final actual = Node(
      ST.placeholderExpr,
      0,
      expectedSymbolCount: 3,
      children: <Node>[Node.openBrace(0), Node.string(1, 'var'), Node.closeBrace(4)],
    );

    final expected = Node(
      ST.placeholderExpr,
      0,
      expectedSymbolCount: 3,
      children: <Node>[Node.openBrace(0), Node.string(1, 'var'), Node.closeBrace(4)],
    );
    expect(actual, equals(expected));

    final wrongType = Node(
      ST.pluralExpr,
      0,
      expectedSymbolCount: 3,
      children: <Node>[Node.openBrace(0), Node.string(1, 'var'), Node.closeBrace(4)],
    );
    expect(actual, isNot(equals(wrongType)));

    final wrongPosition = Node(
      ST.placeholderExpr,
      1,
      expectedSymbolCount: 3,
      children: <Node>[Node.openBrace(0), Node.string(1, 'var'), Node.closeBrace(4)],
    );
    expect(actual, isNot(equals(wrongPosition)));

    final wrongChildrenCount = Node(
      ST.placeholderExpr,
      0,
      expectedSymbolCount: 3,
      children: <Node>[Node.string(1, 'var'), Node.closeBrace(4)],
    );
    expect(actual, isNot(equals(wrongChildrenCount)));

    final wrongChild = Node(
      ST.placeholderExpr,
      0,
      expectedSymbolCount: 3,
      children: <Node>[Node.closeBrace(0), Node.string(1, 'var'), Node.closeBrace(4)],
    );
    expect(actual, isNot(equals(wrongChild)));
  });

  testWithoutContext('lexer basic', () {
    final List<Node> tokens1 = Parser('helloWorld', 'app_en.arb', 'Hello {name}').lexIntoTokens();
    expect(
      tokens1,
      equals(<Node>[
        Node.string(0, 'Hello '),
        Node.openBrace(6),
        Node.identifier(7, 'name'),
        Node.closeBrace(11),
      ]),
    );

    final List<Node> tokens2 = Parser(
      'plural',
      'app_en.arb',
      'There are {count} {count, plural, =1{cat} other{cats}}',
    ).lexIntoTokens();
    expect(
      tokens2,
      equals(<Node>[
        Node.string(0, 'There are '),
        Node.openBrace(10),
        Node.identifier(11, 'count'),
        Node.closeBrace(16),
        Node.string(17, ' '),
        Node.openBrace(18),
        Node.identifier(19, 'count'),
        Node.comma(24),
        Node.pluralKeyword(26),
        Node.comma(32),
        Node.equalSign(34),
        Node.number(35, '1'),
        Node.openBrace(36),
        Node.string(37, 'cat'),
        Node.closeBrace(40),
        Node.otherKeyword(42),
        Node.openBrace(47),
        Node.string(48, 'cats'),
        Node.closeBrace(52),
        Node.closeBrace(53),
      ]),
    );

    final List<Node> tokens3 = Parser(
      'gender',
      'app_en.arb',
      '{gender, select, male{he} female{she} other{they}}',
    ).lexIntoTokens();
    expect(
      tokens3,
      equals(<Node>[
        Node.openBrace(0),
        Node.identifier(1, 'gender'),
        Node.comma(7),
        Node.selectKeyword(9),
        Node.comma(15),
        Node.identifier(17, 'male'),
        Node.openBrace(21),
        Node.string(22, 'he'),
        Node.closeBrace(24),
        Node.identifier(26, 'female'),
        Node.openBrace(32),
        Node.string(33, 'she'),
        Node.closeBrace(36),
        Node.otherKeyword(38),
        Node.openBrace(43),
        Node.string(44, 'they'),
        Node.closeBrace(48),
        Node.closeBrace(49),
      ]),
    );
  });

  testWithoutContext('lexer recursive', () {
    final List<Node> tokens = Parser(
      'plural',
      'app_en.arb',
      '{count, plural, =1{{gender, select, male{he} female{she}}} other{they}}',
    ).lexIntoTokens();
    expect(
      tokens,
      equals(<Node>[
        Node.openBrace(0),
        Node.identifier(1, 'count'),
        Node.comma(6),
        Node.pluralKeyword(8),
        Node.comma(14),
        Node.equalSign(16),
        Node.number(17, '1'),
        Node.openBrace(18),
        Node.openBrace(19),
        Node.identifier(20, 'gender'),
        Node.comma(26),
        Node.selectKeyword(28),
        Node.comma(34),
        Node.identifier(36, 'male'),
        Node.openBrace(40),
        Node.string(41, 'he'),
        Node.closeBrace(43),
        Node.identifier(45, 'female'),
        Node.openBrace(51),
        Node.string(52, 'she'),
        Node.closeBrace(55),
        Node.closeBrace(56),
        Node.closeBrace(57),
        Node.otherKeyword(59),
        Node.openBrace(64),
        Node.string(65, 'they'),
        Node.closeBrace(69),
        Node.closeBrace(70),
      ]),
    );
  });

  testWithoutContext('lexer escaping', () {
    final List<Node> tokens1 = Parser(
      'escaping',
      'app_en.arb',
      "''",
      useEscaping: true,
    ).lexIntoTokens();
    expect(tokens1, equals(<Node>[Node.string(0, "'")]));

    final List<Node> tokens2 = Parser(
      'escaping',
      'app_en.arb',
      "'hello world { name }'",
      useEscaping: true,
    ).lexIntoTokens();
    expect(tokens2, equals(<Node>[Node.string(0, 'hello world { name }')]));

    final List<Node> tokens3 = Parser(
      'escaping',
      'app_en.arb',
      "'{ escaped string }' { not escaped }",
      useEscaping: true,
    ).lexIntoTokens();
    expect(
      tokens3,
      equals(<Node>[
        Node.string(0, '{ escaped string }'),
        Node.string(20, ' '),
        Node.openBrace(21),
        Node.identifier(23, 'not'),
        Node.identifier(27, 'escaped'),
        Node.closeBrace(35),
      ]),
    );

    final List<Node> tokens4 = Parser(
      'escaping',
      'app_en.arb',
      "Flutter''s amazing!",
      useEscaping: true,
    ).lexIntoTokens();
    expect(
      tokens4,
      equals(<Node>[Node.string(0, 'Flutter'), Node.string(7, "'"), Node.string(9, 's amazing!')]),
    );

    final List<Node> tokens5 = Parser(
      'escaping',
      'app_en.arb',
      "'Flutter''s amazing!'",
      useEscaping: true,
    ).lexIntoTokens();
    expect(
      tokens5,
      equals(<Node>[
        Node(ST.string, 0, value: 'Flutter'),
        Node(ST.string, 9, value: "'s amazing!"),
      ]),
    );
  });

  testWithoutContext('lexer identifier names can be "select" or "plural"', () {
    final List<Node> tokens = Parser(
      'keywords',
      'app_en.arb',
      '{ select } { plural, select, singular{test} other{hmm} }',
    ).lexIntoTokens();
    expect(tokens[1].value, equals('select'));
    expect(tokens[1].type, equals(ST.identifier));
    expect(tokens[5].value, equals('plural'));
    expect(tokens[5].type, equals(ST.identifier));
  });

  testWithoutContext('lexer identifier names can contain underscores', () {
    final List<Node> tokens = Parser(
      'keywords',
      'app_en.arb',
      '{ test_placeholder } { test_select, select, singular{test} other{hmm} }',
    ).lexIntoTokens();
    expect(tokens[1].value, equals('test_placeholder'));
    expect(tokens[1].type, equals(ST.identifier));
    expect(tokens[5].value, equals('test_select'));
    expect(tokens[5].type, equals(ST.identifier));
  });

  testWithoutContext('lexer identifier names can contain the strings select or plural', () {
    final List<Node> tokens = Parser(
      'keywords',
      'app_en.arb',
      '{ selectTest } { pluralTest, select, singular{test} other{hmm} }',
    ).lexIntoTokens();
    expect(tokens[1].value, equals('selectTest'));
    expect(tokens[1].type, equals(ST.identifier));
    expect(tokens[5].value, equals('pluralTest'));
    expect(tokens[5].type, equals(ST.identifier));
  });

  testWithoutContext('lexer: lexically correct but syntactically incorrect', () {
    final List<Node> tokens = Parser(
      'syntax',
      'app_en.arb',
      'string { identifier { string { identifier } } }',
    ).lexIntoTokens();
    expect(
      tokens,
      equals(<Node>[
        Node.string(0, 'string '),
        Node.openBrace(7),
        Node.identifier(9, 'identifier'),
        Node.openBrace(20),
        Node.string(21, ' string '),
        Node.openBrace(29),
        Node.identifier(31, 'identifier'),
        Node.closeBrace(42),
        Node.string(43, ' '),
        Node.closeBrace(44),
        Node.closeBrace(46),
      ]),
    );
  });

  testWithoutContext('lexer unmatched single quote', () {
    const message = "here''s an unmatched single quote: '";
    const expectedError = '''
[app_en.arb:escaping] ICU Lexing Error: Unmatched single quotes.
    here''s an unmatched single quote: '
                                       ^''';
    expect(
      () => Parser('escaping', 'app_en.arb', message, useEscaping: true).lexIntoTokens(),
      throwsA(
        isA<L10nException>().having(
          (L10nException e) => e.message,
          'message',
          contains(expectedError),
        ),
      ),
    );
  });

  testWithoutContext('lexer unexpected character', () {
    const message = '{ * }';
    const expectedError = '''
[app_en.arb:lex] ICU Lexing Error: Unexpected character.
    { * }
      ^''';
    expect(
      () => Parser('lex', 'app_en.arb', message).lexIntoTokens(),
      throwsA(
        isA<L10nException>().having(
          (L10nException e) => e.message,
          'message',
          contains(expectedError),
        ),
      ),
    );
  });

  testWithoutContext('relaxed lexer', () {
    final List<Node> tokens1 = Parser(
      'string',
      'app_en.arb',
      '{ }',
      placeholders: <String>[],
    ).lexIntoTokens();
    expect(
      tokens1,
      equals(<Node>[
        Node(ST.string, 0, value: '{'),
        Node(ST.string, 1, value: ' '),
        Node(ST.string, 2, value: '}'),
      ]),
    );

    final List<Node> tokens2 = Parser(
      'string',
      'app_en.arb',
      '{ notAPlaceholder }',
      placeholders: <String>['isAPlaceholder'],
    ).lexIntoTokens();
    expect(
      tokens2,
      equals(<Node>[
        Node(ST.string, 0, value: '{'),
        Node(ST.string, 1, value: ' notAPlaceholder '),
        Node(ST.string, 18, value: '}'),
      ]),
    );

    final List<Node> tokens3 = Parser(
      'string',
      'app_en.arb',
      '{ isAPlaceholder }',
      placeholders: <String>['isAPlaceholder'],
    ).lexIntoTokens();
    expect(
      tokens3,
      equals(<Node>[
        Node(ST.openBrace, 0, value: '{'),
        Node(ST.identifier, 2, value: 'isAPlaceholder'),
        Node(ST.closeBrace, 17, value: '}'),
      ]),
    );
  });

  testWithoutContext('relaxed lexer complex', () {
    const message =
        '{ notPlaceholder } {count,plural, =0{Hello} =1{Hello World} =2{Hello two worlds} few{Hello {count} worlds} many{Hello all {count} worlds} other{Hello other {count} worlds}}';
    final List<Node> tokens = Parser(
      'string',
      'app_en.arb',
      message,
      placeholders: <String>['count'],
    ).lexIntoTokens();
    expect(tokens[0].type, equals(ST.string));
  });

  testWithoutContext('parser basic', () {
    expect(
      Parser('helloWorld', 'app_en.arb', 'Hello {name}').parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(ST.string, 0, value: 'Hello '),
            Node(
              ST.placeholderExpr,
              6,
              children: <Node>[
                Node(ST.openBrace, 6, value: '{'),
                Node(ST.identifier, 7, value: 'name'),
                Node(ST.closeBrace, 11, value: '}'),
              ],
            ),
          ],
        ),
      ),
    );

    expect(
      Parser('argumentTest', 'app_en.arb', 'Today is {date, date, ::yMMd}').parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(ST.string, 0, value: 'Today is '),
            Node(
              ST.argumentExpr,
              9,
              children: <Node>[
                Node(ST.openBrace, 9, value: '{'),
                Node(ST.identifier, 10, value: 'date'),
                Node(ST.comma, 14, value: ','),
                Node(ST.argType, 16, children: <Node>[Node(ST.date, 16, value: 'date')]),
                Node(ST.comma, 20, value: ','),
                Node(ST.colon, 22, value: ':'),
                Node(ST.colon, 23, value: ':'),
                Node(ST.identifier, 24, value: 'yMMd'),
                Node(ST.closeBrace, 28, value: '}'),
              ],
            ),
          ],
        ),
      ),
    );

    expect(
      Parser(
        'plural',
        'app_en.arb',
        'There are {count} {count, plural, =1{cat} other{cats}}',
      ).parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(ST.string, 0, value: 'There are '),
            Node(
              ST.placeholderExpr,
              10,
              children: <Node>[
                Node(ST.openBrace, 10, value: '{'),
                Node(ST.identifier, 11, value: 'count'),
                Node(ST.closeBrace, 16, value: '}'),
              ],
            ),
            Node(ST.string, 17, value: ' '),
            Node(
              ST.pluralExpr,
              18,
              children: <Node>[
                Node(ST.openBrace, 18, value: '{'),
                Node(ST.identifier, 19, value: 'count'),
                Node(ST.comma, 24, value: ','),
                Node(ST.plural, 26, value: 'plural'),
                Node(ST.comma, 32, value: ','),
                Node(
                  ST.pluralParts,
                  34,
                  children: <Node>[
                    Node(
                      ST.pluralPart,
                      34,
                      children: <Node>[
                        Node(ST.equalSign, 34, value: '='),
                        Node(ST.number, 35, value: '1'),
                        Node(ST.openBrace, 36, value: '{'),
                        Node(ST.message, 37, children: <Node>[Node(ST.string, 37, value: 'cat')]),
                        Node(ST.closeBrace, 40, value: '}'),
                      ],
                    ),
                    Node(
                      ST.pluralPart,
                      42,
                      children: <Node>[
                        Node(ST.other, 42, value: 'other'),
                        Node(ST.openBrace, 47, value: '{'),
                        Node(ST.message, 48, children: <Node>[Node(ST.string, 48, value: 'cats')]),
                        Node(ST.closeBrace, 52, value: '}'),
                      ],
                    ),
                  ],
                ),
                Node(ST.closeBrace, 53, value: '}'),
              ],
            ),
          ],
        ),
      ),
    );

    expect(
      Parser('gender', 'app_en.arb', '{gender, select, male{he} female{she} other{they}}').parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(
              ST.selectExpr,
              0,
              children: <Node>[
                Node(ST.openBrace, 0, value: '{'),
                Node(ST.identifier, 1, value: 'gender'),
                Node(ST.comma, 7, value: ','),
                Node(ST.select, 9, value: 'select'),
                Node(ST.comma, 15, value: ','),
                Node(
                  ST.selectParts,
                  17,
                  children: <Node>[
                    Node(
                      ST.selectPart,
                      17,
                      children: <Node>[
                        Node(ST.identifier, 17, value: 'male'),
                        Node(ST.openBrace, 21, value: '{'),
                        Node(ST.message, 22, children: <Node>[Node(ST.string, 22, value: 'he')]),
                        Node(ST.closeBrace, 24, value: '}'),
                      ],
                    ),
                    Node(
                      ST.selectPart,
                      26,
                      children: <Node>[
                        Node(ST.identifier, 26, value: 'female'),
                        Node(ST.openBrace, 32, value: '{'),
                        Node(ST.message, 33, children: <Node>[Node(ST.string, 33, value: 'she')]),
                        Node(ST.closeBrace, 36, value: '}'),
                      ],
                    ),
                    Node(
                      ST.selectPart,
                      38,
                      children: <Node>[
                        Node(ST.other, 38, value: 'other'),
                        Node(ST.openBrace, 43, value: '{'),
                        Node(ST.message, 44, children: <Node>[Node(ST.string, 44, value: 'they')]),
                        Node(ST.closeBrace, 48, value: '}'),
                      ],
                    ),
                  ],
                ),
                Node(ST.closeBrace, 49, value: '}'),
              ],
            ),
          ],
        ),
      ),
    );
  });

  testWithoutContext('parser escaping', () {
    expect(
      Parser('escaping', 'app_en.arb', "Flutter''s amazing!", useEscaping: true).parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(ST.string, 0, value: 'Flutter'),
            Node(ST.string, 7, value: "'"),
            Node(ST.string, 9, value: 's amazing!'),
          ],
        ),
      ),
    );

    expect(
      Parser('escaping', 'app_en.arb', "'Flutter''s amazing!'", useEscaping: true).parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(ST.string, 0, value: 'Flutter'),
            Node(ST.string, 9, value: "'s amazing!"),
          ],
        ),
      ),
    );
  });

  testWithoutContext('parser recursive', () {
    expect(
      Parser(
        'pluralGender',
        'app_en.arb',
        '{count, plural, =1{{gender, select, male{he} female{she} other{they}}} other{they}}',
      ).parse(),
      equals(
        Node(
          ST.message,
          0,
          children: <Node>[
            Node(
              ST.pluralExpr,
              0,
              children: <Node>[
                Node(ST.openBrace, 0, value: '{'),
                Node(ST.identifier, 1, value: 'count'),
                Node(ST.comma, 6, value: ','),
                Node(ST.plural, 8, value: 'plural'),
                Node(ST.comma, 14, value: ','),
                Node(
                  ST.pluralParts,
                  16,
                  children: <Node>[
                    Node(
                      ST.pluralPart,
                      16,
                      children: <Node>[
                        Node(ST.equalSign, 16, value: '='),
                        Node(ST.number, 17, value: '1'),
                        Node(ST.openBrace, 18, value: '{'),
                        Node(
                          ST.message,
                          19,
                          children: <Node>[
                            Node(
                              ST.selectExpr,
                              19,
                              children: <Node>[
                                Node(ST.openBrace, 19, value: '{'),
                                Node(ST.identifier, 20, value: 'gender'),
                                Node(ST.comma, 26, value: ','),
                                Node(ST.select, 28, value: 'select'),
                                Node(ST.comma, 34, value: ','),
                                Node(
                                  ST.selectParts,
                                  36,
                                  children: <Node>[
                                    Node(
                                      ST.selectPart,
                                      36,
                                      children: <Node>[
                                        Node(ST.identifier, 36, value: 'male'),
                                        Node(ST.openBrace, 40, value: '{'),
                                        Node(
                                          ST.message,
                                          41,
                                          children: <Node>[Node(ST.string, 41, value: 'he')],
                                        ),
                                        Node(ST.closeBrace, 43, value: '}'),
                                      ],
                                    ),
                                    Node(
                                      ST.selectPart,
                                      45,
                                      children: <Node>[
                                        Node(ST.identifier, 45, value: 'female'),
                                        Node(ST.openBrace, 51, value: '{'),
                                        Node(
                                          ST.message,
                                          52,
                                          children: <Node>[Node(ST.string, 52, value: 'she')],
                                        ),
                                        Node(ST.closeBrace, 55, value: '}'),
                                      ],
                                    ),
                                    Node(
                                      ST.selectPart,
                                      57,
                                      children: <Node>[
                                        Node(ST.other, 57, value: 'other'),
                                        Node(ST.openBrace, 62, value: '{'),
                                        Node(
                                          ST.message,
                                          63,
                                          children: <Node>[Node(ST.string, 63, value: 'they')],
                                        ),
                                        Node(ST.closeBrace, 67, value: '}'),
                                      ],
                                    ),
                                  ],
                                ),
                                Node(ST.closeBrace, 68, value: '}'),
                              ],
                            ),
                          ],
                        ),
                        Node(ST.closeBrace, 69, value: '}'),
                      ],
                    ),
                    Node(
                      ST.pluralPart,
                      71,
                      children: <Node>[
                        Node(ST.other, 71, value: 'other'),
                        Node(ST.openBrace, 76, value: '{'),
                        Node(ST.message, 77, children: <Node>[Node(ST.string, 77, value: 'they')]),
                        Node(ST.closeBrace, 81, value: '}'),
                      ],
                    ),
                  ],
                ),
                Node(ST.closeBrace, 82, value: '}'),
              ],
            ),
          ],
        ),
      ),
    );
  });

  testWithoutContext('parser unexpected token', () {
    // unexpected token
    const expectedError1 = '''
[app_en.arb:unexpectedToken] ICU Syntax Error: Expected "}" but found "=".
    { placeholder =
                  ^''';
    expect(
      () => Parser('unexpectedToken', 'app_en.arb', '{ placeholder =').parseIntoTree(),
      throwsA(
        isA<L10nException>().having(
          (L10nException e) => e.message,
          'message',
          contains(expectedError1),
        ),
      ),
    );

    const expectedError2 = '''
[app_en.arb:unexpectedToken] ICU Syntax Error: Expected "number" but found "}".
    { count, plural, = }
                       ^''';
    expect(
      () => Parser('unexpectedToken', 'app_en.arb', '{ count, plural, = }').parseIntoTree(),
      throwsA(
        isA<L10nException>().having(
          (L10nException e) => e.message,
          'message',
          contains(expectedError2),
        ),
      ),
    );

    const expectedError3 = '''
[app_en.arb:unexpectedToken] ICU Syntax Error: Expected "identifier" but found ",".
    { , plural , = }
      ^''';
    expect(
      () => Parser('unexpectedToken', 'app_en.arb', '{ , plural , = }').parseIntoTree(),
      throwsA(
        isA<L10nException>().having(
          (L10nException e) => e.message,
          'message',
          contains(expectedError3),
        ),
      ),
    );
  });

  testWithoutContext('parser allows select cases with numbers', () {
    final Node node = Parser(
      'numberSelect',
      'app_en.arb',
      '{ count, select, 0{none} 100{perfect} other{required!} }',
    ).parse();
    final Node selectExpr = node.children[0];
    final Node selectParts = selectExpr.children[5];
    final Node selectPart = selectParts.children[0];
    expect(selectPart.children[0].value, equals('0'));
    expect(selectPart.children[1].value, equals('{'));
    expect(selectPart.children[2].type, equals(ST.message));
    expect(selectPart.children[3].value, equals('}'));
  });
}
