package com.gabrielittner.auto.value.util;

import com.google.auto.value.extension.AutoValueExtension.Context;
import com.google.common.collect.Lists;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeParameterElement;

import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;

public final class AutoValueUtil {

    private static String getAutoValueClassSimpleName(Context context) {
        return context.autoValueClass().getSimpleName().toString();
    }

    public static ClassName getAutoValueClassClassName(Context context) {
        return ClassName.get(context.packageName(), getAutoValueClassSimpleName(context));
    }

    private static String getFinalClassSimpleName(Context context) {
        return "AutoValue_" + context.autoValueClass().getSimpleName().toString();
    }

    public static ClassName getFinalClassClassName(Context context) {
        return ClassName.get(context.packageName(), getFinalClassSimpleName(context));
    }

    public static TypeSpec.Builder typeSpecBuilder(Context context, String className, String classToExtend, boolean isFinal) {
        List<? extends TypeParameterElement> typeParameters = context.autoValueClass().getTypeParameters();
        TypeVariableName[] typeVariables = new TypeVariableName[typeParameters.size()];
        for (int i = 0; i < typeParameters.size(); i++) {
            typeVariables[i] = TypeVariableName.get(typeParameters.get(i));
        }

        return TypeSpec.classBuilder(className)
                .addModifiers(isFinal ? FINAL : ABSTRACT)
                .addTypeVariables(Arrays.asList(typeVariables))
                .superclass(superClass(context, classToExtend, typeVariables))
                .addMethod(generateConstructor(context.properties()));
    }

    private static TypeName superClass(Context context, String classToExtend, TypeVariableName[] typeVariables) {
        ClassName superClassWithoutParameters = ClassName.get(context.packageName(), classToExtend);
        if (typeVariables.length > 0) {
            return ParameterizedTypeName.get(superClassWithoutParameters, (TypeName[]) typeVariables);
        } else {
            return superClassWithoutParameters;
        }
    }

    private static MethodSpec generateConstructor(Map<String, ExecutableElement> properties) {
        List<ParameterSpec> params = Lists.newArrayList();
        for (Map.Entry<String, ExecutableElement> entry : properties.entrySet()) {
            TypeName typeName = TypeName.get(entry.getValue().getReturnType());
            params.add(ParameterSpec.builder(typeName, entry.getKey()).build());
        }

        CodeBlock code = generateConstructorCall("super", properties.keySet().toArray());

        return MethodSpec.constructorBuilder()
                .addParameters(params)
                .addCode(code)
                .build();
    }

    public static CodeBlock generateFinalClassConstructorCall(Context context, Object[] properties) {
        String constructorName = "new " + getFinalClassSimpleName(context);
        return generateConstructorCall(constructorName, properties);
    }

    private static CodeBlock generateConstructorCall(String constructorName, Object[] properties) {
        StringBuilder format = new StringBuilder(constructorName)
                .append("(");
        for (int i = properties.length; i > 0; i--) {
            format.append("$N");
            if (i > 1) format.append(", ");
        }
        format.append(")");
        return CodeBlock.builder()
                .addStatement(format.toString(), properties)
                .build();
    }

    private AutoValueUtil() {
        throw new AssertionError("No instances.");
    }
}

@@ -607,16 +607,61 @@
 lement;%0A
+import javax.lang.model.element.TypeElement;%0A
 import j
@@ -1535,17 +1535,20 @@
 Builder 
-t
+newT
 ypeSpecB
@@ -1588,16 +1588,28 @@
 assName,
+%0A           
  String 
@@ -1646,116 +1646,8 @@
 ) %7B%0A
-        List%3C? extends TypeParameterElement%3E typeParameters = context.autoValueClass().getTypeParameters();%0A
     
@@ -1685,20 +1685,19 @@
 ables = 
-new 
+get
 TypeVari
@@ -1704,181 +1704,36 @@
 able
-Name%5BtypeParameters.size()%5D;%0A        for (int i = 0; i %3C typeParameters.size(); i++) %7B%0A            typeVariables%5Bi%5D = TypeVariableName.get(typeParameters.get(i));%0A        %7D%0A
+s(context.autoValueClass());
 %0A   
@@ -1927,17 +1927,20 @@
 erclass(
-s
+getS
 uperClas
@@ -1948,16 +1948,30 @@
 (context
+.packageName()
 , classT
@@ -2022,24 +2022,19 @@
 dMethod(
-generate
+new
 Construc
@@ -2095,39 +2095,501 @@
 Type
-Name superClass(Context context
+VariableName%5B%5D getTypeVariables(TypeElement autoValueClass) %7B%0A        List%3C? extends TypeParameterElement%3E parameters = autoValueClass.getTypeParameters();%0A        TypeVariableName%5B%5D typeVariables = new TypeVariableName%5Bparameters.size()%5D;%0A        for (int i = 0, length = typeVariables.length; i %3C length; i++) %7B%0A            typeVariables%5Bi%5D = TypeVariableName.get(parameters.get(i));%0A        %7D%0A        return typeVariables;%0A    %7D%0A%0A    private static TypeName getSuperClass(String packageName
 , St
@@ -2607,28 +2607,32 @@
 oExtend,
- TypeVariabl
+%0A            Typ
 eName%5B%5D 
@@ -2706,32 +2706,24 @@
 assName.get(
-context.
 packageName(
@@ -2713,34 +2713,32 @@
 .get(packageName
-()
 , classToExtend)
@@ -2856,21 +2856,8 @@
 ers,
- (TypeName%5B%5D)
  typ
@@ -2981,24 +2981,19 @@
 hodSpec 
-generate
+new
 Construc
@@ -3380,24 +3380,19 @@
  code = 
-generate
+new
 Construc
@@ -3619,24 +3619,19 @@
 deBlock 
-generate
+new
 FinalCla
@@ -3779,24 +3779,19 @@
  return 
-generate
+new
 Construc
@@ -3868,16 +3868,11 @@
 ock 
-generate
+new
 Cons
@@ -3995,33 +3995,16 @@
 torName)
-%0A                
 .append(
@@ -4212,33 +4212,16 @@
 uilder()
-%0A                
 .addStat
@@ -4256,33 +4256,16 @@
 perties)
-%0A                
 .build()

rename some methods