/*
 * Copyright (c) MuleSoft, Inc.  All rights reserved.  http://www.mulesoft.com
 * The software in this package is published under the terms of the CPAL v1.0
 * license, a copy of which has been included with this distribution in the
 * LICENSE.txt file.
 */

package org.mule.test.runner.utils;

import static org.apache.commons.lang3.ClassUtils.getAllInterfaces;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * Utility for annotations related stuff.
 *
 * @since 4.0
 */
public class AnnotationUtils {

  private AnnotationUtils() {}

  /**
   * Looks for the {@link Annotation} in the {@link Class} and invokes the {@link Method}. It will return the result of the
   * invocation. If there {@link Class} is not annotated it will return the default value from the {@link Annotation}.
   *
   * @param klass the {@link Class} where to look for the annotation
   * @param annotationClass the {@link Annotation} class to look for
   * @param methodName the method name of the annotation to be called
   * @param <T> the result of the invocation
   * @throws IllegalStateException if the method is not defined in the annotation
   * @return the attribute from the annotation for the given class
   */
  public static <T> T getAnnotationAttributeFrom(Class<?> klass, Class<? extends Annotation> annotationClass, String methodName) {
    T extensions;
    Annotation annotation = klass.getAnnotation(annotationClass);
    Method method;
    try {
      method = annotationClass.getMethod(methodName);

      if (annotation != null) {
        extensions = (T) method.invoke(annotation);
      } else {
        extensions = (T) method.getDefaultValue();

      }
    } catch (Exception e) {
      throw new IllegalStateException("Cannot read default " + methodName + " from " + annotationClass);
    }

    return extensions;
  }

  /**
   * Looks for the {@link Annotation} in the {@link Class} and invokes the {@link Method} in the whole hierarhcy until it reaches
   * {@link Object}. It will return a {@link List<T>} with the results of each invocation. If there {@link Class} is not annotated
   * it will return the default value from the {@link Annotation}.
   *
   * @param klass the {@link Class} where to look for the annotation
   * @param annotationClass the {@link Annotation} class to look for
   * @param methodName the method name of the annotation to be called
   * @param <T> the result of the invocation
   * @throws IllegalStateException if the method is not defined in the annotation
   * @return a (@link List} of T for the attributes annotated in all the object hierarchy until it reaches {@link Object} class.
   */
  public static <T> List<T> getAnnotationAttributeFromHierarchy(Class<?> klass, Class<? extends Annotation> annotationClass,
                                                                String methodName) {
    List<T> list = new ArrayList<>();
    Class<?> currentClass = klass;
    while (currentClass != Object.class) {
      T attributeFrom = getAnnotationAttributeFrom(currentClass, annotationClass, methodName);
      if (attributeFrom != null) {
        list.add(attributeFrom);
      }
      currentClass = currentClass.getSuperclass();
    }
    getAllInterfaces(klass).forEach(currentInterfaceClass -> {
      T attributeFrom = getAnnotationAttributeFrom((Class<?>) currentInterfaceClass, annotationClass, methodName);
      if (attributeFrom != null) {
        list.add(attributeFrom);
      }
    });
    return list;
  }
}
