package net.ranides.assira.system.impl;

import net.ranides.assira.reflection.AdapterFactory;
import net.ranides.assira.reflection.IClass;
import net.ranides.assira.system.HostContext;

import java.lang.annotation.Annotation;
import java.util.Map;
import java.util.Set;

public class HostSpringContext implements HostContext {

    private final IApplicationContext delegate;

    public HostSpringContext(Object provider) {
        delegate = AdapterFactory.getSystem().cast(IApplicationContext.class, provider);
    }

    @Override
    public Object getBean(String name) {
        return delegate.getBean(name);
    }

    @Override
    public <T> T getBean(IClass<T> type) {
        return delegate.getBean(type.raw());
    }

    @Override
    public <T> T getBean(Class<T> type) {
        return delegate.getBean(type);
    }

    @Override
    public boolean isPrototype(String name) {
        return delegate.isPrototype(name);
    }

    @Override
    public boolean isSingleton(String name) {
        return delegate.isSingleton(name);
    }

    @Override
    public void registerPrototype(String name, IClass<?> type) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void registerSingleton(String name, Object instance) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void registerPrototype(String name, Class<?> type) {
        throw new UnsupportedOperationException();
    }

    /**
     *
     * @noinspection JavadocReference
     */
    public interface IApplicationContext {

        /**
         * Return the names of beans matching the given type (including subclasses),
         * judging from either bean definitions or the value of {@code getObjectType}
         * in the case of FactoryBeans.
         * <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
         * check nested beans which might match the specified type as well.
         * <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
         * will get initialized. If the object created by the FactoryBean doesn't match,
         * the raw FactoryBean itself will be matched against the type.
         * <p>Does not consider any hierarchy this factory may participate in.
         * Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
         * to include beans in ancestor factories too.
         * <p>This version of {@code getBeanNamesForType} matches all kinds of beans,
         * be it singletons, prototypes, or FactoryBeans. In most implementations, the
         * result will be the same as for {@code getBeanNamesForType(type, true, true)}.
         * <p>Bean names returned by this method should always return bean names <i>in the
         * order of definition</i> in the backend configuration, as far as possible.
         * @param type the class or interface to match, or {@code null} for all bean names
         * @return the names of beans (or objects created by FactoryBeans) matching
         * the given object type (including subclasses), or an empty array if none
         * @see FactoryBean#getObjectType
         * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class)
         */
        String[] getBeanNamesForType(Class<?> type);

        /**
         * Return the names of beans matching the given type (including subclasses),
         * judging from either bean definitions or the value of {@code getObjectType}
         * in the case of FactoryBeans.
         * <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
         * check nested beans which might match the specified type as well.
         * <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
         * which means that FactoryBeans will get initialized. If the object created by the
         * FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
         * type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked
         * (which doesn't require initialization of each FactoryBean).
         * <p>Does not consider any hierarchy this factory may participate in.
         * Use BeanFactoryUtils' {@code beanNamesForTypeIncludingAncestors}
         * to include beans in ancestor factories too.
         * <p>Bean names returned by this method should always return bean names <i>in the
         * order of definition</i> in the backend configuration, as far as possible.
         * @param type the class or interface to match, or {@code null} for all bean names
         * @param includeNonSingletons whether to include prototype or scoped beans too
         * or just singletons (also applies to FactoryBeans)
         * @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
         * and <i>objects created by FactoryBeans</i> - or by factory methods with a
         * "factory-bean" reference - for the type check. Note that FactoryBeans need to be
         * eagerly initialized to determine their type: So be aware that passing in "true"
         * for this flag will initialize FactoryBeans and "factory-bean" references. Only
         * actually necessary initialization for type checking purposes will be performed;
         * constructor and method invocations will still be avoided as far as possible.
         * @return the names of beans (or objects created by FactoryBeans) matching
         * the given object type (including subclasses), or an empty array if none
         * @see FactoryBean#getObjectType
         * @see BeanFactoryUtils#beanNamesForTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
         */
        String[] getBeanNamesForType(Class<?> type, boolean includeNonSingletons, boolean allowEagerInit);

        /**
         * Return the bean instances that match the given object type (including
         * subclasses), judging from either bean definitions or the value of
         * {@code getObjectType} in the case of FactoryBeans.
         * <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
         * check nested beans which might match the specified type as well. Also, it
         * <b>suppresses exceptions for beans that are currently in creation in a circular
         * reference scenario:</b> typically, references back to the caller of this method.
         * <p>Does consider objects created by FactoryBeans, which means that FactoryBeans
         * will get initialized. If the object created by the FactoryBean doesn't match,
         * the raw FactoryBean itself will be matched against the type.
         * <p>Does not consider any hierarchy this factory may participate in.
         * Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
         * to include beans in ancestor factories too.
         * <p>This version of getBeansOfType matches all kinds of beans, be it
         * singletons, prototypes, or FactoryBeans. In most implementations, the
         * result will be the same as for {@code getBeansOfType(type, true, true)}.
         * <p>The Map returned by this method should always return bean names and
         * corresponding bean instances <i>in the order of definition</i> in the
         * backend configuration, as far as possible.
         * <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
         * calls for specific bean names in preference to this Map-based retrieval method.</b>
         * Aside from lazy instantiation benefits, this also avoids any exception suppression.
         * @param type the class or interface to match, or {@code null} for all concrete beans
         * @return a Map with the matching beans, containing the bean names as
         * keys and the corresponding bean instances as values
         * @throws BeansException if a bean could not be created
         * @since 1.1.2
         * @see FactoryBean#getObjectType
         * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class)
         */
        <T> Map<String, T> getBeansOfType(Class<T> type);

        /**
         * Return the bean instances that match the given object type (including
         * subclasses), judging from either bean definitions or the value of
         * {@code getObjectType} in the case of FactoryBeans.
         * <p><b>NOTE: This method introspects top-level beans only.</b> It does <i>not</i>
         * check nested beans which might match the specified type as well. Also, it
         * <b>suppresses exceptions for beans that are currently in creation in a circular
         * reference scenario:</b> typically, references back to the caller of this method.
         * <p>Does consider objects created by FactoryBeans if the "allowEagerInit" flag is set,
         * which means that FactoryBeans will get initialized. If the object created by the
         * FactoryBean doesn't match, the raw FactoryBean itself will be matched against the
         * type. If "allowEagerInit" is not set, only raw FactoryBeans will be checked
         * (which doesn't require initialization of each FactoryBean).
         * <p>Does not consider any hierarchy this factory may participate in.
         * Use BeanFactoryUtils' {@code beansOfTypeIncludingAncestors}
         * to include beans in ancestor factories too.
         * <p>The Map returned by this method should always return bean names and
         * corresponding bean instances <i>in the order of definition</i> in the
         * backend configuration, as far as possible.
         * <p><b>Consider {@link #getBeanNamesForType(Class)} with selective {@link #getBean}
         * calls for specific bean names in preference to this Map-based retrieval method.</b>
         * Aside from lazy instantiation benefits, this also avoids any exception suppression.
         * @param type the class or interface to match, or {@code null} for all concrete beans
         * @param includeNonSingletons whether to include prototype or scoped beans too
         * or just singletons (also applies to FactoryBeans)
         * @param allowEagerInit whether to introspect <i>lazy-init singletons</i>
         * and <i>objects created by FactoryBeans</i> - or by factory methods with a
         * "factory-bean" reference - for the type check. Note that FactoryBeans need to be
         * eagerly initialized to determine their type: So be aware that passing in "true"
         * for this flag will initialize FactoryBeans and "factory-bean" references. Only
         * actually necessary initialization for type checking purposes will be performed;
         * constructor and method invocations will still be avoided as far as possible.
         * @return a Map with the matching beans, containing the bean names as
         * keys and the corresponding bean instances as values
         * @throws BeansException if a bean could not be created
         * @see FactoryBean#getObjectType
         * @see BeanFactoryUtils#beansOfTypeIncludingAncestors(ListableBeanFactory, Class, boolean, boolean)
         */
        <T> Map<String, T> getBeansOfType(Class<T> type, boolean includeNonSingletons, boolean allowEagerInit);

        /**
         * Find all names of beans which are annotated with the supplied {@link Annotation}
         * type, without creating corresponding bean instances yet.
         * <p>Note that this method considers objects created by FactoryBeans, which means
         * that FactoryBeans will get initialized in order to determine their object type.
         * @param annotationType the type of annotation to look for
         * (at class, interface or factory method level of the specified bean)
         * @return the names of all matching beans
         * @since 4.0
         * @see #getBeansWithAnnotation(Class)
         * @see #findAnnotationOnBean(String, Class)
         */
        String[] getBeanNamesForAnnotation(Class<? extends Annotation> annotationType);

        /**
         * Find all beans which are annotated with the supplied {@link Annotation} type,
         * returning a Map of bean names with corresponding bean instances.
         * <p>Note that this method considers objects created by FactoryBeans, which means
         * that FactoryBeans will get initialized in order to determine their object type.
         * @param annotationType the type of annotation to look for
         * (at class, interface or factory method level of the specified bean)
         * @return a Map with the matching beans, containing the bean names as
         * keys and the corresponding bean instances as values
         * @throws BeansException if a bean could not be created
         * @since 3.0
         * @see #findAnnotationOnBean(String, Class)
         * @see #findAnnotationOnBean(String, Class, boolean)
         * @see #findAllAnnotationsOnBean(String, Class, boolean)
         */
        Map<String, Object> getBeansWithAnnotation(Class<? extends Annotation> annotationType);

        /**
         * Find an {@link Annotation} of {@code annotationType} on the specified bean,
         * traversing its interfaces and superclasses if no annotation can be found on
         * the given class itself, as well as checking the bean's factory method (if any).
         * @param beanName the name of the bean to look for annotations on
         * @param annotationType the type of annotation to look for
         * (at class, interface or factory method level of the specified bean)
         * @return the annotation of the given type if found, or {@code null} otherwise
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 3.0
         * @see #findAnnotationOnBean(String, Class, boolean)
         * @see #findAllAnnotationsOnBean(String, Class, boolean)
         * @see #getBeanNamesForAnnotation(Class)
         * @see #getBeansWithAnnotation(Class)
         * @see #getType(String)
         */
        <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType);

        /**
         * Find an {@link Annotation} of {@code annotationType} on the specified bean,
         * traversing its interfaces and superclasses if no annotation can be found on
         * the given class itself, as well as checking the bean's factory method (if any).
         * @param beanName the name of the bean to look for annotations on
         * @param annotationType the type of annotation to look for
         * (at class, interface or factory method level of the specified bean)
         * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized
         * just for the purpose of determining its object type
         * @return the annotation of the given type if found, or {@code null} otherwise
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 5.3.14
         * @see #findAnnotationOnBean(String, Class)
         * @see #findAllAnnotationsOnBean(String, Class, boolean)
         * @see #getBeanNamesForAnnotation(Class)
         * @see #getBeansWithAnnotation(Class)
         * @see #getType(String, boolean)
         */
        <A extends Annotation> A findAnnotationOnBean(String beanName, Class<A> annotationType, boolean allowFactoryBeanInit);

        /**
         * Find all {@link Annotation} instances of {@code annotationType} on the specified
         * bean, traversing its interfaces and superclasses if no annotation can be found on
         * the given class itself, as well as checking the bean's factory method (if any).
         * @param beanName the name of the bean to look for annotations on
         * @param annotationType the type of annotation to look for
         * (at class, interface or factory method level of the specified bean)
         * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized
         * just for the purpose of determining its object type
         * @return the set of annotations of the given type found (potentially empty)
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 6.0
         * @see #getBeanNamesForAnnotation(Class)
         * @see #findAnnotationOnBean(String, Class, boolean)
         * @see #getType(String, boolean)
         */
        <A extends Annotation> Set<A> findAllAnnotationsOnBean(
            String beanName, Class<A> annotationType, boolean allowFactoryBeanInit);

        /**
         * Return an instance, which may be shared or independent, of the specified bean.
         * <p>This method allows a Spring BeanFactory to be used as a replacement for the
         * Singleton or Prototype design pattern. Callers may retain references to
         * returned objects in the case of Singleton beans.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to retrieve
         * @return an instance of the bean.
         * Note that the return value will never be {@code null} but possibly a stub for
         * {@code null} returned from a factory method, to be checked via {@code equals(null)}.
         * Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
         * @throws NoSuchBeanDefinitionException if there is no bean with the specified name
         * @throws BeansException if the bean could not be obtained
         */
        Object getBean(String name);

        /**
         * Return an instance, which may be shared or independent, of the specified bean.
         * <p>Behaves the same as {@link #getBean(String)}, but provides a measure of type
         * safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the
         * required type. This means that ClassCastException can't be thrown on casting
         * the result correctly, as can happen with {@link #getBean(String)}.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to retrieve
         * @param requiredType type the bean must match; can be an interface or superclass
         * @return an instance of the bean.
         * Note that the return value will never be {@code null}. In case of a stub for
         * {@code null} from a factory method having been resolved for the requested bean, a
         * {@code BeanNotOfRequiredTypeException} against the NullBean stub will be raised.
         * Consider using {@link #getBeanProvider(Class)} for resolving optional dependencies.
         * @throws NoSuchBeanDefinitionException if there is no such bean definition
         * @throws BeanNotOfRequiredTypeException if the bean is not of the required type
         * @throws BeansException if the bean could not be created
         */
        <T> T getBean(String name, Class<T> requiredType);

        /**
         * Return an instance, which may be shared or independent, of the specified bean.
         * <p>Allows for specifying explicit constructor arguments / factory method arguments,
         * overriding the specified default arguments (if any) in the bean definition.
         * Note that the provided arguments need to match a specific candidate constructor /
         * factory method in the order of declared parameters.
         * @param name the name of the bean to retrieve
         * @param args arguments to use when creating a bean instance using explicit arguments
         * (only applied when creating a new instance as opposed to retrieving an existing one)
         * @return an instance of the bean
         * @throws NoSuchBeanDefinitionException if there is no such bean definition
         * @throws BeanDefinitionStoreException if arguments have been given but
         * the affected bean isn't a prototype
         * @throws BeansException if the bean could not be created
         * @since 2.5
         */
        Object getBean(String name, Object... args);

        /**
         * Return the bean instance that uniquely matches the given object type, if any.
         * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
         * but may also be translated into a conventional by-name lookup based on the name
         * of the given type. For more extensive retrieval operations across sets of beans,
         * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
         * @param requiredType type the bean must match; can be an interface or superclass
         * @return an instance of the single bean matching the required type
         * @throws NoSuchBeanDefinitionException if no bean of the given type was found
         * @throws NoUniqueBeanDefinitionException if more than one bean of the given type was found
         * @throws BeansException if the bean could not be created
         * @since 3.0
         * @see ListableBeanFactory
         */
        <T> T getBean(Class<T> requiredType);

        /**
         * Return an instance, which may be shared or independent, of the specified bean.
         * <p>Allows for specifying explicit constructor arguments / factory method arguments,
         * overriding the specified default arguments (if any) in the bean definition.
         * Note that the provided arguments need to match a specific candidate constructor /
         * factory method in the order of declared parameters.
         * <p>This method goes into {@link ListableBeanFactory} by-type lookup territory
         * but may also be translated into a conventional by-name lookup based on the name
         * of the given type. For more extensive retrieval operations across sets of beans,
         * use {@link ListableBeanFactory} and/or {@link BeanFactoryUtils}.
         * @param requiredType type the bean must match; can be an interface or superclass
         * @param args arguments to use when creating a bean instance using explicit arguments
         * (only applied when creating a new instance as opposed to retrieving an existing one)
         * @return an instance of the bean
         * @throws NoSuchBeanDefinitionException if there is no such bean definition
         * @throws BeanDefinitionStoreException if arguments have been given but
         * the affected bean isn't a prototype
         * @throws BeansException if the bean could not be created
         * @since 4.1
         */
        <T> T getBean(Class<T> requiredType, Object... args);

        /**
         * Does this bean factory contain a bean definition or externally registered singleton
         * instance with the given name?
         * <p>If the given name is an alias, it will be translated back to the corresponding
         * canonical bean name.
         * <p>If this factory is hierarchical, will ask any parent factory if the bean cannot
         * be found in this factory instance.
         * <p>If a bean definition or singleton instance matching the given name is found,
         * this method will return {@code true} whether the named bean definition is concrete
         * or abstract, lazy or eager, in scope or not. Therefore, note that a {@code true}
         * return value from this method does not necessarily indicate that {@link #getBean}
         * will be able to obtain an instance for the same name.
         * @param name the name of the bean to query
         * @return whether a bean with the given name is present
         */
        boolean containsBean(String name);

        /**
         * Is this bean a shared singleton? That is, will {@link #getBean} always
         * return the same instance?
         * <p>Note: This method returning {@code false} does not clearly indicate
         * independent instances. It indicates non-singleton instances, which may correspond
         * to a scoped bean as well. Use the {@link #isPrototype} operation to explicitly
         * check for independent instances.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to query
         * @return whether this bean corresponds to a singleton instance
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @see #getBean
         * @see #isPrototype
         */
        boolean isSingleton(String name);

        /**
         * Is this bean a prototype? That is, will {@link #getBean} always return
         * independent instances?
         * <p>Note: This method returning {@code false} does not clearly indicate
         * a singleton object. It indicates non-independent instances, which may correspond
         * to a scoped bean as well. Use the {@link #isSingleton} operation to explicitly
         * check for a shared singleton instance.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to query
         * @return whether this bean will always deliver independent instances
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 2.0.3
         * @see #getBean
         * @see #isSingleton
         */
        boolean isPrototype(String name);

        /**
         * Check whether the bean with the given name matches the specified type.
         * More specifically, check whether a {@link #getBean} call for the given name
         * would return an object that is assignable to the specified target type.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to query
         * @param typeToMatch the type to match against (as a {@code Class})
         * @return {@code true} if the bean type matches,
         * {@code false} if it doesn't match or cannot be determined yet
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 2.0.1
         * @see #getBean
         * @see #getType
         */
        boolean isTypeMatch(String name, Class<?> typeToMatch);

        /**
         * Determine the type of the bean with the given name. More specifically,
         * determine the type of object that {@link #getBean} would return for the given name.
         * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates,
         * as exposed by {@link FactoryBean#getObjectType()}. This may lead to the initialization
         * of a previously uninitialized {@code FactoryBean} (see {@link #getType(String, boolean)}).
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to query
         * @return the type of the bean, or {@code null} if not determinable
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 1.1.2
         * @see #getBean
         * @see #isTypeMatch
         */
        Class<?> getType(String name);

        /**
         * Determine the type of the bean with the given name. More specifically,
         * determine the type of object that {@link #getBean} would return for the given name.
         * <p>For a {@link FactoryBean}, return the type of object that the FactoryBean creates,
         * as exposed by {@link FactoryBean#getObjectType()}. Depending on the
         * {@code allowFactoryBeanInit} flag, this may lead to the initialization of a previously
         * uninitialized {@code FactoryBean} if no early type information is available.
         * <p>Translates aliases back to the corresponding canonical bean name.
         * <p>Will ask the parent factory if the bean cannot be found in this factory instance.
         * @param name the name of the bean to query
         * @param allowFactoryBeanInit whether a {@code FactoryBean} may get initialized
         * just for the purpose of determining its object type
         * @return the type of the bean, or {@code null} if not determinable
         * @throws NoSuchBeanDefinitionException if there is no bean with the given name
         * @since 5.2
         * @see #getBean
         * @see #isTypeMatch
         */
        Class<?> getType(String name, boolean allowFactoryBeanInit);

    }
}
