// Copyright (c) 2015, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

@TestOn("vm")
import 'package:analyzer/analyzer.dart';
import 'package:test/test.dart';
import 'package:test_core/src/util/string_literal_iterator.dart';

final _offset = "final str = ".length;

void main() {
  group("returns simple characters in", () {
    test("a single simple string", () {
      var iter = _parse('"abc"');

      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("a"));
      expect(iter.offset, equals(_offset + 1));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("b"));
      expect(iter.offset, equals(_offset + 2));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("c"));
      expect(iter.offset, equals(_offset + 3));

      expect(iter.moveNext(), isFalse);
      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 4));
    });

    test("a raw string", () {
      var iter = _parse('r"abc"');

      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 1));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("a"));
      expect(iter.offset, equals(_offset + 2));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("b"));
      expect(iter.offset, equals(_offset + 3));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("c"));
      expect(iter.offset, equals(_offset + 4));

      expect(iter.moveNext(), isFalse);
      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 5));
    });

    test("a multiline string", () {
      var iter = _parse('"""ab\ncd"""');

      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 2));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("a"));
      expect(iter.offset, equals(_offset + 3));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("b"));
      expect(iter.offset, equals(_offset + 4));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("\n"));
      expect(iter.offset, equals(_offset + 5));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("c"));
      expect(iter.offset, equals(_offset + 6));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("d"));
      expect(iter.offset, equals(_offset + 7));

      expect(iter.moveNext(), isFalse);
      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 8));
    });

    test("a raw multiline string", () {
      var iter = _parse('r"""ab\ncd"""');

      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 3));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("a"));
      expect(iter.offset, equals(_offset + 4));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("b"));
      expect(iter.offset, equals(_offset + 5));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("\n"));
      expect(iter.offset, equals(_offset + 6));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("c"));
      expect(iter.offset, equals(_offset + 7));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("d"));
      expect(iter.offset, equals(_offset + 8));

      expect(iter.moveNext(), isFalse);
      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 9));
    });

    test("adjacent strings", () {
      var iter = _parse('"ab" r"cd" """ef\ngh"""');

      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("a"));
      expect(iter.offset, equals(_offset + 1));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("b"));
      expect(iter.offset, equals(_offset + 2));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("c"));
      expect(iter.offset, equals(_offset + 7));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("d"));
      expect(iter.offset, equals(_offset + 8));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("e"));
      expect(iter.offset, equals(_offset + 14));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("f"));
      expect(iter.offset, equals(_offset + 15));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("\n"));
      expect(iter.offset, equals(_offset + 16));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("g"));
      expect(iter.offset, equals(_offset + 17));

      expect(iter.moveNext(), isTrue);
      expect(iter.current, _isRune("h"));
      expect(iter.offset, equals(_offset + 18));

      expect(iter.moveNext(), isFalse);
      expect(iter.current, isNull);
      expect(iter.offset, equals(_offset + 19));
    });
  });

  group("parses an escape sequence for", () {
    test("a newline", () => _expectEscape(r"\n", "\n"));
    test("a carriage return", () => _expectEscape(r"\r", "\r"));
    test("a form feed", () => _expectEscape(r"\f", "\f"));
    test("a backspace", () => _expectEscape(r"\b", "\b"));
    test("a tab", () => _expectEscape(r"\t", "\t"));
    test("a vertical tab", () => _expectEscape(r"\v", "\v"));
    test("a quote", () => _expectEscape(r'\"', '"'));
    test("a backslash", () => _expectEscape(r"\\", "\\"));

    test("a hex character", () {
      _expectEscape(r"\x62", "b");
      _expectEscape(r"\x7A", "z");
      _expectEscape(r"\x7a", "z");
    });

    test("a fixed-length unicode character",
        () => _expectEscape(r"\u0062", "b"));

    test("a short variable-length unicode character",
        () => _expectEscape(r"\u{62}", "b"));

    test("a long variable-length unicode character",
        () => _expectEscape(r"\u{000062}", "b"));
  });

  group("throws an ArgumentError for", () {
    test("interpolation", () {
      expect(() => _parse(r'"$foo"'), throwsArgumentError);
    });

    test("interpolation in an adjacent string", () {
      expect(() => _parse(r'"foo" "$bar" "baz"'), throwsArgumentError);
    });
  });
}

/// Asserts that [escape] is parsed as [value].
void _expectEscape(String escape, String value) {
  var iter = _parse('"a${escape}b"');

  expect(iter.current, isNull);
  expect(iter.offset, equals(_offset));

  expect(iter.moveNext(), isTrue);
  expect(iter.current, _isRune("a"));
  expect(iter.offset, equals(_offset + 1));

  expect(iter.moveNext(), isTrue);
  expect(iter.current, _isRune(value));
  expect(iter.offset, equals(_offset + 2));

  expect(iter.moveNext(), isTrue);
  expect(iter.current, _isRune("b"));
  expect(iter.offset, equals(_offset + escape.length + 2));

  expect(iter.moveNext(), isFalse);
  expect(iter.current, isNull);
  expect(iter.offset, equals(_offset + escape.length + 3));
}

/// Returns a matcher that asserts that the given rune is the rune for [char].
Matcher _isRune(String char) {
  return predicate((rune) {
    return rune is int && String.fromCharCode(rune) == char;
  }, 'is the rune "$char"');
}

/// Parses [dart], which should be a string literal, into a
/// [StringLiteralIterator].
StringLiteralIterator _parse(String dart) {
  var declaration = parseCompilationUnit("final str = $dart;")
      .declarations
      .single as TopLevelVariableDeclaration;
  var literal = declaration.variables.variables.single.initializer;
  return StringLiteralIterator(literal as StringLiteral);
}
