package org.immutables.value.processor.meta;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;

public final class ThrowForInvalidImmutableState {
  public final String qualifiedName;
  public final boolean isCustom;
  public final boolean hasAttributeNamesConstructor;

  private ThrowForInvalidImmutableState(
      String qualifiedName,
      boolean isCustom,
      boolean hasAttributeNamesConstructor) {
    this.qualifiedName = qualifiedName;
    this.isCustom = isCustom;
    this.hasAttributeNamesConstructor = hasAttributeNamesConstructor;
  }

  private static final ThrowForInvalidImmutableState UNCUSTOMIZED =
      new ThrowForInvalidImmutableState(IllegalStateException.class.getName(), false, false);

  static ThrowForInvalidImmutableState from(ProcessingEnvironment processing, StyleInfo style) {
    String qualifiedName = style.throwForInvalidImmutableStateName();
    if (isStandardIllegalStateException(qualifiedName)) {
      return UNCUSTOMIZED;
    }
    TypeElement element = processing.getElementUtils().getTypeElement(qualifiedName);
    if (element == null) {
      // This shouldn't ever happen as annotation carry class literal, so it should always
      // be on the classpath, but I don't want to take chances or raise unuseful errors.
      // so just fallback to uncustomized instance.
      return UNCUSTOMIZED;
    }
    return new ThrowForInvalidImmutableState(
        qualifiedName,
        true,
        hasStringArrayConstructor(element));
  }

  private static boolean isStandardIllegalStateException(String qualifiedName) {
    return qualifiedName.equals(IllegalStateException.class.getName());
  }

  private static boolean hasStringArrayConstructor(TypeElement element) {
    for (ExecutableElement e : ElementFilter.constructorsIn(element.getEnclosedElements())) {
      if (e.getModifiers().contains(Modifier.PUBLIC) && e.getParameters().size() == 1) {
        if (isArrayOfStrings(e.getParameters().get(0).asType())) {
          return true;
        }
      }
    }
    return false;
  }

  private static boolean isArrayOfStrings(TypeMirror type) {
    return type.getKind() == TypeKind.ARRAY
        && ((ArrayType) type).getComponentType().toString().equals(String.class.getName());
  }

  @Override
  public String toString() {
    return qualifiedName;
  }
}
