package com.github.gfx.static_gson;

import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import javax.lang.model.element.Modifier;

public class TypeRegistry {

    private final Map<TypeName, FieldSpec> registry = new HashMap<>();

    public TypeRegistry() {
    }

    private static FieldSpec createField(TypeName type) {
        return FieldSpec.builder(Types.getTypeAdapter(type), toName(type))
                .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
                .build();
    }

    private static CodeBlock toInitializer(TypeName type) {
        return CodeBlock.builder()
                .add("gson.getAdapter($T.get($L))", Types.TypeToken, toTypeExpr(type))
                .build();
    }

    private static CodeBlock toTypeExpr(TypeName type) {
        if (type instanceof ParameterizedTypeName) {
            ParameterizedTypeName parameterizedType = (ParameterizedTypeName) type;
            CodeBlock.Builder typeArgsExpr = CodeBlock.builder();
            for (int i = 0; i < parameterizedType.typeArguments.size(); i++) {
                TypeName t = parameterizedType.typeArguments.get(i);
                if (i != 0) {
                    typeArgsExpr.add(", $L", toTypeExpr(t));
                } else {
                    typeArgsExpr.add("$L", toTypeExpr(t));
                }
            }
            return CodeBlock.builder()
                    .add("$T.newParameterizedTypeWithOwner(null, $T.class, $L)",
                            Types.$Gson$Types, parameterizedType.rawType, typeArgsExpr.build())
                    .build();
        } else {
            return CodeBlock.builder()
                    .add("$T.class", type)
                    .build();
        }
    }

    private static String toName(TypeName type) {
        return type.toString().replaceAll("[^a-zA-Z0-9?]+", "\\$");
    }

    public FieldSpec getField(TypeName type) {
        FieldSpec field = registry.get(type);
        if (field == null) {
            field = createField(type);
            registry.put(type, field);
        }
        return field;
    }

    public Collection<FieldSpec> getFields() {
        return registry.values();
    }

    public CodeBlock getFieldInitialization() {
        CodeBlock.Builder block = CodeBlock.builder();
        registry.forEach((type, field) -> {
            if (type instanceof ParameterizedTypeName) {
                block.addStatement("$N = ($T) $L",
                        field, Types.getTypeAdapter(type), toInitializer(type));
            } else {
                block.addStatement("$N = $L",
                        field, toInitializer(type));
            }
        });
        return block.build();
    }
}

@@ -778,38 +778,10 @@
 ock.
-builder()%0A                .add
+of
 (%22gs
@@ -846,33 +846,8 @@
 pe))
-%0A                .build()
 ;%0A  
@@ -1499,42 +1499,10 @@
 ock.
-builder()%0A                    .add
+of
 (%22$T
@@ -1653,37 +1653,8 @@
 d())
-%0A                    .build()
 ;%0A  
@@ -1701,88 +1701,27 @@
 ock.
-builder()%0A                    .add(%22$T.class%22, type)%0A                    .build(
+of(%22$T.class%22, type
 );%0A 

use CodeBlock.of(), a new feature in JavaPoet 1.6.0