package org.ddd4j.spi;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.ddd4j.collection.Cache;
import org.ddd4j.contract.Require;
import org.ddd4j.value.collection.Configuration;

public abstract class Registry implements Context, ServiceBinder {

	private static class Specific extends Registry {

		private final Registry parent;

		Specific(Configuration configuration, Registry parent) {
			super(configuration);
			this.parent = Require.nonNull(parent);
		}

		@Override
		public <T> T get(Key<T> key) {
			if (hasRegisteredFactory(key)) {
				return super.get(key);
			} else {
				return parent.get(key);
			}
		}

		@Override
		public void initializeEager(Key<?> key) {
			parent.initializeEager(key);
		}

		@Override
		public void start() {
			parent.start();
		}
	}

	private static class Root extends Registry {

		private final Set<Key<?>> eager;

		Root(Configuration configuration) {
			super(configuration);
			this.eager = new HashSet<>();
		}

		@Override
		public void initializeEager(Key<?> key) {
			eager.add(Require.nonNull(key));
		}

		@Override
		public void start() {
			eager.forEach(this::get);
		}
	}

	public static Registry create(Configuration configuration) {
		return new Root(configuration);
	}

	private final Configuration configuration;
	private final Map<Key<?>, Registry> specifics;
	private final Map<Key<?>, ServiceFactory<?>> factories;
	@SuppressWarnings("rawtypes")
	private final Cache.Aside<Key, Object> instances;

	protected Registry(Configuration configuration) {
		this.configuration = Require.nonNull(configuration);
		this.specifics = new HashMap<>();
		this.factories = new HashMap<>();
		this.instances = Cache.sharedOnEqualKey();
	}

	@Override
	public <T> void bind(Key<T> key, ServiceFactory<? extends T> factory, Key<?>... path) {
		@SuppressWarnings("resource")
		Registry current = this;
		for (Key<?> target : path) {
			current = current.specifics.computeIfAbsent(target, t -> new Specific(configuration.prefixed(t), this));
		}
		current.factories.put(key, factory);
	}

	@Override
	public void close() {
		instances.evictAll(this::destroyService);
		specifics.values().forEach(Context::close);
	}

	private <T> T createService(Key<T> key) throws Exception {
		Registry context = specifics.getOrDefault(key, this);
		Configuration config = configuration.prefixed(key.name());
		return factory(key).create(context, config);
	}

	private <T> void destroyService(Key<T> key, T service) throws Exception {
		factory(key).destroy(service);
	}

	private <T> ServiceFactory<T> factory(Key<T> key) {
		@SuppressWarnings("unchecked")
		ServiceFactory<T> factory = (ServiceFactory<T>) factories.get(key);
		if (factory == null) {
			factory = key;
		}
		return factory;
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T get(Key<T> key) {
		return (T) instances.acquire(key, this::createService);
	}

	protected boolean hasRegisteredFactory(Key<?> key) {
		return factories.containsKey(key);
	}

	public abstract void start();
}
@@ -307,31 +307,24 @@
 %7B%0A%0A%09private 
-static 
 class Specif
@@ -366,149 +366,181 @@
 nal 
-Registry parent;%0A%0A%09%09Specific(Configuration configuration, Registry parent) %7B%0A%09%09%09super(configuration);%0A%09%09%09this.parent = Require.nonNull(parent
+Key%3C?%3E key;%0A%0A%09%09Specific(Key%3C?%3E key) %7B%0A%09%09%09this.key = Require.nonNull(key);%0A%09%09%7D%0A%0A%09%09@Override%0A%09%09protected Configuration config() %7B%0A%09%09%09return Registry.this.config().prefixed(key
 );%0A%09
@@ -674,22 +674,29 @@
 %09return 
-parent
+Registry.this
 .get(key
@@ -767,22 +767,29 @@
 y) %7B%0A%09%09%09
-parent
+Registry.this
 .initial
@@ -851,14 +851,21 @@
 %0A%09%09%09
-parent
+Registry.this
 .sta
@@ -917,32 +917,77 @@
 nds Registry %7B%0A%0A
+%09%09private final Configuration configuration;%0A
 %09%09private final 
@@ -1047,21 +1047,52 @@
 n) %7B%0A%09%09%09
-super
+this.configuration = Require.nonNull
 (configu
@@ -1130,32 +1130,111 @@
 shSet%3C%3E();%0A%09%09%7D%0A%0A
+%09%09@Override%0A%09%09protected Configuration config() %7B%0A%09%09%09return configuration;%0A%09%09%7D%0A%0A
 %09%09@Override%0A%09%09pu
@@ -1491,52 +1491,8 @@
 %09%7D%0A%0A
-%09private final Configuration configuration;%0A
 %09pri
@@ -1699,93 +1699,11 @@
 try(
-Configuration configuration) %7B%0A%09%09this.configuration = Require.nonNull(configuration);
+) %7B
 %0A%09%09t
@@ -2069,58 +2069,21 @@
 et, 
-t -%3E new Specific(configuration.prefixed(t), this)
+Specific::new
 );%0A%09
@@ -2248,32 +2248,77 @@
 xt::close);%0A%09%7D%0A%0A
+%09protected abstract Configuration config();%0A%0A
 %09private %3CT%3E T c
@@ -2448,23 +2448,18 @@
 = config
-uration
+()
 .prefixe
@@ -2467,15 +2467,8 @@
 (key
-.name()
 );%0A%09

refactor Registry