package com.hacknife.briefness;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.body.BodyDeclaration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.FieldDeclaration;
import com.github.javaparser.ast.body.TypeDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.type.PrimitiveType;
import com.github.javaparser.ast.type.Type;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

import java.io.File;
import java.util.Arrays;
import java.util.List;

import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;

/**
 * Generates a class that contains all supported field names in an R file as final values.
 * Also enables adding support annotations to indicate the type of resource for every field.
 */
public final class FinalRClassBuilder {
  private static final String ANNOTATION_PACKAGE = "androidx.annotation";
  private static final String ANNOTATION_PACKAGE_LEGACY = "android.support.annotation";
  private static final String[] SUPPORTED_TYPES = {
      "anim", "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "layout", "menu", "plurals",
      "string", "style", "styleable"
  };

  private FinalRClassBuilder() { }

  public static void brewJava(File rFile, File outputDir, String packageName, String className, boolean useLegacyTypes) throws Exception {
    CompilationUnit compilationUnit = JavaParser.parse(rFile);
    TypeDeclaration resourceClass = compilationUnit.getTypes().get(0);

    TypeSpec.Builder result = TypeSpec.classBuilder(className)
        .addModifiers(PUBLIC, FINAL);

    for (Node node : resourceClass.getChildNodes()) {
      if (node instanceof ClassOrInterfaceDeclaration) {
        addResourceType(Arrays.asList(SUPPORTED_TYPES), result, (ClassOrInterfaceDeclaration) node, useLegacyTypes);
      }
    }

    JavaFile finalR = JavaFile.builder(packageName, result.build())
        .addFileComment("Generated code from Butter Knife gradle plugin. Do not modify!")
        .build();

    finalR.writeTo(outputDir);
  }

  private static void addResourceType(List<String> supportedTypes, TypeSpec.Builder result,
                                      ClassOrInterfaceDeclaration node, boolean useLegacyTypes) {
    if (!supportedTypes.contains(node.getNameAsString())) {
      return;
    }

    String type = node.getNameAsString();
    TypeSpec.Builder resourceType = TypeSpec.classBuilder(type)
        .addModifiers(PUBLIC, STATIC, FINAL);

    for (BodyDeclaration field : node.getMembers()) {
      if (field instanceof FieldDeclaration) {
        FieldDeclaration declaration = (FieldDeclaration) field;
        // Check that the field is an Int because styleable also contains Int arrays which can't be
        // used in annotations.
        if (isInt(declaration)) {
          addResourceField(resourceType, declaration.getVariables().get(0),
                  getSupportAnnotationClass(type, useLegacyTypes));
        }
      }
    }

    result.addType(resourceType.build());
  }

  private static boolean isInt(FieldDeclaration field) {
    Type type = field.getCommonType();
    return type instanceof PrimitiveType
        && ((PrimitiveType) type).getType() == PrimitiveType.Primitive.INT;
  }

  private static void addResourceField(TypeSpec.Builder resourceType, VariableDeclarator variable,
                                       ClassName annotation) {
    String fieldName = variable.getNameAsString();
    String fieldValue = variable.getInitializer()
        .map(Node::toString)
        .orElseThrow(
            () -> new IllegalStateException("Field " + fieldName + " missing initializer"));
    FieldSpec.Builder fieldSpecBuilder = FieldSpec.builder(int.class, fieldName)
        .addModifiers(PUBLIC, STATIC, FINAL)
        .initializer(fieldValue);

    if (annotation != null) {
      fieldSpecBuilder.addAnnotation(annotation);
    }

    resourceType.addField(fieldSpecBuilder.build());
  }

  private static ClassName getSupportAnnotationClass(String type, boolean useLegacyTypes) {
    String supportPackage = useLegacyTypes ? ANNOTATION_PACKAGE_LEGACY : ANNOTATION_PACKAGE;
    return ClassName.get(supportPackage, capitalize(type) + "Res");
  }

  private static String capitalize(String word) {
    return Character.toUpperCase(word.charAt(0)) + word.substring(1);
  }
}