package org.springframework.cloud.context.named;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.env.MapPropertySource;

/**
 * Creates a set of child contexts that allows a set of Specifications to define the
 * beans in each child context.
 *
 * Ported from spring-cloud-netflix FeignClientFactory and SpringClientFactory
 *
 * @author Spencer Gibb
 * @author Dave Syer
 */
public abstract class NamedContextFactory<C extends NamedContextFactory.Specification> implements DisposableBean, ApplicationContextAware {

	public interface Specification {
		String getName();
		Class<?>[] getConfiguration();
	}

	private Map<String, AnnotationConfigApplicationContext> contexts = new ConcurrentHashMap<>();

	private Map<String, C> configurations = new ConcurrentHashMap<>();

	private ApplicationContext parent;

	private Class<?> defaultConfigType;
	private final String propertySourceName;
	private final String propertyName;


	public NamedContextFactory(Class<?> defaultConfigType, String propertySourceName, String propertyName) {
		this.defaultConfigType = defaultConfigType;
		this.propertySourceName = propertySourceName;
		this.propertyName = propertyName;
	}

	@Override
	public void setApplicationContext(ApplicationContext parent) throws BeansException {
		this.parent = parent;
	}

	public void setConfigurations(List<C> configurations) {
		for (C client : configurations) {
			this.configurations.put(client.getName(), client);
		}
	}

	@Override
	public void destroy() {
		Collection<AnnotationConfigApplicationContext> values = this.contexts.values();
		for (AnnotationConfigApplicationContext context : values) {
			context.close();
		}
		this.contexts.clear();
	}

	protected AnnotationConfigApplicationContext getContext(String name) {
		if (!this.contexts.containsKey(name)) {
			synchronized (this.contexts) {
				if (!this.contexts.containsKey(name)) {
					this.contexts.put(name, createContext(name));
				}
			}
		}
		return this.contexts.get(name);
	}

	protected AnnotationConfigApplicationContext createContext(String name) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		if (this.configurations.containsKey(name)) {
			for (Class<?> configuration : this.configurations.get(name)
					.getConfiguration()) {
				context.register(configuration);
			}
		}
		for (Map.Entry<String, C> entry : this.configurations
				.entrySet()) {
			if (entry.getKey().startsWith("default.")) {
				for (Class<?> configuration : entry.getValue().getConfiguration()) {
					context.register(configuration);
				}
			}
		}
		context.register(PropertyPlaceholderAutoConfiguration.class,
				this.defaultConfigType);
		context.getEnvironment()
				.getPropertySources()
				.addFirst(new MapPropertySource(
						propertySourceName,
					Collections.<String, Object> singletonMap(propertyName, name)));
		if (this.parent != null) {
			// Uses Environment from parent as well as beans
			context.setParent(this.parent);
		}
		context.refresh();
		return context;
	}

	public <T> T getInstance(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return context.getBean(type);
		}
		return null;
	}

	public <T> Map<String, T> getInstances(String name, Class<T> type) {
		AnnotationConfigApplicationContext context = getContext(name);
		if (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,
				type).length > 0) {
			return BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);
		}
		return null;
	}

}

@@ -789,17 +789,17 @@
  the
-%0A *
  beans
+%0A *
  in 
@@ -1038,17 +1038,19 @@
 ication%3E
- 
+%0A%09%09
 implemen
@@ -1149,16 +1149,17 @@
 Name();%0A
+%0A
 %09%09Class%3C
@@ -1505,17 +1505,16 @@
 yName;%0A%0A
-%0A
 %09public 
@@ -1587,17 +1587,20 @@
 rceName,
- 
+%0A%09%09%09
 String p
@@ -2208,16 +2208,115 @@
 lues) %7B%0A
+%09%09%09// This can fail, but it never throws an exception (you see stack traces%0A%09%09%09// logged as WARN).%0A
 %09%09%09conte
@@ -3059,21 +3059,16 @@
 urations
-%0A%09%09%09%09
 .entrySe
@@ -3366,21 +3366,16 @@
 onment()
-%0A%09%09%09%09
 .getProp
@@ -3387,21 +3387,16 @@
 ources()
-%0A%09%09%09%09
 .addFirs
@@ -3424,18 +3424,21 @@
 ce(%0A%09%09%09%09
-%09%09
+this.
 property
@@ -3449,17 +3449,16 @@
 ceName,%0A
-%09
 %09%09%09%09Coll
@@ -3495,16 +3495,21 @@
 etonMap(
+this.
 property

Add comment about shutdown errors in context.close()