/*
 * Copyright (C) 2014 The Guava Authors
 *
 * 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.auto.value.processor;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assert_;
import static com.google.testing.compile.JavaSourcesSubjectFactory.javaSources;

import com.google.common.collect.ImmutableList;
import com.google.testing.compile.JavaFileObjects;

import junit.framework.TestCase;

import java.io.Writer;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;

/**
 * @author emcmanus@google.com (Éamonn McManus)
 */
public class AutoAnnotationCompilationTest extends TestCase {
  public void testSimple() {
    JavaFileObject myAnnotationJavaFile = JavaFileObjects.forSourceLines(
        "com.example.annotations.MyAnnotation",
        "package com.example.annotations;",
        "",
        "import com.example.enums.MyEnum;",
        "",
        "public @interface MyAnnotation {",
        "  MyEnum value();",
        "}"
    );
    JavaFileObject myEnumJavaFile = JavaFileObjects.forSourceLines(
        "com.example.enums.MyEnum",
        "package com.example.enums;",
        "",
        "public enum MyEnum {",
        "  ONE",
        "}"
    );
    JavaFileObject annotationFactoryJavaFile = JavaFileObjects.forSourceLines(
        "com.example.factories.AnnotationFactory",
        "package com.example.factories;",
        "",
        "import com.google.auto.value.AutoAnnotation;",
        "import com.example.annotations.MyAnnotation;",
        "import com.example.enums.MyEnum;",
        "",
        "public class AnnotationFactory {",
        "  @AutoAnnotation",
        "  public static MyAnnotation newMyAnnotation(MyEnum value) {",
        "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation(value);",
        "  }",
        "}");
    JavaFileObject expectedOutput = JavaFileObjects.forSourceLines(
        "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation",
        "package com.example.factories;",
        "",
        "import com.example.annotations.MyAnnotation;",
        "import com.example.enums.MyEnum;",
        "import javax.annotation.Generated",
        "",
        "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
        "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation {",
        "  private final MyEnum value;",
        "",
        "  AutoAnnotation_AnnotationFactory_newMyAnnotation(MyEnum value) {",
        "    if (value == null) {",
        "      throw new NullPointerException(\"Null value\");",
        "    }",
        "    this.value = value;",
        "  }",
        "",
        "  @Override public Class<? extends MyAnnotation> annotationType() {",
        "    return MyAnnotation.class;",
        "  }",
        "",
        "  @Override public MyEnum value() {",
        "    return value;",
        "  }",
        "",
        "  @Override public String toString() {",
        "    StringBuilder sb = new StringBuilder(\"@com.example.annotations.MyAnnotation(\");",
        "    sb.append(value);",
        "    return sb.append(')').toString();",
        "  }",
        "",
        "  @Override public boolean equals(Object o) {",
        "    if (o == this) {",
        "      return true;",
        "    }",
        "    if (o instanceof MyAnnotation) {",
        "      MyAnnotation that = (MyAnnotation) o;",
        "      return (value.equals(that.value()));",
        "    }",
        "    return false;",
        "  }",
        "",
        "  @Override public int hashCode() {",
        "    return ((127 * " + "value".hashCode() + ") ^ (value.hashCode()));",
        "  }",
        "}"
    );
    assert_().about(javaSources())
        .that(ImmutableList.of(annotationFactoryJavaFile, myAnnotationJavaFile, myEnumJavaFile))
        .processedWith(new AutoAnnotationProcessor())
        .compilesWithoutError()
        .and().generatesSources(expectedOutput);
  }

  public void testCollectionsForArrays() {
    JavaFileObject myAnnotationJavaFile = JavaFileObjects.forSourceLines(
        "com.example.annotations.MyAnnotation",
        "package com.example.annotations;",
        "",
        "import com.example.enums.MyEnum;",
        "",
        "public @interface MyAnnotation {",
        "  int[] value();",
        "  MyEnum[] enums() default {};",
        "}"
    );
    JavaFileObject myEnumJavaFile = JavaFileObjects.forSourceLines(
        "com.example.enums.MyEnum",
        "package com.example.enums;",
        "",
        "public enum MyEnum {",
        "  ONE",
        "}"
    );
    JavaFileObject annotationFactoryJavaFile = JavaFileObjects.forSourceLines(
        "com.example.factories.AnnotationFactory",
        "package com.example.factories;",
        "",
        "import com.google.auto.value.AutoAnnotation;",
        "import com.example.annotations.MyAnnotation;",
        "import com.example.enums.MyEnum;",
        "",
        "import java.util.List;",
        "import java.util.Set;",
        "",
        "public class AnnotationFactory {",
        "  @AutoAnnotation",
        "  public static MyAnnotation newMyAnnotation(",
        "      List<Integer> value, Set<MyEnum> enums) {",
        "    return new AutoAnnotation_AnnotationFactory_newMyAnnotation(value, enums);",
        "  }",
        "}");
    JavaFileObject expectedOutput = JavaFileObjects.forSourceLines(
        "com.example.factories.AutoAnnotation_AnnotationFactory_newMyAnnotation",
        "package com.example.factories;",
        "",
        "import com.example.annotations.MyAnnotation;",
        "import com.example.enums.MyEnum;",
        "import java.util.Arrays;",
        "import java.util.Collection;",
        "import java.util.List;",
        "import java.util.Set;",
        "import javax.annotation.Generated",
        "",
        "@Generated(\"" + AutoAnnotationProcessor.class.getName() + "\")",
        "final class AutoAnnotation_AnnotationFactory_newMyAnnotation implements MyAnnotation {",
        "  private final int[] value;",
        "  private final MyEnum[] enums;",
        "",
        "  AutoAnnotation_AnnotationFactory_newMyAnnotation(",
        "      List<Integer> value,",
        "      Set<MyEnum> enums) {",
        "    if (value == null) {",
        "      throw new NullPointerException(\"Null value\");",
        "    }",
        "    this.value = intArrayFromCollection(value);",
        "    if (enums == null) {",
        "      throw new NullPointerException(\"Null enums\");",
        "    }",
        "    this.enums = enums.toArray(new MyEnum[enums.size()];",
        "  }",
        "",
        "  @Override public Class<? extends MyAnnotation> annotationType() {",
        "    return MyAnnotation.class;",
        "  }",
        "",
        "  @Override public int[] value() {",
        "    return value.clone();",
        "  }",
        "",
        "  @Override public MyEnum[] enums() {",
        "    return enums.clone();",
        "  }",
        "",
        "  @Override public String toString() {",
        "    StringBuilder sb = new StringBuilder(\"@com.example.annotations.MyAnnotation(\");",
        "    sb.append(\"value=\");",
        "    sb.append(Arrays.toString(value));",
        "    sb.append(\", \");",
        "    sb.append(\"enums=\");",
        "    sb.append(Arrays.toString(enums));",
        "    return sb.append(')').toString();",
        "  }",
        "",
        "  @Override public boolean equals(Object o) {",
        "    if (o == this) {",
        "      return true;",
        "    }",
        "    if (o instanceof MyAnnotation) {",
        "      MyAnnotation that = (MyAnnotation) o;",
        "      return (Arrays.equals(value,",
        "          (that instanceof AutoAnnotation_AnnotationFactory_newMyAnnotation)",
        "              ? ((AutoAnnotation_AnnotationFactory_newMyAnnotation) that).value",
        "              : that.value()))",
        "          && (Arrays.equals(enums,",
        "          (that instanceof AutoAnnotation_AnnotationFactory_newMyAnnotation)",
        "              ? ((AutoAnnotation_AnnotationFactory_newMyAnnotation) that).enums",
        "              : that.enums()))",
        "    }",
        "    return false;",
        "  }",
        "",
        "  @Override public int hashCode() {",
        "    return ",
        "        ((127 * " + "value".hashCode() + ") ^ (Arrays.hashCode(value))) +",
        "        ((127 * " + "enums".hashCode() + ") ^ (Arrays.hashCode(enums)));",
        "  }",
        "",
        "  private static int[] intArrayFromCollection(Collection<Integer> c) {",
        "    int[] a = new int[c.size()];",
        "    int i = 0;",
        "    for (int x : c) {",
        "      a[i++] = x;",
        "    }",
        "    return a;",
        "  }",
        "}"
    );
    assert_().about(javaSources())
        .that(ImmutableList.of(annotationFactoryJavaFile, myEnumJavaFile, myAnnotationJavaFile))
        .processedWith(new AutoAnnotationProcessor())
        .compilesWithoutError()
        .and().generatesSources(expectedOutput);
  }

  public void testMissingClass() {
    // Test that referring to an undefined annotation does not trigger @AutoAnnotation processing.
    // The class Erroneous references an undefined annotation @NotAutoAnnotation. If we didn't have
    // any special treatment of undefined types then we could run into a compiler bug where
    // AutoAnnotationProcessor would think that a method annotated with @NotAutoAnnotation was in
    // fact annotated with @AutoAnnotation. As it is, we do get an error about @NotAutoAnnotation
    // being undefined, and we do not get an error complaining that this supposed @AutoAnnotation
    // method is not static. We do need to have @AutoAnnotation appear somewhere so that the
    // processor will run.
    JavaFileObject erroneousJavaFileObject = JavaFileObjects.forSourceLines(
        "com.example.annotations.Erroneous",
        "package com.example.annotations;",
        "",
        "import com.google.auto.value.AutoAnnotation;",
        "",
        "public class Erroneous {",
        "  @interface Empty {}",
        "  @AutoAnnotation static Empty newEmpty() {}",
        "  @NotAutoAnnotation Empty notNewEmpty() {}",
        "}"
    );
    JavaCompiler javaCompiler = ToolProvider.getSystemJavaCompiler();
    DiagnosticCollector<JavaFileObject> diagnosticCollector =
        new DiagnosticCollector<JavaFileObject>();
    JavaCompiler.CompilationTask compilationTask = javaCompiler.getTask(
        (Writer) null, (JavaFileManager) null, diagnosticCollector, (Iterable<String>) null,
        (Iterable<String>) null, ImmutableList.of(erroneousJavaFileObject));
    compilationTask.setProcessors(ImmutableList.of(new AutoAnnotationProcessor()));
    boolean result = compilationTask.call();
    assertThat(result).isFalse();
    List<Diagnostic<? extends JavaFileObject>> diagnostics = diagnosticCollector.getDiagnostics();
    assertThat(diagnostics).isNotEmpty();
    assertThat(diagnostics.get(0).getMessage(null)).contains("NotAutoAnnotation");
    assertThat(diagnostics.get(0).getMessage(null)).doesNotContain("static");
  }
}
