// 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.

library dart_style.example.format;

import 'dart:io';
import 'dart:mirrors';

import 'package:path/path.dart' as p;

import 'package:dart_style/dart_style.dart';
import 'package:dart_style/src/debug.dart' as debug;

void main(List<String> args) {
  // Enable debugging so you can see some of the formatter's internal state.
  // Normal users do not do this.
  debug.traceChunkBuilder = true;
  debug.traceLineWriter = true;
  debug.traceSplitter = true;
  debug.useAnsiColors = true;

  runTest("regression/0000/0068.stmt", 14);

  formatStmt("hello(world);");
}

void formatStmt(String source, [int pageWidth = 80]) {
  runFormatter(source, pageWidth, isCompilationUnit: false);
}

void formatUnit(String source, [int pageWidth = 80]) {
  runFormatter(source, pageWidth, isCompilationUnit: true);
}

void runFormatter(String source, int pageWidth, {bool isCompilationUnit}) {
  try {
    var formatter = new DartFormatter(pageWidth: pageWidth);

    var result;
    if (isCompilationUnit) {
      result = formatter.format(source);
    } else {
      result = formatter.formatStatement(source);
    }

    drawRuler("before", pageWidth);
    print(source);
    drawRuler("after", pageWidth);
    print(result);
  } on FormatterException catch (error) {
    print(error.message());
  }
}

void drawRuler(String label, int width) {
  var padding = " " * (width - label.length - 1);
  print("$label:$padding|");
}

/// Runs the formatter test starting on [line] at [path] inside the "test"
/// directory.
void runTest(String path, int line) {
  var indentPattern = new RegExp(r"^\(indent (\d+)\)\s*");

  // Locate the "test" directory. Use mirrors so that this works with the test
  // package, which loads this suite into an isolate.
  var testDir = p.join(
      p.dirname(currentMirrorSystem()
          .findLibrary(#dart_style.example.format)
          .uri
          .path),
      "../test");

  var lines = new File(p.join(testDir, path)).readAsLinesSync();

  // The first line may have a "|" to indicate the page width.
  var pageWidth = 80;
  if (lines[0].endsWith("|")) {
    pageWidth = lines[0].indexOf("|");
    lines = lines.skip(1).toList();
  }

  var i = 0;
  while (i < lines.length) {
    var description = lines[i++].replaceAll(">>>", "").trim();

    // Let the test specify a leading indentation. This is handy for
    // regression tests which often come from a chunk of nested code.
    var leadingIndent = 0;
    var indentMatch = indentPattern.firstMatch(description);
    if (indentMatch != null) {
      leadingIndent = int.parse(indentMatch[1]);
      description = description.substring(indentMatch.end);
    }

    if (description == "") {
      description = "line ${i + 1}";
    } else {
      description = "line ${i + 1}: $description";
    }
    var startLine = i + 1;

    var input = "";
    while (!lines[i].startsWith("<<<")) {
      input += lines[i++] + "\n";
    }

    var expectedOutput = "";
    while (++i < lines.length && !lines[i].startsWith(">>>")) {
      expectedOutput += lines[i] + "\n";
    }

    if (line != startLine) continue;

    var isCompilationUnit = p.extension(path) == ".unit";

    var inputCode =
        _extractSelection(input, isCompilationUnit: isCompilationUnit);

    var expected =
        _extractSelection(expectedOutput, isCompilationUnit: isCompilationUnit);

    var formatter =
        new DartFormatter(pageWidth: pageWidth, indent: leadingIndent);

    var actual = formatter.formatSource(inputCode);

    // The test files always put a newline at the end of the expectation.
    // Statements from the formatter (correctly) don't have that, so add
    // one to line up with the expected result.
    var actualText = actual.text;
    if (!isCompilationUnit) actualText += "\n";

    print("$path $description");
    drawRuler("before", pageWidth);
    print(input);
    if (actualText == expected.text) {
      drawRuler("result", pageWidth);
      print(actualText);
    } else {
      print("FAIL");
      drawRuler("expected", pageWidth);
      print(expected.text);
      drawRuler("actual", pageWidth);
      print(actualText);
    }
  }
}

/// Given a source string that contains ‹ and › to indicate a selection, returns
/// a [SourceCode] with the text (with the selection markers removed) and the
/// correct selection range.
SourceCode _extractSelection(String source, {bool isCompilationUnit: false}) {
  var start = source.indexOf("‹");
  source = source.replaceAll("‹", "");

  var end = source.indexOf("›");
  source = source.replaceAll("›", "");

  return new SourceCode(source,
      isCompilationUnit: isCompilationUnit,
      selectionStart: start == -1 ? null : start,
      selectionLength: end == -1 ? null : end - start);
}
