package com.gabrielittner.auto.value.with;

import com.google.auto.value.processor.AutoValueProcessor;
import com.google.testing.compile.JavaFileObjects;
import java.util.Arrays;
import java.util.Collections;
import javax.tools.JavaFileObject;
import org.junit.Test;

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

public final class AutoValueWithExtensionTest {

    @Test
    public void simple() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "import javax.annotation.Nonnull;\n"
                + "@AutoValue public abstract class Test {\n"
                // normal
                + "  public abstract String a();\n"
                + "  public abstract String b();\n"
                + "  abstract Test withA(String a);\n"
                // primitive
                + "  public abstract int c();\n"
                + "  public abstract int d();\n"
                + "  abstract Test withC(int c);\n"
                // public
                + "  public abstract String e();\n"
                + "  public abstract Test withE(String e);\n"
                // protected
                + "  public abstract String f();\n"
                + "  protected abstract Test withF(String f);\n"
                // with annotation
                + "  public abstract String g();\n"
                + "  @Nonnull abstract Test withG(String g);\n"
                // public with annotation
                + "  public abstract String h();\n"
                + "  @Nonnull public abstract Test withH(String h);\n"
                // property name starting with "with"
                + "  public abstract String withI();\n"
                + "  abstract Test withWithI(String withI);\n"
                // multiple properties
                + "  abstract Test withAC(String a, int c);\n"
                + "  abstract Test withBACD(String b, String a, int c, int d);\n"
                + "}\n");

        JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
                + "package test;\n"
                + "import java.lang.Override;\n"
                + "import java.lang.String;\n"
                + "import javax.annotation.Nonnull;\n"
                + "final class AutoValue_Test extends $AutoValue_Test {\n"
                + "  AutoValue_Test(String a, String b, int c, int d, String e, String f, String g, String h, String withI) {\n"
                + "    super(a, b, c, d, e, f, g, h, withI);\n"
                + "  }\n"
                + "  @Override final Test withA(String a) {\n"
                + "    return new AutoValue_Test(a, b(), c(), d(), e(), f(), g(), h(), withI());\n"
                + "  }\n"
                + "  @Override final Test withC(int c) {\n"
                + "    return new AutoValue_Test(a(), b(), c, d(), e(), f(), g(), h(), withI());\n"
                + "  }\n"
                + "  @Override public final Test withE(String e) {\n"
                + "    return new AutoValue_Test(a(), b(), c(), d(), e, f(), g(), h(), withI());\n"
                + "  }\n"
                + "  @Override protected final Test withF(String f) {\n"
                + "    return new AutoValue_Test(a(), b(), c(), d(), e(), f, g(), h(), withI());\n"
                + "  }\n"
                + "  @Override @Nonnull final Test withG(String g) {\n"
                + "    return new AutoValue_Test(a(), b(), c(), d(), e(), f(), g, h(), withI());\n"
                + "  }\n"
                + "  @Override @Nonnull public final Test withH(String h) {\n"
                + "    return new AutoValue_Test(a(), b(), c(), d(), e(), f(), g(), h, withI());\n"
                + "  }\n"
                + "  @Override final Test withWithI(String withI) {\n"
                + "    return new AutoValue_Test(a(), b(), c(), d(), e(), f(), g(), h(), withI);\n"
                + "  }\n"
                + "  @Override final Test withAC(String a, int c) {\n"
                + "    return new AutoValue_Test(a, b(), c, d(), e(), f(), g(), h(), withI());\n"
                + "  }\n"
                + "  @Override final Test withBACD(String b, String a, int c, int d) {\n"
                + "    return new AutoValue_Test(a, b, c, d, e(), f(), g(), h(), withI());\n"
                + "  }\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(expectedSource);
    }

    @Test
    public void generic() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test<T> {\n"
                + "  public abstract T a();\n"
                + "  abstract Test<T> withA(T a);\n"
                + "}\n");

        JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
                + "package test;\n"
                + "import java.lang.Override;\n"
                + "final class AutoValue_Test<T> extends $AutoValue_Test<T> {\n"
                + "  AutoValue_Test(T a) {\n"
                + "    super(a);\n"
                + "  }\n"
                + "  @Override final Test<T> withA(T a) {\n"
                + "    return new AutoValue_Test<>(a);\n"
                + "  }\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(expectedSource);
    }

    @Test
    public void returnsSuperType() {
        JavaFileObject source1 = JavaFileObjects.forSourceString("test.AbstractTest", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "public abstract class AbstractTest {\n"
                + "  public abstract String a();\n"
                + "  abstract AbstractTest withA(String a);\n"
                + "  public abstract String b();\n"
                + "  abstract AbstractTest withB(String B);\n"
                + "}\n");
        JavaFileObject source2 = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test extends AbstractTest {\n"
                + "  @Override abstract Test withB(String b);\n"
                + "}\n");

        JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
                + "package test;\n"
                + "import java.lang.Override;\n"
                + "import java.lang.String;\n"
                + "final class AutoValue_Test extends $AutoValue_Test {\n"
                + "  AutoValue_Test(String a, String b) {\n"
                + "    super(a, b);\n"
                + "  }\n"
                + "  @Override final Test withA(String a) {\n"
                + "    return new AutoValue_Test(a, b());\n"
                + "  }\n"
                + "  @Override final Test withB(String b) {\n"
                + "    return new AutoValue_Test(a(), b);\n"
                + "  }\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Arrays.asList(source1, source2))
                .processedWith(new AutoValueProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(expectedSource);
    }

    @Test
    public void returnsGenericSuperType() {
        JavaFileObject source1 = JavaFileObjects.forSourceString("test.AbstractTest", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "public abstract class AbstractTest<T extends AbstractTest<T>> {\n"
                + "  public abstract String a();\n"
                + "  abstract T withA(String a);\n"
                + "}\n");
        JavaFileObject source2 = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "  @AutoValue public abstract class Test extends AbstractTest<Test> {\n"
                + "}\n");

        JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
                + "package test;\n"
                + "import java.lang.Override;\n"
                + "import java.lang.String;\n"
                + "final class AutoValue_Test extends $AutoValue_Test {\n"
                + "  AutoValue_Test(String a) {\n"
                + "    super(a);\n"
                + "  }\n"
                + "  @Override final Test withA(String a) {\n"
                + "    return new AutoValue_Test(a);\n"
                + "  }\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Arrays.asList(source1, source2))
                .processedWith(new AutoValueProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(expectedSource);
    }

    @Test
    public void wrongParameterName() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test {\n"
                + "  public abstract String a();\n"
                + "  abstract Test withA(String b);\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .failsToCompile()
                .withErrorContaining("Property \"b\" not found");
    }

    @Test
    public void wrongParameterType() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test {\n"
                + "  public abstract String a();\n"
                + "  abstract Test withA(int a);\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .failsToCompile()
                .withErrorContaining("Expected type java.lang.String for a");
    }

    @Test
    public void wrongReturnType() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test {\n"
                + "  public abstract String a();\n"
                + "  abstract String withA(String a);\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .failsToCompile()
                .withErrorContaining("Expected test.Test as return type");
    }

    @Test
    public void prefixedMethods() {
        JavaFileObject source = JavaFileObjects.forSourceString("test.Test", ""
                + "package test;\n"
                + "import com.google.auto.value.AutoValue;\n"
                + "@AutoValue public abstract class Test {\n"
                + "  public abstract int getA();\n"
                + "  public abstract boolean isB();\n"
                + "  abstract Test withA(int a);\n"
                + "  abstract Test withB(boolean b);\n"
                + "}\n");

        JavaFileObject expectedSource = JavaFileObjects.forSourceString("test/AutoValue_Test", ""
                + "package test;\n"
                + "import java.lang.Override;\n"
                + "final class AutoValue_Test extends $AutoValue_Test {\n"
                + "  AutoValue_Test(int a, boolean b) {\n"
                + "    super(a, b);\n"
                + "  }\n"
                + "  @Override final Test withA(int a) {\n"
                + "    return new AutoValue_Test(a, isB());\n"
                + "  }\n"
                + "  @Override final Test withB(boolean b) {\n"
                + "    return new AutoValue_Test(getA(), b);\n"
                + "  }\n"
                + "}\n");

        assertAbout(javaSources())
                .that(Collections.singletonList(source))
                .processedWith(new AutoValueProcessor())
                .compilesWithoutError()
                .and()
                .generatesSources(expectedSource);
    }
}