@TestOn('vm')
library parser_test;

import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as pathos;
import 'package:test/test.dart';
import 'package:html/dom.dart';
import 'package:html/parser.dart';
import 'package:html/parser_console.dart' as parser_console;
import 'package:html/src/inputstream.dart' as inputstream;
import 'support.dart';

// Run the parse error checks
// TODO(jmesserly): presumably we want this on by default?
final checkParseErrors = false;

String namespaceHtml(String expected) {
  // TODO(jmesserly): this is a workaround for http://dartbug.com/2979
  // We can't do regex replace directly =\
  // final namespaceExpected = new RegExp(@"^(\s*)<(\S+)>", multiLine: true);
  // return expected.replaceAll(namespaceExpected, @"$1<html $2>");
  final namespaceExpected = new RegExp(r"^(\|\s*)<(\S+)>");
  var lines = expected.split("\n");
  for (int i = 0; i < lines.length; i++) {
    var match = namespaceExpected.firstMatch(lines[i]);
    if (match != null) {
      lines[i] = "${match[1]}<html ${match[2]}>";
    }
  }
  return lines.join("\n");
}

void runParserTest(
    String groupName,
    String innerHTML,
    String input,
    String expected,
    List errors,
    TreeBuilderFactory treeCtor,
    bool namespaceHTMLElements) {
  // XXX - move this out into the setup function
  // concatenate all consecutive character tokens into a single token
  var builder = treeCtor(namespaceHTMLElements);
  var parser = new HtmlParser(input, tree: builder);

  Node document;
  if (innerHTML != null) {
    document = parser.parseFragment(innerHTML);
  } else {
    document = parser.parse();
  }

  var output = testSerializer(document);

  if (namespaceHTMLElements) {
    expected = namespaceHtml(expected);
  }

  expect(output, equals(expected),
      reason:
          "\n\nInput:\n$input\n\nExpected:\n$expected\n\nReceived:\n$output");

  if (checkParseErrors) {
    expect(parser.errors.length, equals(errors.length),
        reason: "\n\nInput:\n$input\n\nExpected errors (${errors.length}):\n"
            "${errors.join('\n')}\n\n"
            "Actual errors (${parser.errors.length}):\n"
            "${parser.errors.map((e) => '$e').join('\n')}");
  }
}

void main() {
  test('dart:io', () {
    // ensure IO support is unregistered
    expect(inputstream.consoleSupport,
        new TypeMatcher<inputstream.ConsoleSupport>());
    var file = new File('$testDataDir/parser_feature/raw_file.html').openSync();
    expect(() => parse(file), throwsA(new TypeMatcher<ArgumentError>()));
    parser_console.useConsole();
    expect(parse(file).body.innerHtml.trim(), 'Hello world!');
  });

  for (var path in getDataFiles('tree-construction')) {
    if (!path.endsWith('.dat')) continue;

    var tests = new TestData(path, "data");
    var testName = pathos.basenameWithoutExtension(path);

    group(testName, () {
      for (var testData in tests) {
        var input = testData['data'];
        var errors = testData['errors'];
        var innerHTML = testData['document-fragment'];
        var expected = testData['document'];
        if (errors != null) {
          errors = errors.split("\n");
        }

        for (var treeCtor in treeTypes.values) {
          for (var namespaceHTMLElements in const [false, true]) {
            test(_nameFor(input), () {
              runParserTest(testName, innerHTML, input, expected, errors,
                  treeCtor, namespaceHTMLElements);
            });
          }
        }
      }
    });
  }
}

/// Extract the name for the test based on the test input data.
_nameFor(String input) {
  // Using jsonDecode to unescape other unicode characters
  var escapeQuote = input
      .replaceAll(new RegExp('\\\\.'), '_')
      .replaceAll(new RegExp('\u0000'), '_')
      .replaceAll('"', '\\"')
      .replaceAll(new RegExp('[\n\r\t]'), '_');
  return jsonDecode('"$escapeQuote"');
}
