package org.checkerframework.common.reflection;

import com.sun.source.tree.Tree;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.common.basetype.BaseTypeValidator;
import org.checkerframework.common.basetype.BaseTypeVisitor;
import org.checkerframework.common.reflection.qual.MethodVal;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeMirror.AnnotatedDeclaredType;
import org.checkerframework.javacutil.AnnotationUtils;

public class MethodValVisitor extends BaseTypeVisitor<MethodValAnnotatedTypeFactory> {

  public MethodValVisitor(BaseTypeChecker checker) {
    super(checker);
  }

  @Override
  protected MethodValAnnotatedTypeFactory createTypeFactory() {
    return new MethodValAnnotatedTypeFactory(checker);
  }

  @Override
  protected BaseTypeValidator createTypeValidator() {
    return new MethodNameValidator(checker, this, atypeFactory);
  }
}

class MethodNameValidator extends BaseTypeValidator {

  public MethodNameValidator(
      BaseTypeChecker checker, BaseTypeVisitor<?> visitor, AnnotatedTypeFactory atypeFactory) {
    super(checker, visitor, atypeFactory);
  }

  @Override
  public Void visitDeclared(AnnotatedDeclaredType type, Tree tree) {
    AnnotationMirror methodVal = type.getPrimaryAnnotation(MethodVal.class);
    if (methodVal != null) {
      AnnotatedTypeFactory atypeFactory = checker.getTypeFactory();
      List<String> classNames =
          AnnotationUtils.getElementValueArray(
              methodVal, atypeFactory.methodValClassNameElement, String.class);
      List<String> methodNames =
          AnnotationUtils.getElementValueArray(
              methodVal, atypeFactory.methodValMethodNameElement, String.class);
      List<Integer> params =
          AnnotationUtils.getElementValueArray(
              methodVal, atypeFactory.methodValParamsElement, Integer.class);
      if (!(classNames.size() == methodNames.size() && classNames.size() == params.size())) {
        checker.reportError(tree, "invalid.methodval", methodVal);
      }

      for (String methodName : methodNames) {
        if (!legalMethodName(methodName)) {
          checker.reportError(tree, "illegal.methodname", methodName, type);
        }
      }
    }
    return super.visitDeclared(type, tree);
  }

  private boolean legalMethodName(String methodName) {
    if (methodName.equals(ReflectionResolver.INIT)) {
      return true;
    }
    if (methodName.length() < 1) {
      return false;
    }
    char[] methodNameChars = methodName.toCharArray();
    if (!Character.isJavaIdentifierStart(methodNameChars[0])) {
      return false;
    }
    for (int i = 1; i < methodNameChars.length; i++) {
      if (!Character.isJavaIdentifierPart(methodNameChars[i])) {
        return false;
      }
    }
    return true;
  }
}
