/*
 * Copyright 2011 Google Inc. 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.
 */

package com.google.devtools.j2objc.gen;

import com.google.devtools.j2objc.GenerationTest;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Unit tests for CPP line directive generation, used to support debugging.
 *
 * @author Tom Ball
 */
public class LineDirectivesTest extends GenerationTest {

  @Override
  protected void setUp() throws IOException {
    super.setUp();
    options.setEmitLineDirectives(true);
  }

  public void testNoHeaderNumbering() throws IOException {
    String translation = translateSourceFile(
        "import java.util.*;\n\n public class A {\n\n"
        + "  // Method one\n"
        + "  void one() {}\n\n"
        + "  // Method two\n"
        + "  void two() {}\n}\n",
        "A", "A.h");
    assertFalse(translation.contains("#line"));
  }

  public void testMethodNumbering() throws IOException {
    String translation = translateSourceFile(
        "import java.util.*;\n\n public class A {\n\n"
        + "  // Method one\n"
        + "  void one() {}\n\n"
        + "  // Method two\n"
        + "  void two() {}\n"
        + "  void three() {}}\n",
        "A", "A.m");
    assertTranslation(translation, "#line 1 \"A.java\"");
    assertTranslation(translation, "#line 3\n@implementation A");
    assertTranslation(translation, "#line 6\n- (void)one");
    // Lines match up between one() and two() so no need for the directive.
    assertNotInTranslation(translation, "#line 9\n- (void)two");
    assertTranslation(translation, "#line 10\n- (void)three");
  }

  public void testStatementNumbering() throws IOException {
    String translation = translateSourceFile(
        "public class A {\n"
        + "  String test() {\n"
        + "    // some comment\n"
        + "    int i = 0;\n\n"
        + "    // another comment\n"
        + "    return Integer.toString(i);\n"
        + "  }}\n",
        "A", "A.m");
    assertTranslation(translation, "#line 1 \"A.java\"");
    assertTranslation(translation, "#line 2\n- (NSString *)test");
    assertTranslation(translation, "#line 4\n  jint i = 0;");
    assertTranslation(translation, "#line 7\n  return JavaLangInteger_toStringWithInt_(i);");
  }

  public void testForIfWhileStatementsWithoutBlocks() throws IOException {
    String translation = translateSourceFile(
        "public class Test {\n"
        + "  void test(int n) {\n"
        + "    for (int i = 0; i < 10; i++)\n"
        + "      if ((n % 2) == 0)\n"
        + "        n += i;\n"
        + "    \n"
        + "    for (int j = 0; j < 100; j++)\n"
        + "      for (int k = 0; k < 1000; k++)\n"
        + "        n += j + k;\n"
        + "    \n"
        + "    while (n > 0)\n"
        + "      n--;"
        + "  }}",
        "Test", "Test.m");
    assertTranslatedLines(translation,
        "for (jint i = 0; i < 10; i++)",
        "#line 4",
        "if ((JreIntMod(n, 2)) == 0)",
        "#line 5",
        "n += i;",
        "",
        "#line 7",
        "for (jint j = 0; j < 100; j++)",
        "#line 8",
        "for (jint k = 0; k < 1000; k++)",
        "#line 9",
        "n += j + k;",
        "",
        "#line 11",
        "while (n > 0)",
        "#line 12",
        "n--;");
  }

  public void testCombinedFileLineDirectives() throws IOException {
    // An example where some types will be reordered
    addSourceFile(
        "package unit;\n"
        + "public class Test {\n"
        + "    public String Dummy(int i) {\n"
        + "        System.out.println(\"Hello world!\");\n"
        + "        return Integer.toString(i);\n"
        + "    }\n"
        + "}\n"
        + "\n"
        + "class NotReordered {"
        + "    public String DummyTwo(int i) {\n"
        + "        int j = i + 1;\n"
        + "        return Integer.toString(j);\n"
        + "    }"
        + "}\n",
        "unit/Test.java");
    addSourceFile(
        "package unit;\n"
        + "public class TestDependent {\n"
        + "    public Test Dummy() {\n"
        + "        return new Test();\n"
        + "    }\n"
        + "    public AnotherTest AnotherDummy() {\n"
        + "        return new AnotherTest();\n"
        + "    }\n"
        + "}\n",
        "unit/TestDependent.java");
    addSourceFile(
        "package unit;\n"
        + "public class AnotherTest extends Test {\n"
        + "    public void AnotherDummy() {\n"
        + "    }\n"
        + "}\n"
        + "\n"
        + "class AlsoNotReordered {"
        + "    public void DummyTwo() {\n"
        + "    }"
        + "}\n",
        "unit/AnotherTest.java");

    String translation = translateCombinedFiles(
        "unit/Foo", ".m",
        "unit/TestDependent.java", "unit/AnotherTest.java", "unit/Test.java");
    assertDirectivePreceedsLine(
        translation, "TestDependent.java", "@implementation UnitTestDependent");
    assertDirectivePreceedsLine(
        translation, "Test.java", "@implementation UnitTest");
    assertDirectivePreceedsLine(
        translation, "AnotherTest.java", "@implementation UnitAnotherTest");
    assertDirectivePreceedsLine(
        translation, "AnotherTest.java", "@implementation UnitAlsoNotReordered");
    assertDirectivePreceedsLine(
        translation, "Test.java", "@implementation UnitNotReordered");
    // make sure lines get re-synced when files are interwoven
    assertTranslatedLines(translation,
        "#line 3",
        "- (NSString *)DummyWithInt:(jint)i {");
    assertTranslatedLines(translation,
        "#line 9",
        "- (NSString *)DummyTwoWithInt:(jint)i {");
  }

  public void testSyncAfterMultilineMethodSignature() throws IOException {
    String translation = translateSourceFile(
        "public class Test {\n"
            + "  int sum(int a, int b, int c) {\n"
            + "    return a + b + c;\n"
            + "  }\n"
            + "}",
        "Test", "Test.m");
    assertTranslatedLines(translation,
        "#line 2",
        "- (jint)sumWithInt:(jint)a",
        "           withInt:(jint)b",
        "           withInt:(jint)c {",
        "",
        "#line 3",
        "  return a + b + c;",
        "}");
  }

  private static final Pattern LINE_DIRECTIVE_PATTERN = Pattern.compile(
      "\\n#line \\d+ \"(\\S*)\"\\n");

  /**
   * Asserts that the directive "#line some_line "some_prefix/$filename""
   * most immediately preceding the given line matches the given filename;
   * that is, the line directives should say we are on the given filename
   * and not some other filename.
   */
  private void assertDirectivePreceedsLine(String translation, String filename, String line) {
    int lineIndex = translation.indexOf(line + "\n");
    assertTrue(lineIndex > 0);
    // Find the last instance of a #line directive before lineIndex
    Matcher m = LINE_DIRECTIVE_PATTERN.matcher(translation.substring(0, lineIndex));
    String foundLineDirectiveFilename = "";
    while (m.find()) {
      foundLineDirectiveFilename = m.group(1);
    }
    assertTrue(foundLineDirectiveFilename.endsWith(filename));
  }
}
