/*
 * Copyright 2017-2020 original authors
 *
 * 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
 *
 * https://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 io.micronaut.core.annotation;

import java.lang.annotation.Annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * An annotation that indicates a type should produce a {@link io.micronaut.core.beans.BeanIntrospection} at compilation time.
 *
 * <p>Typically to produce a {@link io.micronaut.core.beans.BeanIntrospection} one simply annotates a class with this annotation.</p>
 *
 * <pre class="code">
 * &#064;Introspected
 * public class MyBean {
 *      ...
 * }</pre>
 *
 * <p>An alternative approach is to use a {@code AnnotationMapper} to enable introspection for existing annotations such as {@code javax.persistence.Entity}.</p>
 *
 * <p>If the classes you wish to introspect are already compiled then this annotation can be used on another class (doesn't matter which, but typically on a configuration class) to specify which existing compiled classes to produce {@link io.micronaut.core.beans.BeanIntrospection} instances for either through the {@link #classes()} method or the {@link #packages()} method. The latter uses compile time package scanning and for the moment is regarded as {@link Experimental}.</p>
 *
 * <pre class="code">
 * &#064;Introspected(classes = MyBean.class)
 * public class MyConfiguration {
 *      ...
 * }</pre>
 *
 * @author graemerocher
 * @since 1.1
 * @see io.micronaut.core.beans.BeanIntrospection
 * @see io.micronaut.core.beans.BeanIntrospector
 */
@Documented
@Retention(RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Inherited
public @interface Introspected {

    /**
     * The default values for the access kind attribute.
     */
     Introspected.AccessKind[] DEFAULT_ACCESS_KIND = {Introspected.AccessKind.METHOD};

    /**
     * The default values for the visibility attribute.
     */
     Introspected.Visibility[] DEFAULT_VISIBILITY = {Introspected.Visibility.DEFAULT};

    /**
     * By default {@link Introspected} applies to the class it is applied on. However, if classes are specified
     * introspections will instead be generated for each class specified. This is useful in cases where you cannot
     * alter the source code and wish to generate introspections for already compiled classes.
     *
     * @return The classes to generate introspections for
     */
    Class<?>[] classes() default {};

    /**
     * Alternative way to specify the value for `classes` when the class cannot be referenced.
     *
     * @return The class names to generate introspections for
     */
    String[] classNames() default {};

    /**
     * <p>The default access type is {@link AccessKind#METHOD} which treats only public JavaBean getters or Java record components as properties. By specifying {@link AccessKind#FIELD}, public or package-protected fields will be used instead. </p>
     *
     *  <p>If both {@link AccessKind#FIELD} and {@link AccessKind#METHOD} are specified then the order as they appear in the annotation will be used to determine whether the field or method will be used in the case where both exist.</p>
     *
     * @return The access type. Defaults to {@link AccessKind#METHOD}
     * @since 3.0
     */
    AccessKind[] accessKind() default { AccessKind.METHOD };

    /**
     * Allows specifying the visibility policy to use to control which fields and methods are included.
     *
     * @return The visibility policies
     * @since 3.2.0
     */
    Visibility[] visibility() default  { Visibility.DEFAULT };

    /**
     * <p>By default {@link Introspected} applies to the class it is applied on. However if packages are specified
     * introspections will instead be generated for each classes in the given package. Note this only applies to already compiled
     * classes, and classpath scanning will be used to find them. If the class is not compiled then apply the annotation directly
     * to the classs instead.</p>
     *
     * <p>Must be specified in combination with {@link #includedAnnotations()}</p>
     *
     * @return The packages to generate introspections for
     */
    @Experimental
    String[] packages() default {};

    /**
     * The property names to include. Defaults to all properties.
     *
     * @return The names of the properties
     */
    String[] includes() default {};

    /**
     * The property names to excludes. Defaults to excluding none.
     *
     * @return The names of the properties
     */
    String[] excludes() default {};

    /**
     * The annotation types that if present on the property cause the property to be excluded from results.
     *
     * @return The annotation types
     */
    Class<? extends Annotation>[] excludedAnnotations() default {};

    /**
     * The annotation types that if present on the property cause only the properties with the specified annotation to be included in the result.
     *
     * @return The annotation types
     */
    Class<? extends Annotation>[] includedAnnotations() default {};

    /**
     * Whether annotation metadata should be included in the inspection results.
     *
     * @return True if annotation metadata should be included.
     */
    boolean annotationMetadata() default true;

    /**
     * The annotation types that should be indexed for lookup via {@link io.micronaut.core.beans.BeanIntrospection#getIndexedProperties(Class)} or {@link io.micronaut.core.beans.BeanIntrospection#getIndexedProperty(Class, String)} if {@link IndexedAnnotation#member()} is specified.
     *
     * <p>Property lookup indexing allows building indexes at compilation time for performing reverse property lookups. Consider for example a property with an annotation such as {@code @Column(name = "foo_bar"}. To lookup the property by "foo_bar" you can specify:</p>
     *
     * <pre class="code">
     * &#064;Introspected(
     *   indexed = &#064;IndexedAnnotation(annotation = Column.class, member = "name")
     * )
     * public class MyBean {
     *      ...
     * }</pre>
     *
     * <p>With the above in place a reverse lookup on the column can be done using {@link io.micronaut.core.beans.BeanIntrospection#getIndexedProperty(Class, String)}:</p>
     *
     * <pre class="code">
     * BeanProperty property = introspection.getIndexedProperty(Column.class, "foo_bar").orElse(null);
     * </pre>
     *
     *
     * @return The indexed annotation types
     */
    IndexedAnnotation[] indexed() default {};

    /**
     * @return The prefix used for copy constructor invoking methods on immutable types. The default is "with".
     * @since 2.3.0
     */
    String withPrefix() default "with";

    /**
     * @return The package to write introspections to. By default, uses the class package.
     * @since 3.9.0
     */
    @Experimental
    String targetPackage() default "";

    /**
     * Allows specifying a builder for the introspection.
     *
     * @return The builder definition for the introspection.
     * @since 4.1.0
     */
    @Experimental
    IntrospectionBuilder builder() default @IntrospectionBuilder();

    /**
     * Configuration for an introspection builder.
     */
    @Documented
    @Retention(RUNTIME)
    @interface IntrospectionBuilder {
        /**
         * The class that is the builder for this type.
         *
         * <p>Should be accessible. Mutually exclusive with {@link #builderMethod()}</p>
         * @return The builder class
         */
        Class<?> builderClass() default void.class;

        /**
         * The name of the method that is the builder for this type.
         * <p>Should be accessible. Mutually exclusive with {@link #builderClass()} ()}</p>
         * @return The builder method.
         */
        String builderMethod() default "";

        /**
         * The name of the method that builds the instance.
         *
         * @return The method name.
         */
        String creatorMethod() default "build";

        /**
         * @return The accessor style for the write methods of the builder.
         */
        AccessorsStyle accessorStyle() default @AccessorsStyle(writePrefixes = "");
    }

    /**
     * Allow pre-computed indexes for property lookups based on an annotation and a member.
     *
     * @see io.micronaut.core.beans.BeanIntrospection#getIndexedProperty(Class, String)
     */
    @Documented
    @Retention(RUNTIME)
    @Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
    @interface IndexedAnnotation {
        /**
         * @return The annotation
         */
        Class<? extends Annotation> annotation();

        /**
         * @return The member
         */
        String member() default "";
    }

    /**
     * The access type for bean properties.
     *
     * @since 3.0.0
     */
    enum AccessKind {
        /**
         * Allows the use of public or package-protected fields to represent bean properties.
         */
        FIELD,
        /**
         * The default behaviour which is to favour public getters for bean properties.
         */
        METHOD
    }

    /**
     * Visibility policy for bean properties and fields.
     *
     * @since 3.2.0
     */
    enum Visibility {

        /**
         * Only public methods and/or fields are included.
         */
        PUBLIC,

        /**
         * The default behaviour which in addition to public getters and setters will also include package protected fields if an {@link io.micronaut.core.annotation.Introspected.AccessKind} of {@link io.micronaut.core.annotation.Introspected.AccessKind#FIELD} is specified.
         *
         */
        DEFAULT,

        /**
         * All methods and/or fields are included.
         *
         * @since 4.0.0
         */
        ANY
    }
}
