/*
 * Copyright (c) 2008 Kasper Nielsen.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cake.internal.util.descriptors;

import static java.util.Objects.requireNonNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.function.Consumer;

import org.cakeframework.util.descriptors.AnnotatedElementDescriptor;

/** The default abstract implementation of {@link AnnotatedElementDescriptor}. */
public abstract class AbstractAnnotatedElementDescriptor implements AnnotatedElementDescriptor {

    /** The annotations present on the element */
    public final Annotation[] annotations;

    /** The type of each annotation. TODO: Don't know if we want this, premature optimization */
    public final Class<? extends Annotation>[] annotationsTypes;

    /** The annotated element. */
    private final AnnotatedElement element;

    // TODO fix declared vs non-declared annotation
    // https://djitz.com/neu-mscs/java-reflection-notes-getdeclaredannotations-vs-getannotations-method/
    // Think we need to store both for Class
    // And then just ditch annotationTypes....
    // TODO also check for Repeatable annotations
    // HMMM not sure it isAnnotationPresent works for repeatable annotations?
    // We only store the List instance

    // Also

    /**
     * Creates a new AnnotatedElementMirror from an {@link AnnotatedElement}.
     *
     * @param element
     *            the annotated element
     */
    @SuppressWarnings("unchecked")
    public AbstractAnnotatedElementDescriptor(AnnotatedElement element) {
        this.element = element;
        this.annotations = element.getAnnotations();
        this.annotationsTypes = new Class[annotations.length];
        for (int i = 0; i < annotations.length; i++) {
            annotationsTypes[i] = annotations[i].annotationType();
        }
    }

    /** {@inheritDoc} */
    @Override
    public final void forEachAnnotation(Consumer<? super Annotation> action) {
        requireNonNull(action, "action is null");
        for (Annotation a : annotations) {
            action.accept(a);
        }
    }

    /** {@inheritDoc} */
    @Override
    @SuppressWarnings("unchecked")
    public final <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
        for (int i = 0; i < annotationsTypes.length; i++) {
            if (annotationsTypes[i] == annotationClass) {
                return (T) annotations[i];
            }
        }
        return null;
    }

    /** {@inheritDoc} */
    @Override
    public final Annotation[] getAnnotations() {
        return element.getAnnotations();// Safe to use for outside
    }

    /** {@inheritDoc} */
    @Override
    public final <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
        return element.getAnnotationsByType(annotationClass);
    }

    /** {@inheritDoc} */
    @Override
    public final <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
        return element.getDeclaredAnnotation(annotationClass);
    }

    /** {@inheritDoc} */
    @Override
    public final Annotation[] getDeclaredAnnotations() {
        return element.getDeclaredAnnotations();
    }

    /** {@inheritDoc} */
    @Override
    public final <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        return element.getDeclaredAnnotationsByType(annotationClass);
    }

    /**
     * Returns the class that declares the element
     *
     * @return the class that declares the element
     */
    public abstract Class<?> getDeclaringClass();

    /**
     * Returns the modifiers of the annotated element.
     *
     * @return the modifiers of the annotated element
     */
    public abstract int getModifiers();

    /**
     * Returns whether or not there are any annotations present on the element.
     *
     * @return <code>true</code> if at least one annotation is present, <code>false</code> otherwise
     */
    public final boolean isAnnotated() {
        return annotations.length > 0;
    }

    /** {@inheritDoc} */
    @Override
    public final boolean isAnnotationPresent(Class<? extends Annotation> annotationsType) {
        for (Class<? extends Annotation> cl : annotationsTypes) {
            if (cl == annotationsType) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the type of element, is typically used for error messages.
     *
     * @return the type of element
     */
    public abstract String mirrorType();
}

/// **
// * Returns whether or not any one of of the specified annotation types are present on the element.
// *
// * @param annotationsTypes
// * the annotation types to test against
// * @return whether or not any of the annotations are present
// */
// @SuppressWarnings("unchecked")
// public final boolean isAnyAnnotationPresent(Class<? extends Annotation>... annotationsTypes) {
// for (Class<? extends Annotation> c : annotationsTypes) {
// if (isAnnotationPresent(c)) {
// return true;
// }
// }
// return false;
// }
