package com.cosium.spring.data.jpa.graph.generator;

import static javax.lang.model.element.ElementKind.FIELD;

import java.util.List;
import java.util.Optional;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.persistence.Entity;
import javax.persistence.metamodel.Attribute;

/** @author RĂ©da Housni Alaoui */
public class MetamodelAttribute {

  private final VariableElement variableElement;

  private MetamodelAttribute(VariableElement variableElement) {
    this.variableElement = variableElement;
  }

  public static Optional<MetamodelAttribute> parse(
      Elements elements, Types types, Element element) {
    if (!(element instanceof VariableElement)) {
      return Optional.empty();
    }

    VariableElement variableElement = (VariableElement) element;
    if (variableElement.getKind() != FIELD) {
      return Optional.empty();
    }

    TypeElement attributeTypeElement = elements.getTypeElement(Attribute.class.getCanonicalName());
    if (!types.isSubtype(
        types.erasure(variableElement.asType()), types.erasure(attributeTypeElement.asType()))) {
      return Optional.empty();
    }

    return Optional.of(new MetamodelAttribute(variableElement));
  }

  public Optional<MetamodelAttributeTarget> jpaEntityTarget() {
    DeclaredType attributeType = (DeclaredType) variableElement.asType();
    List<? extends TypeMirror> typeArguments = attributeType.getTypeArguments();
    // This is the lazy way of doing this. We consider that any sub interfaces of
    // javax.persistence.metamodel.Attribute
    // will declare the target type as the last type arguments.
    // e.g. MapAttribute<Brand, Long, Product> where Product is the target
    // TODO do this cleanly by browsing the class hierarchy
    DeclaredType targetType = (DeclaredType) typeArguments.get(typeArguments.size() - 1);
    TypeElement targetTypeElement = (TypeElement) targetType.asElement();

    if (targetTypeElement.getAnnotation(Entity.class) == null) {
      return Optional.empty();
    }

    return Optional.of(
        new MetamodelAttributeTarget(
            variableElement.getSimpleName().toString(), targetTypeElement));
  }
}