package com.jaregu.database.queries.ext.guice;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Function;

import javax.inject.Provider;
import javax.inject.Singleton;

import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Module;
import com.google.inject.multibindings.Multibinder;
import com.jaregu.database.queries.Queries;
import com.jaregu.database.queries.parsing.QueriesSource;
import com.jaregu.database.queries.parsing.QueriesSources;
import com.jaregu.database.queries.parsing.Sources;

public class QueriesGuiceSupport {

	private QueriesGuiceSupport() {
	}

	public static Module queriesModule(Function<QueriesSources, Queries> queriesSupplier) {
		return new AbstractModule() {
			@Override
			protected void configure() {

				com.google.inject.Provider<SourcesCollector> provider = binder().getProvider(SourcesCollector.class);

				bind(Queries.class).toProvider(() -> queriesSupplier.apply(provider.get().getSources()))
						.in(Singleton.class);
			}
		};
	}

	public static SourceModuleBuilder sourceModuleBuilder() {
		return new SourceModuleBuilder();
	}

	public static Module sourceModule(QueriesSource... sources) {
		return sourceModuleBuilder().sources(sources).build();
	}

	public static <T> Module proxyModule(Class<T> proxyInterface) {

		return new AbstractModule() {
			@Override
			protected void configure() {
				Provider<Queries> provider = getProvider(Queries.class);

				Multibinder<QueriesSource> sourcesBinder = Multibinder.newSetBinder(binder(), QueriesSource.class);
				sourcesBinder.addBinding().toInstance(QueriesSource.ofClass(proxyInterface));

				bind(proxyInterface).toProvider(new ProxyInterfaceProvider<T>(provider, proxyInterface))
						.in(Singleton.class);
			}
		};
	}

	@SafeVarargs
	public static Module sourceClassesModule(Class<QueriesSource>... sources) {
		return new AbstractModule() {
			@Override
			protected void configure() {
				Multibinder<QueriesSource> sourcesBinder = Multibinder.newSetBinder(binder(), QueriesSource.class);
				for (Class<QueriesSource> source : sources) {
					sourcesBinder.addBinding().to(source);
				}
			}
		};
	}

	public static class SourceModuleBuilder implements Sources<SourceModuleBuilder> {

		private List<QueriesSource> sources = new LinkedList<>();

		@Override
		public SourceModuleBuilder sources(QueriesSources sources) {
			this.sources.addAll(sources.getSources());
			return this;
		}

		public Module build() {
			return new AbstractModule() {
				@Override
				protected void configure() {
					Multibinder<QueriesSource> sourcesBinder = Multibinder.newSetBinder(binder(), QueriesSource.class);
					for (QueriesSource source : sources) {
						sourcesBinder.addBinding().toInstance(source);
					}
				}
			};
		}
	}

	@Singleton
	static class SourcesCollector {

		@Inject(optional = true)
		Set<QueriesSource> queriesSources = Collections.emptySet();
		
		@Inject(optional = true)
		Set<QueriesSource> queriesMappers = Collections.emptySet();

		QueriesSources getSources() {
			return QueriesSources.of(queriesSources);
		}
	}

	private static class ProxyInterfaceProvider<T> implements Provider<T> {

		private Provider<Queries> queriesProvider;
		private Class<T> proxyInterface;

		ProxyInterfaceProvider(Provider<Queries> queriesProvider, Class<T> proxyInterface) {
			this.queriesProvider = queriesProvider;
			this.proxyInterface = proxyInterface;
		}

		@Override
		public T get() {
			return queriesProvider.get().proxy(proxyInterface);
		}
	}
}

@@ -45,38 +45,8 @@
 e;%0A%0A
-import java.util.Collections;%0A
 impo
@@ -280,22 +280,19 @@
 .inject.
-Inject
+Key
 ;%0Aimport
@@ -310,32 +310,70 @@
 .inject.Module;%0A
+import com.google.inject.TypeLiteral;%0A
 import com.googl
@@ -865,112 +865,382 @@
 ) %7B%0A
-%0A
 %09%09%09%09
-com.google.inject.Provider%3CSourcesCollector%3E provider = binder().getProvider(SourcesCollector.class
+// MME called at least one time for guice to create empty list if no source is supplied%0A%09%09%09%09Multibinder.newSetBinder(binder(), QueriesSource.class);%0A%0A%09%09%09%09//Set%3CQueriesSource%3E queriesSources%0A%09%09%09%09TypeLiteral%3CSet%3CQueriesSource%3E%3E sourcesType = new TypeLiteral%3CSet%3CQueriesSource%3E%3E() %7B%0A%09%09%09%09%7D;%0A%09%09%09%09Provider%3CSet%3CQueriesSource%3E%3E sourcesProvider = getProvider(Key.get(sourcesType)
 );%0A%0A
@@ -1302,17 +1302,42 @@
 r.apply(
-p
+QueriesSources.of(sourcesP
 rovider.
@@ -1341,28 +1341,16 @@
 er.get()
-.getSources(
 )))%0A%09%09%09%09
@@ -3149,321 +3149,8 @@
 %09%7D%0A%0A
-%09@Singleton%0A%09static class SourcesCollector %7B%0A%0A%09%09@Inject(optional = true)%0A%09%09Set%3CQueriesSource%3E queriesSources = Collections.emptySet();%0A%09%09%0A%09%09@Inject(optional = true)%0A%09%09Set%3CQueriesSource%3E queriesMappers = Collections.emptySet();%0A%0A%09%09QueriesSources getSources() %7B%0A%09%09%09return QueriesSources.of(queriesSources);%0A%09%09%7D%0A%09%7D%0A%0A
 %09pri

Update Guice support, simplify sources injection