/*
 * JBoss, Home of Professional Open Source
 * Copyright 2016, Red Hat, Inc., and individual contributors
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * 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 javax.enterprise.inject.se;

import java.lang.annotation.Annotation;
import java.util.Iterator;
import java.util.Map;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;

import javax.enterprise.inject.spi.Extension;

/**
 * A CDI container initializer for Java SE. An instance may be obtained by calling {@link SeContainerInitializer#newInstance()}
 * static method.
 * <p>
 * Typical usage looks like this:
 * </p>
 * 
 * <pre>
 * SeContainer container = SeContainerInitializer.newInstance().initialize();
 * container.select(Foo.class).get();
 * container.close();
 * </pre>
 * 
 * <p>
 * Since {@link SeContainer} interface implements AutoCloseable:
 * </p>
 * 
 * <pre>
 * try (SeContainer container = SeContainerInitializer.newInstance().initialize()) {
 *     container.select(Foo.class).get();
 * }
 * </pre>
 * 
 * <p>
 * By default, the discovery is enabled so that all beans from all discovered bean archives are considered. However, it's
 * possible to define a "synthetic" bean archive, or the set of bean classes and enablement respectively:
 * </p>

 * <pre>
 * SeContainer container = SeContainerInitializer.newInstance().addBeanClasses(Foo.class, Bar.class).selectAlternatives(Bar.class).initialize());
 * </pre>
 * 
 * <p>
 * Moreover, it's also possible to disable the discovery completely so that only the "synthetic" bean archive is considered:
 * </p>
 * 
 * <pre>
 * SeContainer container = SeContainerInitializer.newInstance().disableDiscovery().addBeanClasses(Foo.class, Bar.class).initialize());
 * </pre>

 * <p>
 * In the same manner, it is possible to explicitly declare interceptors, decorators, extensions and implementation specific
 * options using the builder.
 * </p>
 * 
 * <pre>
 * SeContainerInitializer containerInitializer = SeContainerInitializer.newInstance()
 *         .disableDiscovery()
 *         .addPackages(Main.class, Utils.class)
 *         .enableInterceptors(TransactionalInterceptor.class)
 *         .addProperty("property", true);
 * SeContainer container = container.initialize();
 * </pre>
 *
 * @author Antoine Sabot-Durand
 * @author Martin Kouba
 * @author John D. Ament
 * @since 2.0
 */
public abstract class SeContainerInitializer {

    /**
     * Returns an instance of {@link SeContainerInitializer}
     * Each call returns a new instance
     *
     * @return a new SeContainerInitializer instance.
     * @throws IllegalStateException if called in a Jakarta EE container
     */
    public static SeContainerInitializer newInstance() {
        return findSeContainerInitializer();
    }

    private static SeContainerInitializer findSeContainerInitializer() {

        SeContainerInitializer result;
        Iterator<SeContainerInitializer> iterator = ServiceLoader.load(SeContainerInitializer.class, SeContainerInitializer.class.getClassLoader()).iterator();

        if (!iterator.hasNext()) {
            throw new IllegalStateException("No valid CDI implementation found");
        }
        try {
            result = iterator.next();
        } catch (ServiceConfigurationError e) {
            throw new IllegalStateException("Error while instantiating SeContainerInitializer", e);
        }
        if (iterator.hasNext())
            throw new IllegalStateException("Two or more CDI implementations found, only one is supported");
        return result;
    }

    /**
     * Add provided bean classes to the synthetic bean archive.
     *
     * @param classes classes to add to the synthetic bean archive
     * @return self
     */
    public abstract SeContainerInitializer addBeanClasses(Class<?>... classes);


    /**
     * All classes from the packages of the specified classes will be added to the set of bean classes for the synthetic bean archive.
     * <p>
     * <p>
     * Note that the scanning possibilities are limited. Therefore, only directories and jar files from the filesystem are supported.
     * </p>
     * <p>
     * <p>
     * Scanning may also have negative impact on SE performance.
     * </p>
     *
     * @param packageClasses classes whose packages will be added to the synthetic bean archive
     * @return self
     */
    public abstract SeContainerInitializer addPackages(Class<?>... packageClasses);

    /**
     * Packages of the specified classes will be scanned and found classes will be added to the set of bean classes for the synthetic bean archive.*
     * <p>
     * <p>
     * Note that the scanning possibilities are limited. Therefore, only directories and jar files from the filesystem are supported.
     * </p>
     * <p>
     * <p>
     * Scanning may also have negative impact on SE performance.
     * </p>
     *
     * @param scanRecursively should subpackages be scanned or not
     * @param packageClasses  classes whose packages will be scanned
     * @return self
     */
    public abstract SeContainerInitializer addPackages(boolean scanRecursively, Class<?>... packageClasses);


    /**
     * All classes from the specified packages will be added to the set of bean classes for the synthetic bean archive.
     * <p>
     * <p>
     * Note that the scanning possibilities are limited. Therefore, only directories and jar files from the filesystem are supported.
     * </p>
     * <p>
     * <p>
     * Scanning may also have negative impact on SE performance.
     * </p>
     *
     * @param packages packages that will be added to the synthetic bean archive
     * @return self
     */
    public abstract SeContainerInitializer addPackages(Package... packages);

    /**
     * All classes from the specified packages will be added to the set of bean classes for the synthetic bean archive.
     * <p>
     * <p>
     * Note that the scanning possibilities are limited. Therefore, only directories and jar files from the filesystem are supported.
     * </p>
     * <p>
     * <p>
     * Scanning may also have negative impact on SE performance.
     * </p>
     *
     * @param scanRecursively should subpackages be scanned or not
     * @param packages        packages that will be added to the synthetic bean archive
     * @return self
     */
    public abstract SeContainerInitializer addPackages(boolean scanRecursively, Package... packages);

    /**
     * Add extensions to the set of extensions.
     *
     * @param extensions extensions to use in the container
     * @return self
     */
    public abstract SeContainerInitializer addExtensions(Extension... extensions);

    /**
     * Add extensions to the set of extensions.
     *
     * @param extensions extensions class to use in the container
     * @return self
     */
    @SuppressWarnings("unchecked")
    public abstract SeContainerInitializer addExtensions(Class<? extends Extension>... extensions);

    /**
     * Add interceptor classes to the list of enabled interceptors for the synthetic bean archive.
     * <p>
     * This method does not add any class to the set of bean classes of the synthetic bean archive.
     * </p>
     * @param interceptorClasses classes of the interceptors to enable.
     * @return self
     */
    public abstract SeContainerInitializer enableInterceptors(Class<?>... interceptorClasses);

    /**
     * Add decorator classes to the list of enabled decorators for the synthetic bean archive.
     * <p>
     * This method does not add any class to the set of bean classes of the synthetic bean archive.
     * </p>
     * @param decoratorClasses classes of the decorators to enable.
     * @return self
     */
    public abstract SeContainerInitializer enableDecorators(Class<?>... decoratorClasses);

    /**
     * Add alternatives classes to the list of selected alternatives for the synthetic bean archive.
     * <p>
     * This method does not add any class to the set of bean classes of the synthetic bean archive.
     * </p>
     * @param alternativeClasses classes of the alternatives to select
     * @return self
     */
    public abstract SeContainerInitializer selectAlternatives(Class<?>... alternativeClasses);

    /**
     * Add alternative stereotype classes to the list of selected alternative stereotypes for the synthetic bean archive.
     * <p>
     * This method does not add any class to the set of bean classes of the synthetic bean archive.
     * </p>
     * @param alternativeStereotypeClasses alternatives stereotypes to select
     * @return self
     */
    @SuppressWarnings("unchecked")
    public abstract SeContainerInitializer selectAlternativeStereotypes(Class<? extends Annotation>... alternativeStereotypeClasses);

    /**
     * Add a configuration property to the container
     *
     * @param key   property name
     * @param value property value
     * @return self
     */
    public abstract SeContainerInitializer addProperty(String key, Object value);

    /**
     * Set all the configuration properties.
     * Erase previous properties set
     *
     * @param properties a map containing properties to add
     * @return self
     */
    public abstract SeContainerInitializer setProperties(Map<String, Object> properties);

    /**
     * By default, the discovery is enabled. However, it's possible to disable the discovery completely so that only the "synthetic" bean archive is considered.
     *
     * @return self
     */
    public abstract SeContainerInitializer disableDiscovery();

    /**
     * Set a {@link ClassLoader}. The given {@link ClassLoader} will be scanned automatically for bean archives if scanning is enabled.
     *
     * @param classLoader the class loader to use
     * @return self
     */
    public abstract SeContainerInitializer setClassLoader(ClassLoader classLoader);

    /**
     * <p>
     * Initializes a CDI SeContainerInitializer.
     * </p>
     * <p>
     * Cannot be called within an application server.
     * </p>
     *
     * @return the {@link SeContainer} instance associated with the container.
     * @throws UnsupportedOperationException if called within an application server
     */
    public abstract SeContainer initialize();

}
